diff --git a/fine-commons-math3/pom.xml b/fine-commons-math3/pom.xml new file mode 100644 index 000000000..ae65c35cc --- /dev/null +++ b/fine-commons-math3/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + com.fr.third + commons-math3 + 3.6.1 + + ${basedir}/src + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.6 + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/Field.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/Field.java new file mode 100644 index 000000000..481924c5e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/Field.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3; + +/** + * Interface representing a field. + *

+ * Classes implementing this interface will often be singletons. + *

+ * @param the type of the field elements + * @see FieldElement + * @since 2.0 + */ +public interface Field { + + /** Get the additive identity of the field. + *

+ * The additive identity is the element e0 of the field such that + * for all elements a of the field, the equalities a + e0 = + * e0 + a = a hold. + *

+ * @return additive identity of the field + */ + T getZero(); + + /** Get the multiplicative identity of the field. + *

+ * The multiplicative identity is the element e1 of the field such that + * for all elements a of the field, the equalities a × e1 = + * e1 × a = a hold. + *

+ * @return multiplicative identity of the field + */ + T getOne(); + + /** + * Returns the runtime class of the FieldElement. + * + * @return The {@code Class} object that represents the runtime + * class of this object. + */ + Class> getRuntimeClass(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/FieldElement.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/FieldElement.java new file mode 100644 index 000000000..f36405188 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/FieldElement.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; + + +/** + * Interface representing field elements. + * @param the type of the field elements + * @see Field + * @since 2.0 + */ +public interface FieldElement { + + /** Compute this + a. + * @param a element to add + * @return a new element representing this + a + * @throws NullArgumentException if {@code a} is {@code null}. + */ + T add(T a) throws NullArgumentException; + + /** Compute this - a. + * @param a element to subtract + * @return a new element representing this - a + * @throws NullArgumentException if {@code a} is {@code null}. + */ + T subtract(T a) throws NullArgumentException; + + /** + * Returns the additive inverse of {@code this} element. + * @return the opposite of {@code this}. + */ + T negate(); + + /** Compute n × this. Multiplication by an integer number is defined + * as the following sum + *
+ * n × this = ∑i=1n this. + *
+ * @param n Number of times {@code this} must be added to itself. + * @return A new element representing n × this. + */ + T multiply(int n); + + /** Compute this × a. + * @param a element to multiply + * @return a new element representing this × a + * @throws NullArgumentException if {@code a} is {@code null}. + */ + T multiply(T a) throws NullArgumentException; + + /** Compute this ÷ a. + * @param a element to divide by + * @return a new element representing this ÷ a + * @throws NullArgumentException if {@code a} is {@code null}. + * @throws MathArithmeticException if {@code a} is zero + */ + T divide(T a) throws NullArgumentException, MathArithmeticException; + + /** + * Returns the multiplicative inverse of {@code this} element. + * @return the inverse of {@code this}. + * @throws MathArithmeticException if {@code this} is zero + */ + T reciprocal() throws MathArithmeticException; + + /** Get the {@link Field} to which the instance belongs. + * @return {@link Field} to which the instance belongs + */ + Field getField(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/RealFieldElement.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/RealFieldElement.java new file mode 100644 index 000000000..1ad610be2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/RealFieldElement.java @@ -0,0 +1,402 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * Interface representing a real + * field. + * @param the type of the field elements + * @see FieldElement + * @since 3.2 + */ +public interface RealFieldElement extends FieldElement { + + /** Get the real value of the number. + * @return real value + */ + double getReal(); + + /** '+' operator. + * @param a right hand side parameter of the operator + * @return this+a + */ + T add(double a); + + /** '-' operator. + * @param a right hand side parameter of the operator + * @return this-a + */ + T subtract(double a); + + /** '×' operator. + * @param a right hand side parameter of the operator + * @return this×a + */ + T multiply(double a); + + /** '÷' operator. + * @param a right hand side parameter of the operator + * @return this÷a + */ + T divide(double a); + + /** IEEE remainder operator. + * @param a right hand side parameter of the operator + * @return this - n × a where n is the closest integer to this/a + * (the even integer is chosen for n if this/a is halfway between two integers) + */ + T remainder(double a); + + /** IEEE remainder operator. + * @param a right hand side parameter of the operator + * @return this - n × a where n is the closest integer to this/a + * (the even integer is chosen for n if this/a is halfway between two integers) + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + T remainder(T a) + throws DimensionMismatchException; + + /** absolute value. + * @return abs(this) + */ + T abs(); + + /** Get the smallest whole number larger than instance. + * @return ceil(this) + */ + T ceil(); + + /** Get the largest whole number smaller than instance. + * @return floor(this) + */ + T floor(); + + /** Get the whole number that is the nearest to the instance, or the even one if x is exactly half way between two integers. + * @return a double number r such that r is an integer r - 0.5 ≤ this ≤ r + 0.5 + */ + T rint(); + + /** Get the closest long to instance value. + * @return closest long to {@link #getReal()} + */ + long round(); + + /** Compute the signum of the instance. + * The signum is -1 for negative numbers, +1 for positive numbers and 0 otherwise + * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a + */ + T signum(); + + /** + * Returns the instance with the sign of the argument. + * A NaN {@code sign} argument is treated as positive. + * + * @param sign the sign for the returned value + * @return the instance with the same sign as the {@code sign} argument + */ + T copySign(T sign); + + /** + * Returns the instance with the sign of the argument. + * A NaN {@code sign} argument is treated as positive. + * + * @param sign the sign for the returned value + * @return the instance with the same sign as the {@code sign} argument + */ + T copySign(double sign); + + /** + * Multiply the instance by a power of 2. + * @param n power of 2 + * @return this × 2n + */ + T scalb(int n); + + /** + * Returns the hypotenuse of a triangle with sides {@code this} and {@code y} + * - sqrt(this2 +y2) + * avoiding intermediate overflow or underflow. + * + *
    + *
  • If either argument is infinite, then the result is positive infinity.
  • + *
  • else, if either argument is NaN then the result is NaN.
  • + *
+ * + * @param y a value + * @return sqrt(this2 +y2) + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + T hypot(T y) + throws DimensionMismatchException; + + /** {@inheritDoc} */ + T reciprocal(); + + /** Square root. + * @return square root of the instance + */ + T sqrt(); + + /** Cubic root. + * @return cubic root of the instance + */ + T cbrt(); + + /** Nth root. + * @param n order of the root + * @return nth root of the instance + */ + T rootN(int n); + + /** Power operation. + * @param p power to apply + * @return thisp + */ + T pow(double p); + + /** Integer power operation. + * @param n power to apply + * @return thisn + */ + T pow(int n); + + /** Power operation. + * @param e exponent + * @return thise + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + T pow(T e) + throws DimensionMismatchException; + + /** Exponential. + * @return exponential of the instance + */ + T exp(); + + /** Exponential minus 1. + * @return exponential minus one of the instance + */ + T expm1(); + + /** Natural logarithm. + * @return logarithm of the instance + */ + T log(); + + /** Shifted natural logarithm. + * @return logarithm of one plus the instance + */ + T log1p(); + +// TODO: add this method in 4.0, as it is not possible to do it in 3.2 +// due to incompatibility of the return type in the Dfp class +// /** Base 10 logarithm. +// * @return base 10 logarithm of the instance +// */ +// T log10(); + + /** Cosine operation. + * @return cos(this) + */ + T cos(); + + /** Sine operation. + * @return sin(this) + */ + T sin(); + + /** Tangent operation. + * @return tan(this) + */ + T tan(); + + /** Arc cosine operation. + * @return acos(this) + */ + T acos(); + + /** Arc sine operation. + * @return asin(this) + */ + T asin(); + + /** Arc tangent operation. + * @return atan(this) + */ + T atan(); + + /** Two arguments arc tangent operation. + * @param x second argument of the arc tangent + * @return atan2(this, x) + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + T atan2(T x) + throws DimensionMismatchException; + + /** Hyperbolic cosine operation. + * @return cosh(this) + */ + T cosh(); + + /** Hyperbolic sine operation. + * @return sinh(this) + */ + T sinh(); + + /** Hyperbolic tangent operation. + * @return tanh(this) + */ + T tanh(); + + /** Inverse hyperbolic cosine operation. + * @return acosh(this) + */ + T acosh(); + + /** Inverse hyperbolic sine operation. + * @return asin(this) + */ + T asinh(); + + /** Inverse hyperbolic tangent operation. + * @return atanh(this) + */ + T atanh(); + + /** + * Compute a linear combination. + * @param a Factors. + * @param b Factors. + * @return Σi ai bi. + * @throws DimensionMismatchException if arrays dimensions don't match + * @since 3.2 + */ + T linearCombination(T[] a, T[] b) + throws DimensionMismatchException; + + /** + * Compute a linear combination. + * @param a Factors. + * @param b Factors. + * @return Σi ai bi. + * @throws DimensionMismatchException if arrays dimensions don't match + * @since 3.2 + */ + T linearCombination(double[] a, T[] b) + throws DimensionMismatchException; + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @return a1×b1 + + * a2×b2 + * @see #linearCombination(Object, Object, Object, Object, Object, Object) + * @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object) + * @since 3.2 + */ + T linearCombination(T a1, T b1, T a2, T b2); + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @return a1×b1 + + * a2×b2 + * @see #linearCombination(double, Object, double, Object, double, Object) + * @see #linearCombination(double, Object, double, Object, double, Object, double, Object) + * @since 3.2 + */ + T linearCombination(double a1, T b1, double a2, T b2); + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + * @see #linearCombination(Object, Object, Object, Object) + * @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object) + * @since 3.2 + */ + T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3); + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + * @see #linearCombination(double, Object, double, Object) + * @see #linearCombination(double, Object, double, Object, double, Object, double, Object) + * @since 3.2 + */ + T linearCombination(double a1, T b1, double a2, T b2, double a3, T b3); + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @param a4 first factor of the third term + * @param b4 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + + * a4×b4 + * @see #linearCombination(Object, Object, Object, Object) + * @see #linearCombination(Object, Object, Object, Object, Object, Object) + * @since 3.2 + */ + T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3, T a4, T b4); + + /** + * Compute a linear combination. + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @param a4 first factor of the third term + * @param b4 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + + * a4×b4 + * @see #linearCombination(double, Object, double, Object) + * @see #linearCombination(double, Object, double, Object, double, Object) + * @since 3.2 + */ + T linearCombination(double a1, T b1, double a2, T b2, double a3, T b3, double a4, T b4); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/BivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/BivariateFunction.java new file mode 100644 index 000000000..47429aeba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/BivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a bivariate real function. + * + * @since 2.1 + */ +public interface BivariateFunction { + /** + * Compute the value for the function. + * + * @param x Abscissa for which the function value should be computed. + * @param y Ordinate for which the function value should be computed. + * @return the value. + */ + double value(double x, double y); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.java new file mode 100644 index 000000000..48dd824e4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; + +/** + * Extension of {@link MultivariateFunction} representing a differentiable + * multivariate real function. + * @since 2.0 + * @deprecated as of 3.1 replaced by {@link MultivariateDifferentiableFunction} + */ +@Deprecated +public interface DifferentiableMultivariateFunction extends MultivariateFunction { + + /** + * Returns the partial derivative of the function with respect to a point coordinate. + *

+ * The partial derivative is defined with respect to point coordinate + * xk. If the partial derivatives with respect to all coordinates are + * needed, it may be more efficient to use the {@link #gradient()} method which will + * compute them all at once. + *

+ * @param k index of the coordinate with respect to which the partial + * derivative is computed + * @return the partial derivative function with respect to kth point coordinate + */ + MultivariateFunction partialDerivative(int k); + + /** + * Returns the gradient function. + *

If only one partial derivative with respect to a specific coordinate is + * needed, it may be more efficient to use the {@link #partialDerivative(int)} method + * which will compute only the specified component.

+ * @return the gradient function + */ + MultivariateVectorFunction gradient(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.java new file mode 100644 index 000000000..0f70a5b55 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; + +/** + * Extension of {@link MultivariateVectorFunction} representing a differentiable + * multivariate vectorial function. + * @since 2.0 + * @deprecated as of 3.1 replaced by {@link MultivariateDifferentiableVectorFunction} + */ +@Deprecated +public interface DifferentiableMultivariateVectorFunction + extends MultivariateVectorFunction { + + /** + * Returns the jacobian function. + * @return the jacobian function + */ + MultivariateMatrixFunction jacobian(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.java new file mode 100644 index 000000000..1e4127e19 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; + +/** + * Extension of {@link UnivariateFunction} representing a differentiable univariate real function. + * + * @deprecated as of 3.1 replaced by {@link UnivariateDifferentiableFunction} + */ +@Deprecated +public interface DifferentiableUnivariateFunction + extends UnivariateFunction { + + /** + * Returns the derivative of the function + * + * @return the derivative function + */ + UnivariateFunction derivative(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.java new file mode 100644 index 000000000..58b7b62d8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableMatrixFunction; + +/** + * Extension of {@link UnivariateMatrixFunction} representing a differentiable univariate matrix function. + * + * @since 2.0 + * @deprecated as of 3.1 replaced by {@link UnivariateDifferentiableMatrixFunction} + */ +@Deprecated +public interface DifferentiableUnivariateMatrixFunction + extends UnivariateMatrixFunction { + + /** + * Returns the derivative of the function + * + * @return the derivative function + */ + UnivariateMatrixFunction derivative(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.java new file mode 100644 index 000000000..1b1bd0dda --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableVectorFunction; + +/** + * Extension of {@link UnivariateVectorFunction} representing a differentiable univariate vectorial function. + * + * @since 2.0 + * @deprecated as of 3.1 replaced by {@link UnivariateDifferentiableVectorFunction} + */ +@Deprecated +public interface DifferentiableUnivariateVectorFunction + extends UnivariateVectorFunction { + + /** + * Returns the derivative of the function + * + * @return the derivative function + */ + UnivariateVectorFunction derivative(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/FunctionUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/FunctionUtils.java new file mode 100644 index 000000000..a5e474f35 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/FunctionUtils.java @@ -0,0 +1,806 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.function.Identity; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Utilities for manipulating function objects. + * + * @since 3.0 + */ +public class FunctionUtils { + /** + * Class only contains static methods. + */ + private FunctionUtils() {} + + /** + * Composes functions. + *

+ * The functions in the argument list are composed sequentially, in the + * given order. For example, compose(f1,f2,f3) acts like f1(f2(f3(x))).

+ * + * @param f List of functions. + * @return the composite function. + */ + public static UnivariateFunction compose(final UnivariateFunction ... f) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = x; + for (int i = f.length - 1; i >= 0; i--) { + r = f[i].value(r); + } + return r; + } + }; + } + + /** + * Composes functions. + *

+ * The functions in the argument list are composed sequentially, in the + * given order. For example, compose(f1,f2,f3) acts like f1(f2(f3(x))).

+ * + * @param f List of functions. + * @return the composite function. + * @since 3.1 + */ + public static UnivariateDifferentiableFunction compose(final UnivariateDifferentiableFunction ... f) { + return new UnivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double t) { + double r = t; + for (int i = f.length - 1; i >= 0; i--) { + r = f[i].value(r); + } + return r; + } + + /** {@inheritDoc} */ + public DerivativeStructure value(final DerivativeStructure t) { + DerivativeStructure r = t; + for (int i = f.length - 1; i >= 0; i--) { + r = f[i].value(r); + } + return r; + } + + }; + } + + /** + * Composes functions. + *

+ * The functions in the argument list are composed sequentially, in the + * given order. For example, compose(f1,f2,f3) acts like f1(f2(f3(x))).

+ * + * @param f List of functions. + * @return the composite function. + * @deprecated as of 3.1 replaced by {@link #compose(UnivariateDifferentiableFunction...)} + */ + @Deprecated + public static DifferentiableUnivariateFunction compose(final DifferentiableUnivariateFunction ... f) { + return new DifferentiableUnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = x; + for (int i = f.length - 1; i >= 0; i--) { + r = f[i].value(r); + } + return r; + } + + /** {@inheritDoc} */ + public UnivariateFunction derivative() { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double p = 1; + double r = x; + for (int i = f.length - 1; i >= 0; i--) { + p *= f[i].derivative().value(r); + r = f[i].value(r); + } + return p; + } + }; + } + }; + } + + /** + * Adds functions. + * + * @param f List of functions. + * @return a function that computes the sum of the functions. + */ + public static UnivariateFunction add(final UnivariateFunction ... f) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = f[0].value(x); + for (int i = 1; i < f.length; i++) { + r += f[i].value(x); + } + return r; + } + }; + } + + /** + * Adds functions. + * + * @param f List of functions. + * @return a function that computes the sum of the functions. + * @since 3.1 + */ + public static UnivariateDifferentiableFunction add(final UnivariateDifferentiableFunction ... f) { + return new UnivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double t) { + double r = f[0].value(t); + for (int i = 1; i < f.length; i++) { + r += f[i].value(t); + } + return r; + } + + /** {@inheritDoc} + * @throws DimensionMismatchException if functions are not consistent with each other + */ + public DerivativeStructure value(final DerivativeStructure t) + throws DimensionMismatchException { + DerivativeStructure r = f[0].value(t); + for (int i = 1; i < f.length; i++) { + r = r.add(f[i].value(t)); + } + return r; + } + + }; + } + + /** + * Adds functions. + * + * @param f List of functions. + * @return a function that computes the sum of the functions. + * @deprecated as of 3.1 replaced by {@link #add(UnivariateDifferentiableFunction...)} + */ + @Deprecated + public static DifferentiableUnivariateFunction add(final DifferentiableUnivariateFunction ... f) { + return new DifferentiableUnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = f[0].value(x); + for (int i = 1; i < f.length; i++) { + r += f[i].value(x); + } + return r; + } + + /** {@inheritDoc} */ + public UnivariateFunction derivative() { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = f[0].derivative().value(x); + for (int i = 1; i < f.length; i++) { + r += f[i].derivative().value(x); + } + return r; + } + }; + } + }; + } + + /** + * Multiplies functions. + * + * @param f List of functions. + * @return a function that computes the product of the functions. + */ + public static UnivariateFunction multiply(final UnivariateFunction ... f) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = f[0].value(x); + for (int i = 1; i < f.length; i++) { + r *= f[i].value(x); + } + return r; + } + }; + } + + /** + * Multiplies functions. + * + * @param f List of functions. + * @return a function that computes the product of the functions. + * @since 3.1 + */ + public static UnivariateDifferentiableFunction multiply(final UnivariateDifferentiableFunction ... f) { + return new UnivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double t) { + double r = f[0].value(t); + for (int i = 1; i < f.length; i++) { + r *= f[i].value(t); + } + return r; + } + + /** {@inheritDoc} */ + public DerivativeStructure value(final DerivativeStructure t) { + DerivativeStructure r = f[0].value(t); + for (int i = 1; i < f.length; i++) { + r = r.multiply(f[i].value(t)); + } + return r; + } + + }; + } + + /** + * Multiplies functions. + * + * @param f List of functions. + * @return a function that computes the product of the functions. + * @deprecated as of 3.1 replaced by {@link #multiply(UnivariateDifferentiableFunction...)} + */ + @Deprecated + public static DifferentiableUnivariateFunction multiply(final DifferentiableUnivariateFunction ... f) { + return new DifferentiableUnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double r = f[0].value(x); + for (int i = 1; i < f.length; i++) { + r *= f[i].value(x); + } + return r; + } + + /** {@inheritDoc} */ + public UnivariateFunction derivative() { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + double sum = 0; + for (int i = 0; i < f.length; i++) { + double prod = f[i].derivative().value(x); + for (int j = 0; j < f.length; j++) { + if (i != j) { + prod *= f[j].value(x); + } + } + sum += prod; + } + return sum; + } + }; + } + }; + } + + /** + * Returns the univariate function + * {@code h(x) = combiner(f(x), g(x)).} + * + * @param combiner Combiner function. + * @param f Function. + * @param g Function. + * @return the composite function. + */ + public static UnivariateFunction combine(final BivariateFunction combiner, + final UnivariateFunction f, + final UnivariateFunction g) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + return combiner.value(f.value(x), g.value(x)); + } + }; + } + + /** + * Returns a MultivariateFunction h(x[]) defined by
 
+     * h(x[]) = combiner(...combiner(combiner(initialValue,f(x[0])),f(x[1]))...),f(x[x.length-1]))
+     * 
+ * + * @param combiner Combiner function. + * @param f Function. + * @param initialValue Initial value. + * @return a collector function. + */ + public static MultivariateFunction collector(final BivariateFunction combiner, + final UnivariateFunction f, + final double initialValue) { + return new MultivariateFunction() { + /** {@inheritDoc} */ + public double value(double[] point) { + double result = combiner.value(initialValue, f.value(point[0])); + for (int i = 1; i < point.length; i++) { + result = combiner.value(result, f.value(point[i])); + } + return result; + } + }; + } + + /** + * Returns a MultivariateFunction h(x[]) defined by
 
+     * h(x[]) = combiner(...combiner(combiner(initialValue,x[0]),x[1])...),x[x.length-1])
+     * 
+ * + * @param combiner Combiner function. + * @param initialValue Initial value. + * @return a collector function. + */ + public static MultivariateFunction collector(final BivariateFunction combiner, + final double initialValue) { + return collector(combiner, new Identity(), initialValue); + } + + /** + * Creates a unary function by fixing the first argument of a binary function. + * + * @param f Binary function. + * @param fixed value to which the first argument of {@code f} is set. + * @return the unary function h(x) = f(fixed, x) + */ + public static UnivariateFunction fix1stArgument(final BivariateFunction f, + final double fixed) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + return f.value(fixed, x); + } + }; + } + /** + * Creates a unary function by fixing the second argument of a binary function. + * + * @param f Binary function. + * @param fixed value to which the second argument of {@code f} is set. + * @return the unary function h(x) = f(x, fixed) + */ + public static UnivariateFunction fix2ndArgument(final BivariateFunction f, + final double fixed) { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) { + return f.value(x, fixed); + } + }; + } + + /** + * Samples the specified univariate real function on the specified interval. + *

+ * The interval is divided equally into {@code n} sections and sample points + * are taken from {@code min} to {@code max - (max - min) / n}; therefore + * {@code f} is not sampled at the upper bound {@code max}.

+ * + * @param f Function to be sampled + * @param min Lower bound of the interval (included). + * @param max Upper bound of the interval (excluded). + * @param n Number of sample points. + * @return the array of samples. + * @throws NumberIsTooLargeException if the lower bound {@code min} is + * greater than, or equal to the upper bound {@code max}. + * @throws NotStrictlyPositiveException if the number of sample points + * {@code n} is negative. + */ + public static double[] sample(UnivariateFunction f, double min, double max, int n) + throws NumberIsTooLargeException, NotStrictlyPositiveException { + + if (n <= 0) { + throw new NotStrictlyPositiveException( + LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES, + Integer.valueOf(n)); + } + if (min >= max) { + throw new NumberIsTooLargeException(min, max, false); + } + + final double[] s = new double[n]; + final double h = (max - min) / n; + for (int i = 0; i < n; i++) { + s[i] = f.value(min + i * h); + } + return s; + } + + /** + * Convert a {@link UnivariateDifferentiableFunction} into a {@link DifferentiableUnivariateFunction}. + * + * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableUnivariateFunction} interface itself is deprecated + */ + @Deprecated + public static DifferentiableUnivariateFunction toDifferentiableUnivariateFunction(final UnivariateDifferentiableFunction f) { + return new DifferentiableUnivariateFunction() { + + /** {@inheritDoc} */ + public double value(final double x) { + return f.value(x); + } + + /** {@inheritDoc} */ + public UnivariateFunction derivative() { + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(final double x) { + return f.value(new DerivativeStructure(1, 1, 0, x)).getPartialDerivative(1); + } + }; + } + + }; + } + + /** + * Convert a {@link DifferentiableUnivariateFunction} into a {@link UnivariateDifferentiableFunction}. + *

+ * Note that the converted function is able to handle {@link DerivativeStructure} up to order one. + * If the function is called with higher order, a {@link NumberIsTooLargeException} is thrown. + *

+ * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableUnivariateFunction} interface itself is deprecated + */ + @Deprecated + public static UnivariateDifferentiableFunction toUnivariateDifferential(final DifferentiableUnivariateFunction f) { + return new UnivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double x) { + return f.value(x); + } + + /** {@inheritDoc} + * @exception NumberIsTooLargeException if derivation order is greater than 1 + */ + public DerivativeStructure value(final DerivativeStructure t) + throws NumberIsTooLargeException { + switch (t.getOrder()) { + case 0 : + return new DerivativeStructure(t.getFreeParameters(), 0, f.value(t.getValue())); + case 1 : { + final int parameters = t.getFreeParameters(); + final double[] derivatives = new double[parameters + 1]; + derivatives[0] = f.value(t.getValue()); + final double fPrime = f.derivative().value(t.getValue()); + int[] orders = new int[parameters]; + for (int i = 0; i < parameters; ++i) { + orders[i] = 1; + derivatives[i + 1] = fPrime * t.getPartialDerivative(orders); + orders[i] = 0; + } + return new DerivativeStructure(parameters, 1, derivatives); + } + default : + throw new NumberIsTooLargeException(t.getOrder(), 1, true); + } + } + + }; + } + + /** + * Convert a {@link MultivariateDifferentiableFunction} into a {@link DifferentiableMultivariateFunction}. + * + * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableMultivariateFunction} interface itself is deprecated + */ + @Deprecated + public static DifferentiableMultivariateFunction toDifferentiableMultivariateFunction(final MultivariateDifferentiableFunction f) { + return new DifferentiableMultivariateFunction() { + + /** {@inheritDoc} */ + public double value(final double[] x) { + return f.value(x); + } + + /** {@inheritDoc} */ + public MultivariateFunction partialDerivative(final int k) { + return new MultivariateFunction() { + /** {@inheritDoc} */ + public double value(final double[] x) { + + final int n = x.length; + + // delegate computation to underlying function + final DerivativeStructure[] dsX = new DerivativeStructure[n]; + for (int i = 0; i < n; ++i) { + if (i == k) { + dsX[i] = new DerivativeStructure(1, 1, 0, x[i]); + } else { + dsX[i] = new DerivativeStructure(1, 1, x[i]); + } + } + final DerivativeStructure y = f.value(dsX); + + // extract partial derivative + return y.getPartialDerivative(1); + + } + }; + } + + /** {@inheritDoc} */ + public MultivariateVectorFunction gradient() { + return new MultivariateVectorFunction() { + /** {@inheritDoc} */ + public double[] value(final double[] x) { + + final int n = x.length; + + // delegate computation to underlying function + final DerivativeStructure[] dsX = new DerivativeStructure[n]; + for (int i = 0; i < n; ++i) { + dsX[i] = new DerivativeStructure(n, 1, i, x[i]); + } + final DerivativeStructure y = f.value(dsX); + + // extract gradient + final double[] gradient = new double[n]; + final int[] orders = new int[n]; + for (int i = 0; i < n; ++i) { + orders[i] = 1; + gradient[i] = y.getPartialDerivative(orders); + orders[i] = 0; + } + + return gradient; + + } + }; + } + + }; + } + + /** + * Convert a {@link DifferentiableMultivariateFunction} into a {@link MultivariateDifferentiableFunction}. + *

+ * Note that the converted function is able to handle {@link DerivativeStructure} elements + * that all have the same number of free parameters and order, and with order at most 1. + * If the function is called with inconsistent numbers of free parameters or higher order, a + * {@link DimensionMismatchException} or a {@link NumberIsTooLargeException} will be thrown. + *

+ * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableMultivariateFunction} interface itself is deprecated + */ + @Deprecated + public static MultivariateDifferentiableFunction toMultivariateDifferentiableFunction(final DifferentiableMultivariateFunction f) { + return new MultivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double[] x) { + return f.value(x); + } + + /** {@inheritDoc} + * @exception NumberIsTooLargeException if derivation order is higher than 1 + * @exception DimensionMismatchException if numbers of free parameters are inconsistent + */ + public DerivativeStructure value(final DerivativeStructure[] t) + throws DimensionMismatchException, NumberIsTooLargeException { + + // check parameters and orders limits + final int parameters = t[0].getFreeParameters(); + final int order = t[0].getOrder(); + final int n = t.length; + if (order > 1) { + throw new NumberIsTooLargeException(order, 1, true); + } + + // check all elements in the array are consistent + for (int i = 0; i < n; ++i) { + if (t[i].getFreeParameters() != parameters) { + throw new DimensionMismatchException(t[i].getFreeParameters(), parameters); + } + + if (t[i].getOrder() != order) { + throw new DimensionMismatchException(t[i].getOrder(), order); + } + } + + // delegate computation to underlying function + final double[] point = new double[n]; + for (int i = 0; i < n; ++i) { + point[i] = t[i].getValue(); + } + final double value = f.value(point); + final double[] gradient = f.gradient().value(point); + + // merge value and gradient into one DerivativeStructure + final double[] derivatives = new double[parameters + 1]; + derivatives[0] = value; + final int[] orders = new int[parameters]; + for (int i = 0; i < parameters; ++i) { + orders[i] = 1; + for (int j = 0; j < n; ++j) { + derivatives[i + 1] += gradient[j] * t[j].getPartialDerivative(orders); + } + orders[i] = 0; + } + + return new DerivativeStructure(parameters, order, derivatives); + + } + + }; + } + + /** + * Convert a {@link MultivariateDifferentiableVectorFunction} into a {@link DifferentiableMultivariateVectorFunction}. + * + * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableMultivariateVectorFunction} interface itself is deprecated + */ + @Deprecated + public static DifferentiableMultivariateVectorFunction toDifferentiableMultivariateVectorFunction(final MultivariateDifferentiableVectorFunction f) { + return new DifferentiableMultivariateVectorFunction() { + + /** {@inheritDoc} */ + public double[] value(final double[] x) { + return f.value(x); + } + + /** {@inheritDoc} */ + public MultivariateMatrixFunction jacobian() { + return new MultivariateMatrixFunction() { + /** {@inheritDoc} */ + public double[][] value(final double[] x) { + + final int n = x.length; + + // delegate computation to underlying function + final DerivativeStructure[] dsX = new DerivativeStructure[n]; + for (int i = 0; i < n; ++i) { + dsX[i] = new DerivativeStructure(n, 1, i, x[i]); + } + final DerivativeStructure[] y = f.value(dsX); + + // extract Jacobian + final double[][] jacobian = new double[y.length][n]; + final int[] orders = new int[n]; + for (int i = 0; i < y.length; ++i) { + for (int j = 0; j < n; ++j) { + orders[j] = 1; + jacobian[i][j] = y[i].getPartialDerivative(orders); + orders[j] = 0; + } + } + + return jacobian; + + } + }; + } + + }; + } + + /** + * Convert a {@link DifferentiableMultivariateVectorFunction} into a {@link MultivariateDifferentiableVectorFunction}. + *

+ * Note that the converted function is able to handle {@link DerivativeStructure} elements + * that all have the same number of free parameters and order, and with order at most 1. + * If the function is called with inconsistent numbers of free parameters or higher order, a + * {@link DimensionMismatchException} or a {@link NumberIsTooLargeException} will be thrown. + *

+ * @param f function to convert + * @return converted function + * @deprecated this conversion method is temporary in version 3.1, as the {@link + * DifferentiableMultivariateFunction} interface itself is deprecated + */ + @Deprecated + public static MultivariateDifferentiableVectorFunction toMultivariateDifferentiableVectorFunction(final DifferentiableMultivariateVectorFunction f) { + return new MultivariateDifferentiableVectorFunction() { + + /** {@inheritDoc} */ + public double[] value(final double[] x) { + return f.value(x); + } + + /** {@inheritDoc} + * @exception NumberIsTooLargeException if derivation order is higher than 1 + * @exception DimensionMismatchException if numbers of free parameters are inconsistent + */ + public DerivativeStructure[] value(final DerivativeStructure[] t) + throws DimensionMismatchException, NumberIsTooLargeException { + + // check parameters and orders limits + final int parameters = t[0].getFreeParameters(); + final int order = t[0].getOrder(); + final int n = t.length; + if (order > 1) { + throw new NumberIsTooLargeException(order, 1, true); + } + + // check all elements in the array are consistent + for (int i = 0; i < n; ++i) { + if (t[i].getFreeParameters() != parameters) { + throw new DimensionMismatchException(t[i].getFreeParameters(), parameters); + } + + if (t[i].getOrder() != order) { + throw new DimensionMismatchException(t[i].getOrder(), order); + } + } + + // delegate computation to underlying function + final double[] point = new double[n]; + for (int i = 0; i < n; ++i) { + point[i] = t[i].getValue(); + } + final double[] value = f.value(point); + final double[][] jacobian = f.jacobian().value(point); + + // merge value and Jacobian into a DerivativeStructure array + final DerivativeStructure[] merged = new DerivativeStructure[value.length]; + for (int k = 0; k < merged.length; ++k) { + final double[] derivatives = new double[parameters + 1]; + derivatives[0] = value[k]; + final int[] orders = new int[parameters]; + for (int i = 0; i < parameters; ++i) { + orders[i] = 1; + for (int j = 0; j < n; ++j) { + derivatives[i + 1] += jacobian[k][j] * t[j].getPartialDerivative(orders); + } + orders[i] = 0; + } + merged[k] = new DerivativeStructure(parameters, order, derivatives); + } + + return merged; + + } + + }; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateFunction.java new file mode 100644 index 000000000..69f741bd5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * An interface representing a multivariate real function. + * + * @since 2.0 + */ +public interface MultivariateFunction { + + /** + * Compute the value for the function at the given point. + * + * @param point Point at which the function must be evaluated. + * @return the function value for the given point. + * @throws DimensionMismatchException + * if the parameter's dimension is wrong for the function being evaluated. + * @throws MathIllegalArgumentException + * when the activated method itself can ascertain that preconditions, + * specified in the API expressed at the level of the activated method, + * have been violated. In the vast majority of cases where Commons Math + * throws this exception, it is the result of argument checking of actual + * parameters immediately passed to a method. + */ + double value(double[] point); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java new file mode 100644 index 000000000..a393b33a4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a multivariate matrix function. + * @since 2.0 + */ +public interface MultivariateMatrixFunction { + + /** + * Compute the value for the function at the given point. + * @param point point at which the function must be evaluated + * @return function value for the given point + * @exception IllegalArgumentException if point's dimension is wrong + */ + double[][] value(double[] point) + throws IllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateVectorFunction.java new file mode 100644 index 000000000..ad2d64f48 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/MultivariateVectorFunction.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a multivariate vectorial function. + * @since 2.0 + */ +public interface MultivariateVectorFunction { + + /** + * Compute the value for the function at the given point. + * @param point point at which the function must be evaluated + * @return function value for the given point + * @exception IllegalArgumentException if point's dimension is wrong + */ + double[] value(double[] point) + throws IllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java new file mode 100644 index 000000000..35e767dba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a real function that depends on one independent + * variable plus some extra parameters. + * + * @since 3.0 + */ +public interface ParametricUnivariateFunction { + /** + * Compute the value of the function. + * + * @param x Point for which the function value should be computed. + * @param parameters Function parameters. + * @return the value. + */ + double value(double x, double ... parameters); + + /** + * Compute the gradient of the function with respect to its parameters. + * + * @param x Point for which the function value should be computed. + * @param parameters Function parameters. + * @return the value. + */ + double[] gradient(double x, double ... parameters); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.java new file mode 100644 index 000000000..30b555db3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * An interface representing a univariate real function. + *

+ * When a user-defined function encounters an error during + * evaluation, the {@link #value(RealFieldElement) value} method should throw a + * user-defined unchecked exception.

+ *

+ * The following code excerpt shows the recommended way to do that using + * a root solver as an example, but the same construct is applicable to + * ODE integrators or optimizers.

+ * + *
+ * private static class LocalException extends RuntimeException {
+ *     // The x value that caused the problem.
+ *     private final SomeFieldType x;
+ *
+ *     public LocalException(SomeFieldType x) {
+ *         this.x = x;
+ *     }
+ *
+ *     public double getX() {
+ *         return x;
+ *     }
+ * }
+ *
+ * private static class MyFunction implements FieldUnivariateFunction<SomeFieldType> {
+ *     public SomeFieldType value(SomeFieldType x) {
+ *         SomeFieldType y = hugeFormula(x);
+ *         if (somethingBadHappens) {
+ *           throw new LocalException(x);
+ *         }
+ *         return y;
+ *     }
+ * }
+ *
+ * public void compute() {
+ *     try {
+ *         solver.solve(maxEval, new MyFunction(a, b, c), min, max);
+ *     } catch (LocalException le) {
+ *         // Retrieve the x value.
+ *     }
+ * }
+ * 
+ * + * As shown, the exception is local to the user's code and it is guaranteed + * that Apache Commons Math will not catch it. + * + * @param the type of the field elements + * @since 3.6 + * @see UnivariateFunction + */ +public interface RealFieldUnivariateFunction> { + /** + * Compute the value of the function. + * + * @param x Point at which the function value should be computed. + * @return the value of the function. + * @throws IllegalArgumentException when the activated method itself can + * ascertain that a precondition, specified in the API expressed at the + * level of the activated method, has been violated. + * When Commons Math throws an {@code IllegalArgumentException}, it is + * usually the consequence of checking the actual parameters passed to + * the method. + */ + T value(T x); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/TrivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/TrivariateFunction.java new file mode 100644 index 000000000..c39745d0a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/TrivariateFunction.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a trivariate real function. + * + * @since 2.2 + */ +public interface TrivariateFunction { + /** + * Compute the value for the function. + * + * @param x x-coordinate for which the function value should be computed. + * @param y y-coordinate for which the function value should be computed. + * @param z z-coordinate for which the function value should be computed. + * @return the value. + */ + double value(double x, double y, double z); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateFunction.java new file mode 100644 index 000000000..680594714 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateFunction.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 com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a univariate real function. + *

+ * When a user-defined function encounters an error during + * evaluation, the {@link #value(double) value} method should throw a + * user-defined unchecked exception.

+ *

+ * The following code excerpt shows the recommended way to do that using + * a root solver as an example, but the same construct is applicable to + * ODE integrators or optimizers.

+ * + *
+ * private static class LocalException extends RuntimeException {
+ *     // The x value that caused the problem.
+ *     private final double x;
+ *
+ *     public LocalException(double x) {
+ *         this.x = x;
+ *     }
+ *
+ *     public double getX() {
+ *         return x;
+ *     }
+ * }
+ *
+ * private static class MyFunction implements UnivariateFunction {
+ *     public double value(double x) {
+ *         double y = hugeFormula(x);
+ *         if (somethingBadHappens) {
+ *           throw new LocalException(x);
+ *         }
+ *         return y;
+ *     }
+ * }
+ *
+ * public void compute() {
+ *     try {
+ *         solver.solve(maxEval, new MyFunction(a, b, c), min, max);
+ *     } catch (LocalException le) {
+ *         // Retrieve the x value.
+ *     }
+ * }
+ * 
+ * + * As shown, the exception is local to the user's code and it is guaranteed + * that Apache Commons Math will not catch it. + * + */ +public interface UnivariateFunction { + /** + * Compute the value of the function. + * + * @param x Point at which the function value should be computed. + * @return the value of the function. + * @throws IllegalArgumentException when the activated method itself can + * ascertain that a precondition, specified in the API expressed at the + * level of the activated method, has been violated. + * When Commons Math throws an {@code IllegalArgumentException}, it is + * usually the consequence of checking the actual parameters passed to + * the method. + */ + double value(double x); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java new file mode 100644 index 000000000..112b5abe4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a univariate matrix function. + * + * @since 2.0 + */ +public interface UnivariateMatrixFunction { + + /** + * Compute the value for the function. + * @param x the point for which the function value should be computed + * @return the value + */ + double[][] value(double x); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateVectorFunction.java new file mode 100644 index 000000000..da9019632 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/UnivariateVectorFunction.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis; + +/** + * An interface representing a univariate vectorial function. + * + * @since 2.0 + */ +public interface UnivariateVectorFunction { + + /** + * Compute the value for the function. + * @param x the point for which the function value should be computed + * @return the value + */ + double[] value(double x); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DSCompiler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DSCompiler.java new file mode 100644 index 000000000..bfde9cc96 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DSCompiler.java @@ -0,0 +1,1820 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** Class holding "compiled" computation rules for derivative structures. + *

This class implements the computation rules described in Dan Kalman's paper Doubly + * Recursive Multivariate Automatic Differentiation, Mathematics Magazine, vol. 75, + * no. 3, June 2002. However, in order to avoid performances bottlenecks, the recursive + * rules are "compiled" once in an unfold form. This class does this recursion unrolling + * and stores the computation rules as simple loops with pre-computed indirection arrays.

+ *

+ * This class maps all derivative computation into single dimension arrays that hold the + * value and partial derivatives. The class does not hold these arrays, which remains under + * the responsibility of the caller. For each combination of number of free parameters and + * derivation order, only one compiler is necessary, and this compiler will be used to + * perform computations on all arrays provided to it, which can represent hundreds or + * thousands of different parameters kept together with all theur partial derivatives. + *

+ *

+ * The arrays on which compilers operate contain only the partial derivatives together + * with the 0th derivative, i.e. the value. The partial derivatives are stored in + * a compiler-specific order, which can be retrieved using methods {@link + * #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} and {@link + * #getPartialDerivativeOrders(int)}. The value is guaranteed to be stored as the first element + * (i.e. the {@link #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} method returns + * 0 when called with 0 for all derivation orders and {@link #getPartialDerivativeOrders(int) + * getPartialDerivativeOrders} returns an array filled with 0 when called with 0 as the index). + *

+ *

+ * Note that the ordering changes with number of parameters and derivation order. For example + * given 2 parameters x and y, df/dy is stored at index 2 when derivation order is set to 1 (in + * this case the array has three elements: f, df/dx and df/dy). If derivation order is set to + * 2, then df/dy will be stored at index 3 (in this case the array has six elements: f, df/dx, + * df/dxdx, df/dy, df/dxdy and df/dydy). + *

+ *

+ * Given this structure, users can perform some simple operations like adding, subtracting + * or multiplying constants and negating the elements by themselves, knowing if they want to + * mutate their array or create a new array. These simple operations are not provided by + * the compiler. The compiler provides only the more complex operations between several arrays. + *

+ *

This class is mainly used as the engine for scalar variable {@link DerivativeStructure}. + * It can also be used directly to hold several variables in arrays for more complex data + * structures. User can for example store a vector of n variables depending on three x, y + * and z free parameters in one array as follows:

+ *   // parameter 0 is x, parameter 1 is y, parameter 2 is z
+ *   int parameters = 3;
+ *   DSCompiler compiler = DSCompiler.getCompiler(parameters, order);
+ *   int size = compiler.getSize();
+ *
+ *   // pack all elements in a single array
+ *   double[] array = new double[n * size];
+ *   for (int i = 0; i < n; ++i) {
+ *
+ *     // we know value is guaranteed to be the first element
+ *     array[i * size] = v[i];
+ *
+ *     // we don't know where first derivatives are stored, so we ask the compiler
+ *     array[i * size + compiler.getPartialDerivativeIndex(1, 0, 0) = dvOnDx[i][0];
+ *     array[i * size + compiler.getPartialDerivativeIndex(0, 1, 0) = dvOnDy[i][0];
+ *     array[i * size + compiler.getPartialDerivativeIndex(0, 0, 1) = dvOnDz[i][0];
+ *
+ *     // we let all higher order derivatives set to 0
+ *
+ *   }
+ * 
+ *

Then in another function, user can perform some operations on all elements stored + * in the single array, such as a simple product of all variables:

+ *   // compute the product of all elements
+ *   double[] product = new double[size];
+ *   prod[0] = 1.0;
+ *   for (int i = 0; i < n; ++i) {
+ *     double[] tmp = product.clone();
+ *     compiler.multiply(tmp, 0, array, i * size, product, 0);
+ *   }
+ *
+ *   // value
+ *   double p = product[0];
+ *
+ *   // first derivatives
+ *   double dPdX = product[compiler.getPartialDerivativeIndex(1, 0, 0)];
+ *   double dPdY = product[compiler.getPartialDerivativeIndex(0, 1, 0)];
+ *   double dPdZ = product[compiler.getPartialDerivativeIndex(0, 0, 1)];
+ *
+ *   // cross derivatives (assuming order was at least 2)
+ *   double dPdXdX = product[compiler.getPartialDerivativeIndex(2, 0, 0)];
+ *   double dPdXdY = product[compiler.getPartialDerivativeIndex(1, 1, 0)];
+ *   double dPdXdZ = product[compiler.getPartialDerivativeIndex(1, 0, 1)];
+ *   double dPdYdY = product[compiler.getPartialDerivativeIndex(0, 2, 0)];
+ *   double dPdYdZ = product[compiler.getPartialDerivativeIndex(0, 1, 1)];
+ *   double dPdZdZ = product[compiler.getPartialDerivativeIndex(0, 0, 2)];
+ * 
+ * @see DerivativeStructure + * @since 3.1 + */ +public class DSCompiler { + + /** Array of all compilers created so far. */ + private static AtomicReference compilers = + new AtomicReference(null); + + /** Number of free parameters. */ + private final int parameters; + + /** Derivation order. */ + private final int order; + + /** Number of partial derivatives (including the single 0 order derivative element). */ + private final int[][] sizes; + + /** Indirection array for partial derivatives. */ + private final int[][] derivativesIndirection; + + /** Indirection array of the lower derivative elements. */ + private final int[] lowerIndirection; + + /** Indirection arrays for multiplication. */ + private final int[][][] multIndirection; + + /** Indirection arrays for function composition. */ + private final int[][][] compIndirection; + + /** Private constructor, reserved for the factory method {@link #getCompiler(int, int)}. + * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @param derivativeCompiler compiler for the derivative part + * @throws NumberIsTooLargeException if order is too large + */ + private DSCompiler(final int parameters, final int order, + final DSCompiler valueCompiler, final DSCompiler derivativeCompiler) + throws NumberIsTooLargeException { + + this.parameters = parameters; + this.order = order; + this.sizes = compileSizes(parameters, order, valueCompiler); + this.derivativesIndirection = + compileDerivativesIndirection(parameters, order, + valueCompiler, derivativeCompiler); + this.lowerIndirection = + compileLowerIndirection(parameters, order, + valueCompiler, derivativeCompiler); + this.multIndirection = + compileMultiplicationIndirection(parameters, order, + valueCompiler, derivativeCompiler, lowerIndirection); + this.compIndirection = + compileCompositionIndirection(parameters, order, + valueCompiler, derivativeCompiler, + sizes, derivativesIndirection); + + } + + /** Get the compiler for number of free parameters and order. + * @param parameters number of free parameters + * @param order derivation order + * @return cached rules set + * @throws NumberIsTooLargeException if order is too large + */ + public static DSCompiler getCompiler(int parameters, int order) + throws NumberIsTooLargeException { + + // get the cached compilers + final DSCompiler[][] cache = compilers.get(); + if (cache != null && cache.length > parameters && + cache[parameters].length > order && cache[parameters][order] != null) { + // the compiler has already been created + return cache[parameters][order]; + } + + // we need to create more compilers + final int maxParameters = FastMath.max(parameters, cache == null ? 0 : cache.length); + final int maxOrder = FastMath.max(order, cache == null ? 0 : cache[0].length); + final DSCompiler[][] newCache = new DSCompiler[maxParameters + 1][maxOrder + 1]; + + if (cache != null) { + // preserve the already created compilers + for (int i = 0; i < cache.length; ++i) { + System.arraycopy(cache[i], 0, newCache[i], 0, cache[i].length); + } + } + + // create the array in increasing diagonal order + for (int diag = 0; diag <= parameters + order; ++diag) { + for (int o = FastMath.max(0, diag - parameters); o <= FastMath.min(order, diag); ++o) { + final int p = diag - o; + if (newCache[p][o] == null) { + final DSCompiler valueCompiler = (p == 0) ? null : newCache[p - 1][o]; + final DSCompiler derivativeCompiler = (o == 0) ? null : newCache[p][o - 1]; + newCache[p][o] = new DSCompiler(p, o, valueCompiler, derivativeCompiler); + } + } + } + + // atomically reset the cached compilers array + compilers.compareAndSet(cache, newCache); + + return newCache[parameters][order]; + + } + + /** Compile the sizes array. + * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @return sizes array + */ + private static int[][] compileSizes(final int parameters, final int order, + final DSCompiler valueCompiler) { + + final int[][] sizes = new int[parameters + 1][order + 1]; + if (parameters == 0) { + Arrays.fill(sizes[0], 1); + } else { + System.arraycopy(valueCompiler.sizes, 0, sizes, 0, parameters); + sizes[parameters][0] = 1; + for (int i = 0; i < order; ++i) { + sizes[parameters][i + 1] = sizes[parameters][i] + sizes[parameters - 1][i + 1]; + } + } + + return sizes; + + } + + /** Compile the derivatives indirection array. + * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @param derivativeCompiler compiler for the derivative part + * @return derivatives indirection array + */ + private static int[][] compileDerivativesIndirection(final int parameters, final int order, + final DSCompiler valueCompiler, + final DSCompiler derivativeCompiler) { + + if (parameters == 0 || order == 0) { + return new int[1][parameters]; + } + + final int vSize = valueCompiler.derivativesIndirection.length; + final int dSize = derivativeCompiler.derivativesIndirection.length; + final int[][] derivativesIndirection = new int[vSize + dSize][parameters]; + + // set up the indices for the value part + for (int i = 0; i < vSize; ++i) { + // copy the first indices, the last one remaining set to 0 + System.arraycopy(valueCompiler.derivativesIndirection[i], 0, + derivativesIndirection[i], 0, + parameters - 1); + } + + // set up the indices for the derivative part + for (int i = 0; i < dSize; ++i) { + + // copy the indices + System.arraycopy(derivativeCompiler.derivativesIndirection[i], 0, + derivativesIndirection[vSize + i], 0, + parameters); + + // increment the derivation order for the last parameter + derivativesIndirection[vSize + i][parameters - 1]++; + + } + + return derivativesIndirection; + + } + + /** Compile the lower derivatives indirection array. + *

+ * This indirection array contains the indices of all elements + * except derivatives for last derivation order. + *

+ * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @param derivativeCompiler compiler for the derivative part + * @return lower derivatives indirection array + */ + private static int[] compileLowerIndirection(final int parameters, final int order, + final DSCompiler valueCompiler, + final DSCompiler derivativeCompiler) { + + if (parameters == 0 || order <= 1) { + return new int[] { 0 }; + } + + // this is an implementation of definition 6 in Dan Kalman's paper. + final int vSize = valueCompiler.lowerIndirection.length; + final int dSize = derivativeCompiler.lowerIndirection.length; + final int[] lowerIndirection = new int[vSize + dSize]; + System.arraycopy(valueCompiler.lowerIndirection, 0, lowerIndirection, 0, vSize); + for (int i = 0; i < dSize; ++i) { + lowerIndirection[vSize + i] = valueCompiler.getSize() + derivativeCompiler.lowerIndirection[i]; + } + + return lowerIndirection; + + } + + /** Compile the multiplication indirection array. + *

+ * This indirection array contains the indices of all pairs of elements + * involved when computing a multiplication. This allows a straightforward + * loop-based multiplication (see {@link #multiply(double[], int, double[], int, double[], int)}). + *

+ * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @param derivativeCompiler compiler for the derivative part + * @param lowerIndirection lower derivatives indirection array + * @return multiplication indirection array + */ + private static int[][][] compileMultiplicationIndirection(final int parameters, final int order, + final DSCompiler valueCompiler, + final DSCompiler derivativeCompiler, + final int[] lowerIndirection) { + + if ((parameters == 0) || (order == 0)) { + return new int[][][] { { { 1, 0, 0 } } }; + } + + // this is an implementation of definition 3 in Dan Kalman's paper. + final int vSize = valueCompiler.multIndirection.length; + final int dSize = derivativeCompiler.multIndirection.length; + final int[][][] multIndirection = new int[vSize + dSize][][]; + + System.arraycopy(valueCompiler.multIndirection, 0, multIndirection, 0, vSize); + + for (int i = 0; i < dSize; ++i) { + final int[][] dRow = derivativeCompiler.multIndirection[i]; + List row = new ArrayList(dRow.length * 2); + for (int j = 0; j < dRow.length; ++j) { + row.add(new int[] { dRow[j][0], lowerIndirection[dRow[j][1]], vSize + dRow[j][2] }); + row.add(new int[] { dRow[j][0], vSize + dRow[j][1], lowerIndirection[dRow[j][2]] }); + } + + // combine terms with similar derivation orders + final List combined = new ArrayList(row.size()); + for (int j = 0; j < row.size(); ++j) { + final int[] termJ = row.get(j); + if (termJ[0] > 0) { + for (int k = j + 1; k < row.size(); ++k) { + final int[] termK = row.get(k); + if (termJ[1] == termK[1] && termJ[2] == termK[2]) { + // combine termJ and termK + termJ[0] += termK[0]; + // make sure we will skip termK later on in the outer loop + termK[0] = 0; + } + } + combined.add(termJ); + } + } + + multIndirection[vSize + i] = combined.toArray(new int[combined.size()][]); + + } + + return multIndirection; + + } + + /** Compile the function composition indirection array. + *

+ * This indirection array contains the indices of all sets of elements + * involved when computing a composition. This allows a straightforward + * loop-based composition (see {@link #compose(double[], int, double[], double[], int)}). + *

+ * @param parameters number of free parameters + * @param order derivation order + * @param valueCompiler compiler for the value part + * @param derivativeCompiler compiler for the derivative part + * @param sizes sizes array + * @param derivativesIndirection derivatives indirection array + * @return multiplication indirection array + * @throws NumberIsTooLargeException if order is too large + */ + private static int[][][] compileCompositionIndirection(final int parameters, final int order, + final DSCompiler valueCompiler, + final DSCompiler derivativeCompiler, + final int[][] sizes, + final int[][] derivativesIndirection) + throws NumberIsTooLargeException { + + if ((parameters == 0) || (order == 0)) { + return new int[][][] { { { 1, 0 } } }; + } + + final int vSize = valueCompiler.compIndirection.length; + final int dSize = derivativeCompiler.compIndirection.length; + final int[][][] compIndirection = new int[vSize + dSize][][]; + + // the composition rules from the value part can be reused as is + System.arraycopy(valueCompiler.compIndirection, 0, compIndirection, 0, vSize); + + // the composition rules for the derivative part are deduced by + // differentiation the rules from the underlying compiler once + // with respect to the parameter this compiler handles and the + // underlying one did not handle + for (int i = 0; i < dSize; ++i) { + List row = new ArrayList(); + for (int[] term : derivativeCompiler.compIndirection[i]) { + + // handle term p * f_k(g(x)) * g_l1(x) * g_l2(x) * ... * g_lp(x) + + // derive the first factor in the term: f_k with respect to new parameter + int[] derivedTermF = new int[term.length + 1]; + derivedTermF[0] = term[0]; // p + derivedTermF[1] = term[1] + 1; // f_(k+1) + int[] orders = new int[parameters]; + orders[parameters - 1] = 1; + derivedTermF[term.length] = getPartialDerivativeIndex(parameters, order, sizes, orders); // g_1 + for (int j = 2; j < term.length; ++j) { + // convert the indices as the mapping for the current order + // is different from the mapping with one less order + derivedTermF[j] = convertIndex(term[j], parameters, + derivativeCompiler.derivativesIndirection, + parameters, order, sizes); + } + Arrays.sort(derivedTermF, 2, derivedTermF.length); + row.add(derivedTermF); + + // derive the various g_l + for (int l = 2; l < term.length; ++l) { + int[] derivedTermG = new int[term.length]; + derivedTermG[0] = term[0]; + derivedTermG[1] = term[1]; + for (int j = 2; j < term.length; ++j) { + // convert the indices as the mapping for the current order + // is different from the mapping with one less order + derivedTermG[j] = convertIndex(term[j], parameters, + derivativeCompiler.derivativesIndirection, + parameters, order, sizes); + if (j == l) { + // derive this term + System.arraycopy(derivativesIndirection[derivedTermG[j]], 0, orders, 0, parameters); + orders[parameters - 1]++; + derivedTermG[j] = getPartialDerivativeIndex(parameters, order, sizes, orders); + } + } + Arrays.sort(derivedTermG, 2, derivedTermG.length); + row.add(derivedTermG); + } + + } + + // combine terms with similar derivation orders + final List combined = new ArrayList(row.size()); + for (int j = 0; j < row.size(); ++j) { + final int[] termJ = row.get(j); + if (termJ[0] > 0) { + for (int k = j + 1; k < row.size(); ++k) { + final int[] termK = row.get(k); + boolean equals = termJ.length == termK.length; + for (int l = 1; equals && l < termJ.length; ++l) { + equals &= termJ[l] == termK[l]; + } + if (equals) { + // combine termJ and termK + termJ[0] += termK[0]; + // make sure we will skip termK later on in the outer loop + termK[0] = 0; + } + } + combined.add(termJ); + } + } + + compIndirection[vSize + i] = combined.toArray(new int[combined.size()][]); + + } + + return compIndirection; + + } + + /** Get the index of a partial derivative in the array. + *

+ * If all orders are set to 0, then the 0th order derivative + * is returned, which is the value of the function. + *

+ *

The indices of derivatives are between 0 and {@link #getSize() getSize()} - 1. + * Their specific order is fixed for a given compiler, but otherwise not + * publicly specified. There are however some simple cases which have guaranteed + * indices: + *

+ *
    + *
  • the index of 0th order derivative is always 0
  • + *
  • if there is only 1 {@link #getFreeParameters() free parameter}, then the + * derivatives are sorted in increasing derivation order (i.e. f at index 0, df/dp + * at index 1, d2f/dp2 at index 2 ... + * dkf/dpk at index k),
  • + *
  • if the {@link #getOrder() derivation order} is 1, then the derivatives + * are sorted in increasing free parameter order (i.e. f at index 0, df/dx1 + * at index 1, df/dx2 at index 2 ... df/dxk at index k),
  • + *
  • all other cases are not publicly specified
  • + *
+ *

+ * This method is the inverse of method {@link #getPartialDerivativeOrders(int)} + *

+ * @param orders derivation orders with respect to each parameter + * @return index of the partial derivative + * @exception DimensionMismatchException if the numbers of parameters does not + * match the instance + * @exception NumberIsTooLargeException if sum of derivation orders is larger + * than the instance limits + * @see #getPartialDerivativeOrders(int) + */ + public int getPartialDerivativeIndex(final int ... orders) + throws DimensionMismatchException, NumberIsTooLargeException { + + // safety check + if (orders.length != getFreeParameters()) { + throw new DimensionMismatchException(orders.length, getFreeParameters()); + } + + return getPartialDerivativeIndex(parameters, order, sizes, orders); + + } + + /** Get the index of a partial derivative in an array. + * @param parameters number of free parameters + * @param order derivation order + * @param sizes sizes array + * @param orders derivation orders with respect to each parameter + * (the lenght of this array must match the number of parameters) + * @return index of the partial derivative + * @exception NumberIsTooLargeException if sum of derivation orders is larger + * than the instance limits + */ + private static int getPartialDerivativeIndex(final int parameters, final int order, + final int[][] sizes, final int ... orders) + throws NumberIsTooLargeException { + + // the value is obtained by diving into the recursive Dan Kalman's structure + // this is theorem 2 of his paper, with recursion replaced by iteration + int index = 0; + int m = order; + int ordersSum = 0; + for (int i = parameters - 1; i >= 0; --i) { + + // derivative order for current free parameter + int derivativeOrder = orders[i]; + + // safety check + ordersSum += derivativeOrder; + if (ordersSum > order) { + throw new NumberIsTooLargeException(ordersSum, order, true); + } + + while (derivativeOrder-- > 0) { + // as long as we differentiate according to current free parameter, + // we have to skip the value part and dive into the derivative part + // so we add the size of the value part to the base index + index += sizes[i][m--]; + } + + } + + return index; + + } + + /** Convert an index from one (parameters, order) structure to another. + * @param index index of a partial derivative in source derivative structure + * @param srcP number of free parameters in source derivative structure + * @param srcDerivativesIndirection derivatives indirection array for the source + * derivative structure + * @param destP number of free parameters in destination derivative structure + * @param destO derivation order in destination derivative structure + * @param destSizes sizes array for the destination derivative structure + * @return index of the partial derivative with the same characteristics + * in destination derivative structure + * @throws NumberIsTooLargeException if order is too large + */ + private static int convertIndex(final int index, + final int srcP, final int[][] srcDerivativesIndirection, + final int destP, final int destO, final int[][] destSizes) + throws NumberIsTooLargeException { + int[] orders = new int[destP]; + System.arraycopy(srcDerivativesIndirection[index], 0, orders, 0, FastMath.min(srcP, destP)); + return getPartialDerivativeIndex(destP, destO, destSizes, orders); + } + + /** Get the derivation orders for a specific index in the array. + *

+ * This method is the inverse of {@link #getPartialDerivativeIndex(int...)}. + *

+ * @param index of the partial derivative + * @return orders derivation orders with respect to each parameter + * @see #getPartialDerivativeIndex(int...) + */ + public int[] getPartialDerivativeOrders(final int index) { + return derivativesIndirection[index]; + } + + /** Get the number of free parameters. + * @return number of free parameters + */ + public int getFreeParameters() { + return parameters; + } + + /** Get the derivation order. + * @return derivation order + */ + public int getOrder() { + return order; + } + + /** Get the array size required for holding partial derivatives data. + *

+ * This number includes the single 0 order derivative element, which is + * guaranteed to be stored in the first element of the array. + *

+ * @return array size required for holding partial derivatives data + */ + public int getSize() { + return sizes[parameters][order]; + } + + /** Compute linear combination. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + * @param a1 first scale factor + * @param c1 first base (unscaled) component + * @param offset1 offset of first operand in its array + * @param a2 second scale factor + * @param c2 second base (unscaled) component + * @param offset2 offset of second operand in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void linearCombination(final double a1, final double[] c1, final int offset1, + final double a2, final double[] c2, final int offset2, + final double[] result, final int resultOffset) { + for (int i = 0; i < getSize(); ++i) { + result[resultOffset + i] = + MathArrays.linearCombination(a1, c1[offset1 + i], a2, c2[offset2 + i]); + } + } + + /** Compute linear combination. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4 + * @param a1 first scale factor + * @param c1 first base (unscaled) component + * @param offset1 offset of first operand in its array + * @param a2 second scale factor + * @param c2 second base (unscaled) component + * @param offset2 offset of second operand in its array + * @param a3 third scale factor + * @param c3 third base (unscaled) component + * @param offset3 offset of third operand in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void linearCombination(final double a1, final double[] c1, final int offset1, + final double a2, final double[] c2, final int offset2, + final double a3, final double[] c3, final int offset3, + final double[] result, final int resultOffset) { + for (int i = 0; i < getSize(); ++i) { + result[resultOffset + i] = + MathArrays.linearCombination(a1, c1[offset1 + i], + a2, c2[offset2 + i], + a3, c3[offset3 + i]); + } + } + + /** Compute linear combination. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4 + * @param a1 first scale factor + * @param c1 first base (unscaled) component + * @param offset1 offset of first operand in its array + * @param a2 second scale factor + * @param c2 second base (unscaled) component + * @param offset2 offset of second operand in its array + * @param a3 third scale factor + * @param c3 third base (unscaled) component + * @param offset3 offset of third operand in its array + * @param a4 fourth scale factor + * @param c4 fourth base (unscaled) component + * @param offset4 offset of fourth operand in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void linearCombination(final double a1, final double[] c1, final int offset1, + final double a2, final double[] c2, final int offset2, + final double a3, final double[] c3, final int offset3, + final double a4, final double[] c4, final int offset4, + final double[] result, final int resultOffset) { + for (int i = 0; i < getSize(); ++i) { + result[resultOffset + i] = + MathArrays.linearCombination(a1, c1[offset1 + i], + a2, c2[offset2 + i], + a3, c3[offset3 + i], + a4, c4[offset4 + i]); + } + } + + /** Perform addition of two derivative structures. + * @param lhs array holding left hand side of addition + * @param lhsOffset offset of the left hand side in its array + * @param rhs array right hand side of addition + * @param rhsOffset offset of the right hand side in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void add(final double[] lhs, final int lhsOffset, + final double[] rhs, final int rhsOffset, + final double[] result, final int resultOffset) { + for (int i = 0; i < getSize(); ++i) { + result[resultOffset + i] = lhs[lhsOffset + i] + rhs[rhsOffset + i]; + } + } + /** Perform subtraction of two derivative structures. + * @param lhs array holding left hand side of subtraction + * @param lhsOffset offset of the left hand side in its array + * @param rhs array right hand side of subtraction + * @param rhsOffset offset of the right hand side in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void subtract(final double[] lhs, final int lhsOffset, + final double[] rhs, final int rhsOffset, + final double[] result, final int resultOffset) { + for (int i = 0; i < getSize(); ++i) { + result[resultOffset + i] = lhs[lhsOffset + i] - rhs[rhsOffset + i]; + } + } + + /** Perform multiplication of two derivative structures. + * @param lhs array holding left hand side of multiplication + * @param lhsOffset offset of the left hand side in its array + * @param rhs array right hand side of multiplication + * @param rhsOffset offset of the right hand side in its array + * @param result array where result must be stored (for + * multiplication the result array cannot be one of + * the input arrays) + * @param resultOffset offset of the result in its array + */ + public void multiply(final double[] lhs, final int lhsOffset, + final double[] rhs, final int rhsOffset, + final double[] result, final int resultOffset) { + for (int i = 0; i < multIndirection.length; ++i) { + final int[][] mappingI = multIndirection[i]; + double r = 0; + for (int j = 0; j < mappingI.length; ++j) { + r += mappingI[j][0] * + lhs[lhsOffset + mappingI[j][1]] * + rhs[rhsOffset + mappingI[j][2]]; + } + result[resultOffset + i] = r; + } + } + + /** Perform division of two derivative structures. + * @param lhs array holding left hand side of division + * @param lhsOffset offset of the left hand side in its array + * @param rhs array right hand side of division + * @param rhsOffset offset of the right hand side in its array + * @param result array where result must be stored (for + * division the result array cannot be one of + * the input arrays) + * @param resultOffset offset of the result in its array + */ + public void divide(final double[] lhs, final int lhsOffset, + final double[] rhs, final int rhsOffset, + final double[] result, final int resultOffset) { + final double[] reciprocal = new double[getSize()]; + pow(rhs, lhsOffset, -1, reciprocal, 0); + multiply(lhs, lhsOffset, reciprocal, 0, result, resultOffset); + } + + /** Perform remainder of two derivative structures. + * @param lhs array holding left hand side of remainder + * @param lhsOffset offset of the left hand side in its array + * @param rhs array right hand side of remainder + * @param rhsOffset offset of the right hand side in its array + * @param result array where result must be stored (it may be + * one of the input arrays) + * @param resultOffset offset of the result in its array + */ + public void remainder(final double[] lhs, final int lhsOffset, + final double[] rhs, final int rhsOffset, + final double[] result, final int resultOffset) { + + // compute k such that lhs % rhs = lhs - k rhs + final double rem = FastMath.IEEEremainder(lhs[lhsOffset], rhs[rhsOffset]); + final double k = FastMath.rint((lhs[lhsOffset] - rem) / rhs[rhsOffset]); + + // set up value + result[resultOffset] = rem; + + // set up partial derivatives + for (int i = 1; i < getSize(); ++i) { + result[resultOffset + i] = lhs[lhsOffset + i] - k * rhs[rhsOffset + i]; + } + + } + + /** Compute power of a double to a derivative structure. + * @param a number to exponentiate + * @param operand array holding the power + * @param operandOffset offset of the power in its array + * @param result array where result must be stored (for + * power the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + * @since 3.3 + */ + public void pow(final double a, + final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ] + final double[] function = new double[1 + order]; + if (a == 0) { + if (operand[operandOffset] == 0) { + function[0] = 1; + double infinity = Double.POSITIVE_INFINITY; + for (int i = 1; i < function.length; ++i) { + infinity = -infinity; + function[i] = infinity; + } + } else if (operand[operandOffset] < 0) { + Arrays.fill(function, Double.NaN); + } + } else { + function[0] = FastMath.pow(a, operand[operandOffset]); + final double lnA = FastMath.log(a); + for (int i = 1; i < function.length; ++i) { + function[i] = lnA * function[i - 1]; + } + } + + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute power of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param p power to apply + * @param result array where result must be stored (for + * power the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void pow(final double[] operand, final int operandOffset, final double p, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + // [x^p, px^(p-1), p(p-1)x^(p-2), ... ] + double[] function = new double[1 + order]; + double xk = FastMath.pow(operand[operandOffset], p - order); + for (int i = order; i > 0; --i) { + function[i] = xk; + xk *= operand[operandOffset]; + } + function[0] = xk; + double coefficient = p; + for (int i = 1; i <= order; ++i) { + function[i] *= coefficient; + coefficient *= p - i; + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute integer power of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param n power to apply + * @param result array where result must be stored (for + * power the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void pow(final double[] operand, final int operandOffset, final int n, + final double[] result, final int resultOffset) { + + if (n == 0) { + // special case, x^0 = 1 for all x + result[resultOffset] = 1.0; + Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0); + return; + } + + // create the power function value and derivatives + // [x^n, nx^(n-1), n(n-1)x^(n-2), ... ] + double[] function = new double[1 + order]; + + if (n > 0) { + // strictly positive power + final int maxOrder = FastMath.min(order, n); + double xk = FastMath.pow(operand[operandOffset], n - maxOrder); + for (int i = maxOrder; i > 0; --i) { + function[i] = xk; + xk *= operand[operandOffset]; + } + function[0] = xk; + } else { + // strictly negative power + final double inv = 1.0 / operand[operandOffset]; + double xk = FastMath.pow(inv, -n); + for (int i = 0; i <= order; ++i) { + function[i] = xk; + xk *= inv; + } + } + + double coefficient = n; + for (int i = 1; i <= order; ++i) { + function[i] *= coefficient; + coefficient *= n - i; + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute power of a derivative structure. + * @param x array holding the base + * @param xOffset offset of the base in its array + * @param y array holding the exponent + * @param yOffset offset of the exponent in its array + * @param result array where result must be stored (for + * power the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void pow(final double[] x, final int xOffset, + final double[] y, final int yOffset, + final double[] result, final int resultOffset) { + final double[] logX = new double[getSize()]; + log(x, xOffset, logX, 0); + final double[] yLogX = new double[getSize()]; + multiply(logX, 0, y, yOffset, yLogX, 0); + exp(yLogX, 0, result, resultOffset); + } + + /** Compute nth root of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param n order of the root + * @param result array where result must be stored (for + * nth root the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void rootN(final double[] operand, final int operandOffset, final int n, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ] + double[] function = new double[1 + order]; + double xk; + if (n == 2) { + function[0] = FastMath.sqrt(operand[operandOffset]); + xk = 0.5 / function[0]; + } else if (n == 3) { + function[0] = FastMath.cbrt(operand[operandOffset]); + xk = 1.0 / (3.0 * function[0] * function[0]); + } else { + function[0] = FastMath.pow(operand[operandOffset], 1.0 / n); + xk = 1.0 / (n * FastMath.pow(function[0], n - 1)); + } + final double nReciprocal = 1.0 / n; + final double xReciprocal = 1.0 / operand[operandOffset]; + for (int i = 1; i <= order; ++i) { + function[i] = xk; + xk *= xReciprocal * (nReciprocal - i); + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute exponential of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * exponential the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void exp(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + Arrays.fill(function, FastMath.exp(operand[operandOffset])); + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute exp(x) - 1 of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * exponential the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void expm1(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.expm1(operand[operandOffset]); + Arrays.fill(function, 1, 1 + order, FastMath.exp(operand[operandOffset])); + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute natural logarithm of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * logarithm the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void log(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.log(operand[operandOffset]); + if (order > 0) { + double inv = 1.0 / operand[operandOffset]; + double xk = inv; + for (int i = 1; i <= order; ++i) { + function[i] = xk; + xk *= -i * inv; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Computes shifted logarithm of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * shifted logarithm the result array cannot be the input array) + * @param resultOffset offset of the result in its array + */ + public void log1p(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.log1p(operand[operandOffset]); + if (order > 0) { + double inv = 1.0 / (1.0 + operand[operandOffset]); + double xk = inv; + for (int i = 1; i <= order; ++i) { + function[i] = xk; + xk *= -i * inv; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Computes base 10 logarithm of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * base 10 logarithm the result array cannot be the input array) + * @param resultOffset offset of the result in its array + */ + public void log10(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.log10(operand[operandOffset]); + if (order > 0) { + double inv = 1.0 / operand[operandOffset]; + double xk = inv / FastMath.log(10.0); + for (int i = 1; i <= order; ++i) { + function[i] = xk; + xk *= -i * inv; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute cosine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * cosine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void cos(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.cos(operand[operandOffset]); + if (order > 0) { + function[1] = -FastMath.sin(operand[operandOffset]); + for (int i = 2; i <= order; ++i) { + function[i] = -function[i - 2]; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute sine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * sine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void sin(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.sin(operand[operandOffset]); + if (order > 0) { + function[1] = FastMath.cos(operand[operandOffset]); + for (int i = 2; i <= order; ++i) { + function[i] = -function[i - 2]; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute tangent of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * tangent the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void tan(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + final double[] function = new double[1 + order]; + final double t = FastMath.tan(operand[operandOffset]); + function[0] = t; + + if (order > 0) { + + // the nth order derivative of tan has the form: + // dn(tan(x)/dxn = P_n(tan(x)) + // where P_n(t) is a degree n+1 polynomial with same parity as n+1 + // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ... + // the general recurrence relation for P_n is: + // P_n(x) = (1+t^2) P_(n-1)'(t) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order + 2]; + p[1] = 1; + final double t2 = t * t; + for (int n = 1; n <= order; ++n) { + + // update and evaluate polynomial P_n(t) + double v = 0; + p[n + 1] = n * p[n]; + for (int k = n + 1; k >= 0; k -= 2) { + v = v * t2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] + (k - 3) * p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 0) { + v *= t; + } + + function[n] = v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute arc cosine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * arc cosine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void acos(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.acos(x); + if (order > 0) { + // the nth order derivative of acos has the form: + // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2) + // where P_n(x) is a degree n-1 polynomial with same parity as n-1 + // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ... + // the general recurrence relation for P_n is: + // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order]; + p[0] = -1; + final double x2 = x * x; + final double f = 1.0 / (1 - x2); + double coeff = FastMath.sqrt(f); + function[1] = coeff * p[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial P_n(x) + double v = 0; + p[n - 1] = (n - 1) * p[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute arc sine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * arc sine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void asin(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.asin(x); + if (order > 0) { + // the nth order derivative of asin has the form: + // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2) + // where P_n(x) is a degree n-1 polynomial with same parity as n-1 + // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ... + // the general recurrence relation for P_n is: + // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order]; + p[0] = 1; + final double x2 = x * x; + final double f = 1.0 / (1 - x2); + double coeff = FastMath.sqrt(f); + function[1] = coeff * p[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial P_n(x) + double v = 0; + p[n - 1] = (n - 1) * p[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute arc tangent of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * arc tangent the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void atan(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.atan(x); + if (order > 0) { + // the nth order derivative of atan has the form: + // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n + // where Q_n(x) is a degree n-1 polynomial with same parity as n-1 + // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ... + // the general recurrence relation for Q_n is: + // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x) + // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array + final double[] q = new double[order]; + q[0] = 1; + final double x2 = x * x; + final double f = 1.0 / (1 + x2); + double coeff = f; + function[1] = coeff * q[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial Q_n(x) + double v = 0; + q[n - 1] = -n * q[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + q[k]; + if (k > 2) { + q[k - 2] = (k - 1) * q[k - 1] + (k - 1 - 2 * n) * q[k - 3]; + } else if (k == 2) { + q[0] = q[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute two arguments arc tangent of a derivative structure. + * @param y array holding the first operand + * @param yOffset offset of the first operand in its array + * @param x array holding the second operand + * @param xOffset offset of the second operand in its array + * @param result array where result must be stored (for + * two arguments arc tangent the result array cannot + * be the input array) + * @param resultOffset offset of the result in its array + */ + public void atan2(final double[] y, final int yOffset, + final double[] x, final int xOffset, + final double[] result, final int resultOffset) { + + // compute r = sqrt(x^2+y^2) + double[] tmp1 = new double[getSize()]; + multiply(x, xOffset, x, xOffset, tmp1, 0); // x^2 + double[] tmp2 = new double[getSize()]; + multiply(y, yOffset, y, yOffset, tmp2, 0); // y^2 + add(tmp1, 0, tmp2, 0, tmp2, 0); // x^2 + y^2 + rootN(tmp2, 0, 2, tmp1, 0); // r = sqrt(x^2 + y^2) + + if (x[xOffset] >= 0) { + + // compute atan2(y, x) = 2 atan(y / (r + x)) + add(tmp1, 0, x, xOffset, tmp2, 0); // r + x + divide(y, yOffset, tmp2, 0, tmp1, 0); // y /(r + x) + atan(tmp1, 0, tmp2, 0); // atan(y / (r + x)) + for (int i = 0; i < tmp2.length; ++i) { + result[resultOffset + i] = 2 * tmp2[i]; // 2 * atan(y / (r + x)) + } + + } else { + + // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x)) + subtract(tmp1, 0, x, xOffset, tmp2, 0); // r - x + divide(y, yOffset, tmp2, 0, tmp1, 0); // y /(r - x) + atan(tmp1, 0, tmp2, 0); // atan(y / (r - x)) + result[resultOffset] = + ((tmp2[0] <= 0) ? -FastMath.PI : FastMath.PI) - 2 * tmp2[0]; // +/-pi - 2 * atan(y / (r - x)) + for (int i = 1; i < tmp2.length; ++i) { + result[resultOffset + i] = -2 * tmp2[i]; // +/-pi - 2 * atan(y / (r - x)) + } + + } + + // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly + result[resultOffset] = FastMath.atan2(y[yOffset], x[xOffset]); + + } + + /** Compute hyperbolic cosine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * hyperbolic cosine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void cosh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.cosh(operand[operandOffset]); + if (order > 0) { + function[1] = FastMath.sinh(operand[operandOffset]); + for (int i = 2; i <= order; ++i) { + function[i] = function[i - 2]; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute hyperbolic sine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * hyperbolic sine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void sinh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + function[0] = FastMath.sinh(operand[operandOffset]); + if (order > 0) { + function[1] = FastMath.cosh(operand[operandOffset]); + for (int i = 2; i <= order; ++i) { + function[i] = function[i - 2]; + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute hyperbolic tangent of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * hyperbolic tangent the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void tanh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + final double[] function = new double[1 + order]; + final double t = FastMath.tanh(operand[operandOffset]); + function[0] = t; + + if (order > 0) { + + // the nth order derivative of tanh has the form: + // dn(tanh(x)/dxn = P_n(tanh(x)) + // where P_n(t) is a degree n+1 polynomial with same parity as n+1 + // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ... + // the general recurrence relation for P_n is: + // P_n(x) = (1-t^2) P_(n-1)'(t) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order + 2]; + p[1] = 1; + final double t2 = t * t; + for (int n = 1; n <= order; ++n) { + + // update and evaluate polynomial P_n(t) + double v = 0; + p[n + 1] = -n * p[n]; + for (int k = n + 1; k >= 0; k -= 2) { + v = v * t2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] - (k - 3) * p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 0) { + v *= t; + } + + function[n] = v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute inverse hyperbolic cosine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * inverse hyperbolic cosine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void acosh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.acosh(x); + if (order > 0) { + // the nth order derivative of acosh has the form: + // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2) + // where P_n(x) is a degree n-1 polynomial with same parity as n-1 + // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ... + // the general recurrence relation for P_n is: + // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order]; + p[0] = 1; + final double x2 = x * x; + final double f = 1.0 / (x2 - 1); + double coeff = FastMath.sqrt(f); + function[1] = coeff * p[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial P_n(x) + double v = 0; + p[n - 1] = (1 - n) * p[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + p[k]; + if (k > 2) { + p[k - 2] = (1 - k) * p[k - 1] + (k - 2 * n) * p[k - 3]; + } else if (k == 2) { + p[0] = -p[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute inverse hyperbolic sine of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * inverse hyperbolic sine the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void asinh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.asinh(x); + if (order > 0) { + // the nth order derivative of asinh has the form: + // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2) + // where P_n(x) is a degree n-1 polynomial with same parity as n-1 + // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ... + // the general recurrence relation for P_n is: + // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[order]; + p[0] = 1; + final double x2 = x * x; + final double f = 1.0 / (1 + x2); + double coeff = FastMath.sqrt(f); + function[1] = coeff * p[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial P_n(x) + double v = 0; + p[n - 1] = (1 - n) * p[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] + (k - 2 * n) * p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute inverse hyperbolic tangent of a derivative structure. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param result array where result must be stored (for + * inverse hyperbolic tangent the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void atanh(final double[] operand, final int operandOffset, + final double[] result, final int resultOffset) { + + // create the function value and derivatives + double[] function = new double[1 + order]; + final double x = operand[operandOffset]; + function[0] = FastMath.atanh(x); + if (order > 0) { + // the nth order derivative of atanh has the form: + // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n + // where Q_n(x) is a degree n-1 polynomial with same parity as n-1 + // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ... + // the general recurrence relation for Q_n is: + // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x) + // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array + final double[] q = new double[order]; + q[0] = 1; + final double x2 = x * x; + final double f = 1.0 / (1 - x2); + double coeff = f; + function[1] = coeff * q[0]; + for (int n = 2; n <= order; ++n) { + + // update and evaluate polynomial Q_n(x) + double v = 0; + q[n - 1] = n * q[n - 2]; + for (int k = n - 1; k >= 0; k -= 2) { + v = v * x2 + q[k]; + if (k > 2) { + q[k - 2] = (k - 1) * q[k - 1] + (2 * n - k + 1) * q[k - 3]; + } else if (k == 2) { + q[0] = q[1]; + } + } + if ((n & 0x1) == 0) { + v *= x; + } + + coeff *= f; + function[n] = coeff * v; + + } + } + + // apply function composition + compose(operand, operandOffset, function, result, resultOffset); + + } + + /** Compute composition of a derivative structure by a function. + * @param operand array holding the operand + * @param operandOffset offset of the operand in its array + * @param f array of value and derivatives of the function at + * the current point (i.e. at {@code operand[operandOffset]}). + * @param result array where result must be stored (for + * composition the result array cannot be the input + * array) + * @param resultOffset offset of the result in its array + */ + public void compose(final double[] operand, final int operandOffset, final double[] f, + final double[] result, final int resultOffset) { + for (int i = 0; i < compIndirection.length; ++i) { + final int[][] mappingI = compIndirection[i]; + double r = 0; + for (int j = 0; j < mappingI.length; ++j) { + final int[] mappingIJ = mappingI[j]; + double product = mappingIJ[0] * f[mappingIJ[1]]; + for (int k = 2; k < mappingIJ.length; ++k) { + product *= operand[operandOffset + mappingIJ[k]]; + } + r += product; + } + result[resultOffset + i] = r; + } + } + + /** Evaluate Taylor expansion of a derivative structure. + * @param ds array holding the derivative structure + * @param dsOffset offset of the derivative structure in its array + * @param delta parameters offsets (Δx, Δy, ...) + * @return value of the Taylor expansion at x + Δx, y + Δy, ... + * @throws MathArithmeticException if factorials becomes too large + */ + public double taylor(final double[] ds, final int dsOffset, final double ... delta) + throws MathArithmeticException { + double value = 0; + for (int i = getSize() - 1; i >= 0; --i) { + final int[] orders = getPartialDerivativeOrders(i); + double term = ds[dsOffset + i]; + for (int k = 0; k < orders.length; ++k) { + if (orders[k] > 0) { + try { + term *= FastMath.pow(delta[k], orders[k]) / + CombinatoricsUtils.factorial(orders[k]); + } catch (NotPositiveException e) { + // this cannot happen + throw new MathInternalError(e); + } + } + } + value += term; + } + return value; + } + + /** Check rules set compatibility. + * @param compiler other compiler to check against instance + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + public void checkCompatibility(final DSCompiler compiler) + throws DimensionMismatchException { + if (parameters != compiler.parameters) { + throw new DimensionMismatchException(parameters, compiler.parameters); + } + if (order != compiler.order) { + throw new DimensionMismatchException(order, compiler.order); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java new file mode 100644 index 000000000..51c6a6ca3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java @@ -0,0 +1,1195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** Class representing both the value and the differentials of a function. + *

This class is the workhorse of the differentiation package.

+ *

This class is an implementation of the extension to Rall's + * numbers described in Dan Kalman's paper Doubly + * Recursive Multivariate Automatic Differentiation, Mathematics Magazine, vol. 75, + * no. 3, June 2002. Rall's numbers are an extension to the real numbers used + * throughout mathematical expressions; they hold the derivative together with the + * value of a function. Dan Kalman's derivative structures hold all partial derivatives + * up to any specified order, with respect to any number of free parameters. Rall's + * numbers therefore can be seen as derivative structures for order one derivative and + * one free parameter, and real numbers can be seen as derivative structures with zero + * order derivative and no free parameters.

+ *

{@link DerivativeStructure} instances can be used directly thanks to + * the arithmetic operators to the mathematical functions provided as + * methods by this class (+, -, *, /, %, sin, cos ...).

+ *

Implementing complex expressions by hand using these classes is + * a tedious and error-prone task but has the advantage of having no limitation + * on the derivation order despite no requiring users to compute the derivatives by + * themselves. Implementing complex expression can also be done by developing computation + * code using standard primitive double values and to use {@link + * UnivariateFunctionDifferentiator differentiators} to create the {@link + * DerivativeStructure}-based instances. This method is simpler but may be limited in + * the accuracy and derivation orders and may be computationally intensive (this is + * typically the case for {@link FiniteDifferencesDifferentiator finite differences + * differentiator}.

+ *

Instances of this class are guaranteed to be immutable.

+ * @see DSCompiler + * @since 3.1 + */ +public class DerivativeStructure implements RealFieldElement, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120730L; + + /** Compiler for the current dimensions. */ + private transient DSCompiler compiler; + + /** Combined array holding all values. */ + private final double[] data; + + /** Build an instance with all values and derivatives set to 0. + * @param compiler compiler to use for computation + */ + private DerivativeStructure(final DSCompiler compiler) { + this.compiler = compiler; + this.data = new double[compiler.getSize()]; + } + + /** Build an instance with all values and derivatives set to 0. + * @param parameters number of free parameters + * @param order derivation order + * @throws NumberIsTooLargeException if order is too large + */ + public DerivativeStructure(final int parameters, final int order) + throws NumberIsTooLargeException { + this(DSCompiler.getCompiler(parameters, order)); + } + + /** Build an instance representing a constant value. + * @param parameters number of free parameters + * @param order derivation order + * @param value value of the constant + * @throws NumberIsTooLargeException if order is too large + * @see #DerivativeStructure(int, int, int, double) + */ + public DerivativeStructure(final int parameters, final int order, final double value) + throws NumberIsTooLargeException { + this(parameters, order); + this.data[0] = value; + } + + /** Build an instance representing a variable. + *

Instances built using this constructor are considered + * to be the free variables with respect to which differentials + * are computed. As such, their differential with respect to + * themselves is +1.

+ * @param parameters number of free parameters + * @param order derivation order + * @param index index of the variable (from 0 to {@code parameters - 1}) + * @param value value of the variable + * @exception NumberIsTooLargeException if {@code index >= parameters}. + * @see #DerivativeStructure(int, int, double) + */ + public DerivativeStructure(final int parameters, final int order, + final int index, final double value) + throws NumberIsTooLargeException { + this(parameters, order, value); + + if (index >= parameters) { + throw new NumberIsTooLargeException(index, parameters, false); + } + + if (order > 0) { + // the derivative of the variable with respect to itself is 1. + data[DSCompiler.getCompiler(index, order).getSize()] = 1.0; + } + + } + + /** Linear combination constructor. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + * @param a1 first scale factor + * @param ds1 first base (unscaled) derivative structure + * @param a2 second scale factor + * @param ds2 second base (unscaled) derivative structure + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + public DerivativeStructure(final double a1, final DerivativeStructure ds1, + final double a2, final DerivativeStructure ds2) + throws DimensionMismatchException { + this(ds1.compiler); + compiler.checkCompatibility(ds2.compiler); + compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0, data, 0); + } + + /** Linear combination constructor. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + * @param a1 first scale factor + * @param ds1 first base (unscaled) derivative structure + * @param a2 second scale factor + * @param ds2 second base (unscaled) derivative structure + * @param a3 third scale factor + * @param ds3 third base (unscaled) derivative structure + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + public DerivativeStructure(final double a1, final DerivativeStructure ds1, + final double a2, final DerivativeStructure ds2, + final double a3, final DerivativeStructure ds3) + throws DimensionMismatchException { + this(ds1.compiler); + compiler.checkCompatibility(ds2.compiler); + compiler.checkCompatibility(ds3.compiler); + compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0, a3, ds3.data, 0, data, 0); + } + + /** Linear combination constructor. + * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4 + * @param a1 first scale factor + * @param ds1 first base (unscaled) derivative structure + * @param a2 second scale factor + * @param ds2 second base (unscaled) derivative structure + * @param a3 third scale factor + * @param ds3 third base (unscaled) derivative structure + * @param a4 fourth scale factor + * @param ds4 fourth base (unscaled) derivative structure + * @exception DimensionMismatchException if number of free parameters or orders are inconsistent + */ + public DerivativeStructure(final double a1, final DerivativeStructure ds1, + final double a2, final DerivativeStructure ds2, + final double a3, final DerivativeStructure ds3, + final double a4, final DerivativeStructure ds4) + throws DimensionMismatchException { + this(ds1.compiler); + compiler.checkCompatibility(ds2.compiler); + compiler.checkCompatibility(ds3.compiler); + compiler.checkCompatibility(ds4.compiler); + compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0, + a3, ds3.data, 0, a4, ds4.data, 0, + data, 0); + } + + /** Build an instance from all its derivatives. + * @param parameters number of free parameters + * @param order derivation order + * @param derivatives derivatives sorted according to + * {@link DSCompiler#getPartialDerivativeIndex(int...)} + * @exception DimensionMismatchException if derivatives array does not match the + * {@link DSCompiler#getSize() size} expected by the compiler + * @throws NumberIsTooLargeException if order is too large + * @see #getAllDerivatives() + */ + public DerivativeStructure(final int parameters, final int order, final double ... derivatives) + throws DimensionMismatchException, NumberIsTooLargeException { + this(parameters, order); + if (derivatives.length != data.length) { + throw new DimensionMismatchException(derivatives.length, data.length); + } + System.arraycopy(derivatives, 0, data, 0, data.length); + } + + /** Copy constructor. + * @param ds instance to copy + */ + private DerivativeStructure(final DerivativeStructure ds) { + this.compiler = ds.compiler; + this.data = ds.data.clone(); + } + + /** Get the number of free parameters. + * @return number of free parameters + */ + public int getFreeParameters() { + return compiler.getFreeParameters(); + } + + /** Get the derivation order. + * @return derivation order + */ + public int getOrder() { + return compiler.getOrder(); + } + + /** Create a constant compatible with instance order and number of parameters. + *

+ * This method is a convenience factory method, it simply calls + * {@code new DerivativeStructure(getFreeParameters(), getOrder(), c)} + *

+ * @param c value of the constant + * @return a constant compatible with instance order and number of parameters + * @see #DerivativeStructure(int, int, double) + * @since 3.3 + */ + public DerivativeStructure createConstant(final double c) { + return new DerivativeStructure(getFreeParameters(), getOrder(), c); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public double getReal() { + return data[0]; + } + + /** Get the value part of the derivative structure. + * @return value part of the derivative structure + * @see #getPartialDerivative(int...) + */ + public double getValue() { + return data[0]; + } + + /** Get a partial derivative. + * @param orders derivation orders with respect to each variable (if all orders are 0, + * the value is returned) + * @return partial derivative + * @see #getValue() + * @exception DimensionMismatchException if the numbers of variables does not + * match the instance + * @exception NumberIsTooLargeException if sum of derivation orders is larger + * than the instance limits + */ + public double getPartialDerivative(final int ... orders) + throws DimensionMismatchException, NumberIsTooLargeException { + return data[compiler.getPartialDerivativeIndex(orders)]; + } + + /** Get all partial derivatives. + * @return a fresh copy of partial derivatives, in an array sorted according to + * {@link DSCompiler#getPartialDerivativeIndex(int...)} + */ + public double[] getAllDerivatives() { + return data.clone(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure add(final double a) { + final DerivativeStructure ds = new DerivativeStructure(this); + ds.data[0] += a; + return ds; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + */ + public DerivativeStructure add(final DerivativeStructure a) + throws DimensionMismatchException { + compiler.checkCompatibility(a.compiler); + final DerivativeStructure ds = new DerivativeStructure(this); + compiler.add(data, 0, a.data, 0, ds.data, 0); + return ds; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure subtract(final double a) { + return add(-a); + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + */ + public DerivativeStructure subtract(final DerivativeStructure a) + throws DimensionMismatchException { + compiler.checkCompatibility(a.compiler); + final DerivativeStructure ds = new DerivativeStructure(this); + compiler.subtract(data, 0, a.data, 0, ds.data, 0); + return ds; + } + + /** {@inheritDoc} */ + public DerivativeStructure multiply(final int n) { + return multiply((double) n); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure multiply(final double a) { + final DerivativeStructure ds = new DerivativeStructure(this); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] *= a; + } + return ds; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + */ + public DerivativeStructure multiply(final DerivativeStructure a) + throws DimensionMismatchException { + compiler.checkCompatibility(a.compiler); + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.multiply(data, 0, a.data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure divide(final double a) { + final DerivativeStructure ds = new DerivativeStructure(this); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] /= a; + } + return ds; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + */ + public DerivativeStructure divide(final DerivativeStructure a) + throws DimensionMismatchException { + compiler.checkCompatibility(a.compiler); + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.divide(data, 0, a.data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} */ + public DerivativeStructure remainder(final double a) { + final DerivativeStructure ds = new DerivativeStructure(this); + ds.data[0] = FastMath.IEEEremainder(ds.data[0], a); + return ds; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure remainder(final DerivativeStructure a) + throws DimensionMismatchException { + compiler.checkCompatibility(a.compiler); + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.remainder(data, 0, a.data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} */ + public DerivativeStructure negate() { + final DerivativeStructure ds = new DerivativeStructure(compiler); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] = -data[i]; + } + return ds; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure abs() { + if (Double.doubleToLongBits(data[0]) < 0) { + // we use the bits representation to also handle -0.0 + return negate(); + } else { + return this; + } + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure ceil() { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getOrder(), + FastMath.ceil(data[0])); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure floor() { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getOrder(), + FastMath.floor(data[0])); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure rint() { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getOrder(), + FastMath.rint(data[0])); + } + + /** {@inheritDoc} */ + public long round() { + return FastMath.round(data[0]); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure signum() { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getOrder(), + FastMath.signum(data[0])); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure copySign(final DerivativeStructure sign){ + long m = Double.doubleToLongBits(data[0]); + long s = Double.doubleToLongBits(sign.data[0]); + if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure copySign(final double sign) { + long m = Double.doubleToLongBits(data[0]); + long s = Double.doubleToLongBits(sign); + if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** + * Return the exponent of the instance value, removing the bias. + *

+ * For double numbers of the form 2x, the unbiased + * exponent is exactly x. + *

+ * @return exponent for instance in IEEE754 representation, without bias + */ + public int getExponent() { + return FastMath.getExponent(data[0]); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure scalb(final int n) { + final DerivativeStructure ds = new DerivativeStructure(compiler); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] = FastMath.scalb(data[i], n); + } + return ds; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure hypot(final DerivativeStructure y) + throws DimensionMismatchException { + + compiler.checkCompatibility(y.compiler); + + if (Double.isInfinite(data[0]) || Double.isInfinite(y.data[0])) { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getFreeParameters(), + Double.POSITIVE_INFINITY); + } else if (Double.isNaN(data[0]) || Double.isNaN(y.data[0])) { + return new DerivativeStructure(compiler.getFreeParameters(), + compiler.getFreeParameters(), + Double.NaN); + } else { + + final int expX = getExponent(); + final int expY = y.getExponent(); + if (expX > expY + 27) { + // y is neglectible with respect to x + return abs(); + } else if (expY > expX + 27) { + // x is neglectible with respect to y + return y.abs(); + } else { + + // find an intermediate scale to avoid both overflow and underflow + final int middleExp = (expX + expY) / 2; + + // scale parameters without losing precision + final DerivativeStructure scaledX = scalb(-middleExp); + final DerivativeStructure scaledY = y.scalb(-middleExp); + + // compute scaled hypotenuse + final DerivativeStructure scaledH = + scaledX.multiply(scaledX).add(scaledY.multiply(scaledY)).sqrt(); + + // remove scaling + return scaledH.scalb(middleExp); + + } + + } + } + + /** + * Returns the hypotenuse of a triangle with sides {@code x} and {@code y} + * - sqrt(x2 +y2) + * avoiding intermediate overflow or underflow. + * + *
    + *
  • If either argument is infinite, then the result is positive infinity.
  • + *
  • else, if either argument is NaN then the result is NaN.
  • + *
+ * + * @param x a value + * @param y a value + * @return sqrt(x2 +y2) + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public static DerivativeStructure hypot(final DerivativeStructure x, final DerivativeStructure y) + throws DimensionMismatchException { + return x.hypot(y); + } + + /** Compute composition of the instance by a univariate function. + * @param f array of value and derivatives of the function at + * the current point (i.e. [f({@link #getValue()}), + * f'({@link #getValue()}), f''({@link #getValue()})...]). + * @return f(this) + * @exception DimensionMismatchException if the number of derivatives + * in the array is not equal to {@link #getOrder() order} + 1 + */ + public DerivativeStructure compose(final double ... f) + throws DimensionMismatchException { + if (f.length != getOrder() + 1) { + throw new DimensionMismatchException(f.length, getOrder() + 1); + } + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.compose(data, 0, f, result.data, 0); + return result; + } + + /** {@inheritDoc} */ + public DerivativeStructure reciprocal() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.pow(data, 0, -1, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure sqrt() { + return rootN(2); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure cbrt() { + return rootN(3); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure rootN(final int n) { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.rootN(data, 0, n, result.data, 0); + return result; + } + + /** {@inheritDoc} */ + public Field getField() { + return new Field() { + + /** {@inheritDoc} */ + public DerivativeStructure getZero() { + return new DerivativeStructure(compiler.getFreeParameters(), compiler.getOrder(), 0.0); + } + + /** {@inheritDoc} */ + public DerivativeStructure getOne() { + return new DerivativeStructure(compiler.getFreeParameters(), compiler.getOrder(), 1.0); + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return DerivativeStructure.class; + } + + }; + } + + /** Compute ax where a is a double and x a {@link DerivativeStructure} + * @param a number to exponentiate + * @param x power to apply + * @return ax + * @since 3.3 + */ + public static DerivativeStructure pow(final double a, final DerivativeStructure x) { + final DerivativeStructure result = new DerivativeStructure(x.compiler); + x.compiler.pow(a, x.data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure pow(final double p) { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.pow(data, 0, p, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure pow(final int n) { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.pow(data, 0, n, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure pow(final DerivativeStructure e) + throws DimensionMismatchException { + compiler.checkCompatibility(e.compiler); + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.pow(data, 0, e.data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure exp() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.exp(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure expm1() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.expm1(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure log() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.log(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure log1p() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.log1p(data, 0, result.data, 0); + return result; + } + + /** Base 10 logarithm. + * @return base 10 logarithm of the instance + */ + public DerivativeStructure log10() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.log10(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure cos() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.cos(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure sin() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.sin(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure tan() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.tan(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure acos() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.acos(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure asin() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.asin(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure atan() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.atan(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure atan2(final DerivativeStructure x) + throws DimensionMismatchException { + compiler.checkCompatibility(x.compiler); + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.atan2(data, 0, x.data, 0, result.data, 0); + return result; + } + + /** Two arguments arc tangent operation. + * @param y first argument of the arc tangent + * @param x second argument of the arc tangent + * @return atan2(y, x) + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public static DerivativeStructure atan2(final DerivativeStructure y, final DerivativeStructure x) + throws DimensionMismatchException { + return y.atan2(x); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure cosh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.cosh(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure sinh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.sinh(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure tanh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.tanh(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure acosh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.acosh(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure asinh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.asinh(data, 0, result.data, 0); + return result; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public DerivativeStructure atanh() { + final DerivativeStructure result = new DerivativeStructure(compiler); + compiler.atanh(data, 0, result.data, 0); + return result; + } + + /** Convert radians to degrees, with error of less than 0.5 ULP + * @return instance converted into degrees + */ + public DerivativeStructure toDegrees() { + final DerivativeStructure ds = new DerivativeStructure(compiler); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] = FastMath.toDegrees(data[i]); + } + return ds; + } + + /** Convert degrees to radians, with error of less than 0.5 ULP + * @return instance converted into radians + */ + public DerivativeStructure toRadians() { + final DerivativeStructure ds = new DerivativeStructure(compiler); + for (int i = 0; i < ds.data.length; ++i) { + ds.data[i] = FastMath.toRadians(data[i]); + } + return ds; + } + + /** Evaluate Taylor expansion a derivative structure. + * @param delta parameters offsets (Δx, Δy, ...) + * @return value of the Taylor expansion at x + Δx, y + Δy, ... + * @throws MathArithmeticException if factorials becomes too large + */ + public double taylor(final double ... delta) throws MathArithmeticException { + return compiler.taylor(data, 0, delta); + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final DerivativeStructure[] a, final DerivativeStructure[] b) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double[] aDouble = new double[a.length]; + for (int i = 0; i < a.length; ++i) { + aDouble[i] = a[i].getValue(); + } + final double[] bDouble = new double[b.length]; + for (int i = 0; i < b.length; ++i) { + bDouble[i] = b[i].getValue(); + } + final double accurateValue = MathArrays.linearCombination(aDouble, bDouble); + + // compute a simple value, with all partial derivatives + DerivativeStructure simpleValue = a[0].getField().getZero(); + for (int i = 0; i < a.length; ++i) { + simpleValue = simpleValue.add(a[i].multiply(b[i])); + } + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(simpleValue.getFreeParameters(), simpleValue.getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final double[] a, final DerivativeStructure[] b) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double[] bDouble = new double[b.length]; + for (int i = 0; i < b.length; ++i) { + bDouble[i] = b[i].getValue(); + } + final double accurateValue = MathArrays.linearCombination(a, bDouble); + + // compute a simple value, with all partial derivatives + DerivativeStructure simpleValue = b[0].getField().getZero(); + for (int i = 0; i < a.length; ++i) { + simpleValue = simpleValue.add(b[i].multiply(a[i])); + } + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(simpleValue.getFreeParameters(), simpleValue.getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1, + final DerivativeStructure a2, final DerivativeStructure b2) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(), + a2.getValue(), b2.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1, + final double a2, final DerivativeStructure b2) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(), + a2, b2.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1, + final DerivativeStructure a2, final DerivativeStructure b2, + final DerivativeStructure a3, final DerivativeStructure b3) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(), + a2.getValue(), b2.getValue(), + a3.getValue(), b3.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1, + final double a2, final DerivativeStructure b2, + final double a3, final DerivativeStructure b3) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(), + a2, b2.getValue(), + a3, b3.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1, + final DerivativeStructure a2, final DerivativeStructure b2, + final DerivativeStructure a3, final DerivativeStructure b3, + final DerivativeStructure a4, final DerivativeStructure b4) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(), + a2.getValue(), b2.getValue(), + a3.getValue(), b3.getValue(), + a4.getValue(), b4.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** {@inheritDoc} + * @exception DimensionMismatchException if number of free parameters + * or orders do not match + * @since 3.2 + */ + public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1, + final double a2, final DerivativeStructure b2, + final double a3, final DerivativeStructure b3, + final double a4, final DerivativeStructure b4) + throws DimensionMismatchException { + + // compute an accurate value, taking care of cancellations + final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(), + a2, b2.getValue(), + a3, b3.getValue(), + a4, b4.getValue()); + + // compute a simple value, with all partial derivatives + final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4)); + + // create a result with accurate value and all derivatives (not necessarily as accurate as the value) + final double[] all = simpleValue.getAllDerivatives(); + all[0] = accurateValue; + return new DerivativeStructure(getFreeParameters(), getOrder(), all); + + } + + /** + * Test for the equality of two derivative structures. + *

+ * Derivative structures are considered equal if they have the same number + * of free parameters, the same derivation order, and the same derivatives. + *

+ * @param other Object to test for equality to this + * @return true if two derivative structures are equal + * @since 3.2 + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof DerivativeStructure) { + final DerivativeStructure rhs = (DerivativeStructure)other; + return (getFreeParameters() == rhs.getFreeParameters()) && + (getOrder() == rhs.getOrder()) && + MathArrays.equals(data, rhs.data); + } + + return false; + + } + + /** + * Get a hashCode for the derivative structure. + * @return a hash code value for this object + * @since 3.2 + */ + @Override + public int hashCode() { + return 227 + 229 * getFreeParameters() + 233 * getOrder() + 239 * MathUtils.hash(data); + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(compiler.getFreeParameters(), compiler.getOrder(), data); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120730L; + + /** Number of variables. + * @serial + */ + private final int variables; + + /** Derivation order. + * @serial + */ + private final int order; + + /** Partial derivatives. + * @serial + */ + private final double[] data; + + /** Simple constructor. + * @param variables number of variables + * @param order derivation order + * @param data partial derivatives + */ + DataTransferObject(final int variables, final int order, final double[] data) { + this.variables = variables; + this.order = order; + this.data = data; + } + + /** Replace the deserialized data transfer object with a {@link DerivativeStructure}. + * @return replacement {@link DerivativeStructure} + */ + private Object readResolve() { + return new DerivativeStructure(variables, order, data); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java new file mode 100644 index 000000000..02f3afe00 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java @@ -0,0 +1,384 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Univariate functions differentiator using finite differences. + *

+ * This class creates some wrapper objects around regular + * {@link UnivariateFunction univariate functions} (or {@link + * UnivariateVectorFunction univariate vector functions} or {@link + * UnivariateMatrixFunction univariate matrix functions}). These + * wrapper objects compute derivatives in addition to function + * values. + *

+ *

+ * The wrapper objects work by calling the underlying function on + * a sampling grid around the current point and performing polynomial + * interpolation. A finite differences scheme with n points is + * theoretically able to compute derivatives up to order n-1, but + * it is generally better to have a slight margin. The step size must + * also be small enough in order for the polynomial approximation to + * be good in the current point neighborhood, but it should not be too + * small because numerical instability appears quickly (there are several + * differences of close points). Choosing the number of points and + * the step size is highly problem dependent. + *

+ *

+ * As an example of good and bad settings, lets consider the quintic + * polynomial function {@code f(x) = (x-1)*(x-0.5)*x*(x+0.5)*(x+1)}. + * Since it is a polynomial, finite differences with at least 6 points + * should theoretically recover the exact same polynomial and hence + * compute accurate derivatives for any order. However, due to numerical + * errors, we get the following results for a 7 points finite differences + * for abscissae in the [-10, 10] range: + *

    + *
  • step size = 0.25, second order derivative error about 9.97e-10
  • + *
  • step size = 0.25, fourth order derivative error about 5.43e-8
  • + *
  • step size = 1.0e-6, second order derivative error about 148
  • + *
  • step size = 1.0e-6, fourth order derivative error about 6.35e+14
  • + *
+ *

+ * This example shows that the small step size is really bad, even simply + * for second order derivative!

+ * + * @since 3.1 + */ +public class FiniteDifferencesDifferentiator + implements UnivariateFunctionDifferentiator, UnivariateVectorFunctionDifferentiator, + UnivariateMatrixFunctionDifferentiator, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120917L; + + /** Number of points to use. */ + private final int nbPoints; + + /** Step size. */ + private final double stepSize; + + /** Half sample span. */ + private final double halfSampleSpan; + + /** Lower bound for independent variable. */ + private final double tMin; + + /** Upper bound for independent variable. */ + private final double tMax; + + /** + * Build a differentiator with number of points and step size when independent variable is unbounded. + *

+ * Beware that wrong settings for the finite differences differentiator + * can lead to highly unstable and inaccurate results, especially for + * high derivation orders. Using very small step sizes is often a + * bad idea. + *

+ * @param nbPoints number of points to use + * @param stepSize step size (gap between each point) + * @exception NotPositiveException if {@code stepsize <= 0} (note that + * {@link NotPositiveException} extends {@link NumberIsTooSmallException}) + * @exception NumberIsTooSmallException {@code nbPoint <= 1} + */ + public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize) + throws NotPositiveException, NumberIsTooSmallException { + this(nbPoints, stepSize, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + } + + /** + * Build a differentiator with number of points and step size when independent variable is bounded. + *

+ * When the independent variable is bounded (tLower < t < tUpper), the sampling + * points used for differentiation will be adapted to ensure the constraint holds + * even near the boundaries. This means the sample will not be centered anymore in + * these cases. At an extreme case, computing derivatives exactly at the lower bound + * will lead the sample to be entirely on the right side of the derivation point. + *

+ *

+ * Note that the boundaries are considered to be excluded for function evaluation. + *

+ *

+ * Beware that wrong settings for the finite differences differentiator + * can lead to highly unstable and inaccurate results, especially for + * high derivation orders. Using very small step sizes is often a + * bad idea. + *

+ * @param nbPoints number of points to use + * @param stepSize step size (gap between each point) + * @param tLower lower bound for independent variable (may be {@code Double.NEGATIVE_INFINITY} + * if there are no lower bounds) + * @param tUpper upper bound for independent variable (may be {@code Double.POSITIVE_INFINITY} + * if there are no upper bounds) + * @exception NotPositiveException if {@code stepsize <= 0} (note that + * {@link NotPositiveException} extends {@link NumberIsTooSmallException}) + * @exception NumberIsTooSmallException {@code nbPoint <= 1} + * @exception NumberIsTooLargeException {@code stepSize * (nbPoints - 1) >= tUpper - tLower} + */ + public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize, + final double tLower, final double tUpper) + throws NotPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + + if (nbPoints <= 1) { + throw new NumberIsTooSmallException(stepSize, 1, false); + } + this.nbPoints = nbPoints; + + if (stepSize <= 0) { + throw new NotPositiveException(stepSize); + } + this.stepSize = stepSize; + + halfSampleSpan = 0.5 * stepSize * (nbPoints - 1); + if (2 * halfSampleSpan >= tUpper - tLower) { + throw new NumberIsTooLargeException(2 * halfSampleSpan, tUpper - tLower, false); + } + final double safety = FastMath.ulp(halfSampleSpan); + this.tMin = tLower + halfSampleSpan + safety; + this.tMax = tUpper - halfSampleSpan - safety; + + } + + /** + * Get the number of points to use. + * @return number of points to use + */ + public int getNbPoints() { + return nbPoints; + } + + /** + * Get the step size. + * @return step size + */ + public double getStepSize() { + return stepSize; + } + + /** + * Evaluate derivatives from a sample. + *

+ * Evaluation is done using divided differences. + *

+ * @param t evaluation abscissa value and derivatives + * @param t0 first sample point abscissa + * @param y function values sample {@code y[i] = f(t[i]) = f(t0 + i * stepSize)} + * @return value and derivatives at {@code t} + * @exception NumberIsTooLargeException if the requested derivation order + * is larger or equal to the number of points + */ + private DerivativeStructure evaluate(final DerivativeStructure t, final double t0, + final double[] y) + throws NumberIsTooLargeException { + + // create divided differences diagonal arrays + final double[] top = new double[nbPoints]; + final double[] bottom = new double[nbPoints]; + + for (int i = 0; i < nbPoints; ++i) { + + // update the bottom diagonal of the divided differences array + bottom[i] = y[i]; + for (int j = 1; j <= i; ++j) { + bottom[i - j] = (bottom[i - j + 1] - bottom[i - j]) / (j * stepSize); + } + + // update the top diagonal of the divided differences array + top[i] = bottom[0]; + + } + + // evaluate interpolation polynomial (represented by top diagonal) at t + final int order = t.getOrder(); + final int parameters = t.getFreeParameters(); + final double[] derivatives = t.getAllDerivatives(); + final double dt0 = t.getValue() - t0; + DerivativeStructure interpolation = new DerivativeStructure(parameters, order, 0.0); + DerivativeStructure monomial = null; + for (int i = 0; i < nbPoints; ++i) { + if (i == 0) { + // start with monomial(t) = 1 + monomial = new DerivativeStructure(parameters, order, 1.0); + } else { + // monomial(t) = (t - t0) * (t - t1) * ... * (t - t(i-1)) + derivatives[0] = dt0 - (i - 1) * stepSize; + final DerivativeStructure deltaX = new DerivativeStructure(parameters, order, derivatives); + monomial = monomial.multiply(deltaX); + } + interpolation = interpolation.add(monomial.multiply(top[i])); + } + + return interpolation; + + } + + /** {@inheritDoc} + *

The returned object cannot compute derivatives to arbitrary orders. The + * value function will throw a {@link NumberIsTooLargeException} if the requested + * derivation order is larger or equal to the number of points. + *

+ */ + public UnivariateDifferentiableFunction differentiate(final UnivariateFunction function) { + return new UnivariateDifferentiableFunction() { + + /** {@inheritDoc} */ + public double value(final double x) throws MathIllegalArgumentException { + return function.value(x); + } + + /** {@inheritDoc} */ + public DerivativeStructure value(final DerivativeStructure t) + throws MathIllegalArgumentException { + + // check we can achieve the requested derivation order with the sample + if (t.getOrder() >= nbPoints) { + throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false); + } + + // compute sample position, trying to be centered if possible + final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan; + + // compute sample points + final double[] y = new double[nbPoints]; + for (int i = 0; i < nbPoints; ++i) { + y[i] = function.value(t0 + i * stepSize); + } + + // evaluate derivatives + return evaluate(t, t0, y); + + } + + }; + } + + /** {@inheritDoc} + *

The returned object cannot compute derivatives to arbitrary orders. The + * value function will throw a {@link NumberIsTooLargeException} if the requested + * derivation order is larger or equal to the number of points. + *

+ */ + public UnivariateDifferentiableVectorFunction differentiate(final UnivariateVectorFunction function) { + return new UnivariateDifferentiableVectorFunction() { + + /** {@inheritDoc} */ + public double[]value(final double x) throws MathIllegalArgumentException { + return function.value(x); + } + + /** {@inheritDoc} */ + public DerivativeStructure[] value(final DerivativeStructure t) + throws MathIllegalArgumentException { + + // check we can achieve the requested derivation order with the sample + if (t.getOrder() >= nbPoints) { + throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false); + } + + // compute sample position, trying to be centered if possible + final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan; + + // compute sample points + double[][] y = null; + for (int i = 0; i < nbPoints; ++i) { + final double[] v = function.value(t0 + i * stepSize); + if (i == 0) { + y = new double[v.length][nbPoints]; + } + for (int j = 0; j < v.length; ++j) { + y[j][i] = v[j]; + } + } + + // evaluate derivatives + final DerivativeStructure[] value = new DerivativeStructure[y.length]; + for (int j = 0; j < value.length; ++j) { + value[j] = evaluate(t, t0, y[j]); + } + + return value; + + } + + }; + } + + /** {@inheritDoc} + *

The returned object cannot compute derivatives to arbitrary orders. The + * value function will throw a {@link NumberIsTooLargeException} if the requested + * derivation order is larger or equal to the number of points. + *

+ */ + public UnivariateDifferentiableMatrixFunction differentiate(final UnivariateMatrixFunction function) { + return new UnivariateDifferentiableMatrixFunction() { + + /** {@inheritDoc} */ + public double[][] value(final double x) throws MathIllegalArgumentException { + return function.value(x); + } + + /** {@inheritDoc} */ + public DerivativeStructure[][] value(final DerivativeStructure t) + throws MathIllegalArgumentException { + + // check we can achieve the requested derivation order with the sample + if (t.getOrder() >= nbPoints) { + throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false); + } + + // compute sample position, trying to be centered if possible + final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan; + + // compute sample points + double[][][] y = null; + for (int i = 0; i < nbPoints; ++i) { + final double[][] v = function.value(t0 + i * stepSize); + if (i == 0) { + y = new double[v.length][v[0].length][nbPoints]; + } + for (int j = 0; j < v.length; ++j) { + for (int k = 0; k < v[j].length; ++k) { + y[j][k][i] = v[j][k]; + } + } + } + + // evaluate derivatives + final DerivativeStructure[][] value = new DerivativeStructure[y.length][y[0].length]; + for (int j = 0; j < value.length; ++j) { + for (int k = 0; k < y[j].length; ++k) { + value[j][k] = evaluate(t, t0, y[j][k]); + } + } + + return value; + + } + + }; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/GradientFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/GradientFunction.java new file mode 100644 index 000000000..3d1272186 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/GradientFunction.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; + +/** Class representing the gradient of a multivariate function. + *

+ * The vectorial components of the function represent the derivatives + * with respect to each function parameters. + *

+ * @since 3.1 + */ +public class GradientFunction implements MultivariateVectorFunction { + + /** Underlying real-valued function. */ + private final MultivariateDifferentiableFunction f; + + /** Simple constructor. + * @param f underlying real-valued function + */ + public GradientFunction(final MultivariateDifferentiableFunction f) { + this.f = f; + } + + /** {@inheritDoc} */ + public double[] value(double[] point) { + + // set up parameters + final DerivativeStructure[] dsX = new DerivativeStructure[point.length]; + for (int i = 0; i < point.length; ++i) { + dsX[i] = new DerivativeStructure(point.length, 1, i, point[i]); + } + + // compute the derivatives + final DerivativeStructure dsY = f.value(dsX); + + // extract the gradient + final double[] y = new double[point.length]; + final int[] orders = new int[point.length]; + for (int i = 0; i < point.length; ++i) { + orders[i] = 1; + y[i] = dsY.getPartialDerivative(orders); + orders[i] = 0; + } + + return y; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java new file mode 100644 index 000000000..68c12c0ea --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; + +/** Class representing the Jacobian of a multivariate vector function. + *

+ * The rows iterate on the model functions while the columns iterate on the parameters; thus, + * the numbers of rows is equal to the dimension of the underlying function vector + * value and the number of columns is equal to the number of free parameters of + * the underlying function. + *

+ * @since 3.1 + */ +public class JacobianFunction implements MultivariateMatrixFunction { + + /** Underlying vector-valued function. */ + private final MultivariateDifferentiableVectorFunction f; + + /** Simple constructor. + * @param f underlying vector-valued function + */ + public JacobianFunction(final MultivariateDifferentiableVectorFunction f) { + this.f = f; + } + + /** {@inheritDoc} */ + public double[][] value(double[] point) { + + // set up parameters + final DerivativeStructure[] dsX = new DerivativeStructure[point.length]; + for (int i = 0; i < point.length; ++i) { + dsX[i] = new DerivativeStructure(point.length, 1, i, point[i]); + } + + // compute the derivatives + final DerivativeStructure[] dsY = f.value(dsX); + + // extract the Jacobian + final double[][] y = new double[dsY.length][point.length]; + final int[] orders = new int[point.length]; + for (int i = 0; i < dsY.length; ++i) { + for (int j = 0; j < point.length; ++j) { + orders[j] = 1; + y[i][j] = dsY[i].getPartialDerivative(orders); + orders[j] = 0; + } + } + + return y; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java new file mode 100644 index 000000000..c8efc5781 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * Extension of {@link MultivariateFunction} representing a + * multivariate differentiable real function. + * @since 3.1 + */ +public interface MultivariateDifferentiableFunction extends MultivariateFunction { + + /** + * Compute the value for the function at the given point. + * + * @param point Point at which the function must be evaluated. + * @return the function value for the given point. + * @exception MathIllegalArgumentException if {@code point} does not + * satisfy the function's constraints (wrong dimension, argument out of bound, + * or unsupported derivative order for example) + */ + DerivativeStructure value(DerivativeStructure[] point) + throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java new file mode 100644 index 000000000..acf608583 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; + + +/** + * Extension of {@link MultivariateVectorFunction} representing a + * multivariate differentiable vectorial function. + * @since 3.1 + */ +public interface MultivariateDifferentiableVectorFunction + extends MultivariateVectorFunction { + + /** + * Compute the value for the function at the given point. + * @param point point at which the function must be evaluated + * @return function value for the given point + * @exception MathIllegalArgumentException if {@code point} does not + * satisfy the function's constraints (wrong dimension, argument out of bound, + * or unsupported derivative order for example) + */ + DerivativeStructure[] value(DerivativeStructure[] point) + throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/SparseGradient.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/SparseGradient.java new file mode 100644 index 000000000..bcf9798d8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/SparseGradient.java @@ -0,0 +1,877 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * First derivative computation with large number of variables. + *

+ * This class plays a similar role to {@link DerivativeStructure}, with + * a focus on efficiency when dealing with large number of independent variables + * and most computation depend only on a few of them, and when only first derivative + * is desired. When these conditions are met, this class should be much faster than + * {@link DerivativeStructure} and use less memory. + *

+ * + * @since 3.3 + */ +public class SparseGradient implements RealFieldElement, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20131025L; + + /** Value of the calculation. */ + private double value; + + /** Stored derivative, each key representing a different independent variable. */ + private final Map derivatives; + + /** Internal constructor. + * @param value value of the function + * @param derivatives derivatives map, a deep copy will be performed, + * so the map given here will remain safe from changes in the new instance, + * may be null to create an empty derivatives map, i.e. a constant value + */ + private SparseGradient(final double value, final Map derivatives) { + this.value = value; + this.derivatives = new HashMap(); + if (derivatives != null) { + this.derivatives.putAll(derivatives); + } + } + + /** Internal constructor. + * @param value value of the function + * @param scale scaling factor to apply to all derivatives + * @param derivatives derivatives map, a deep copy will be performed, + * so the map given here will remain safe from changes in the new instance, + * may be null to create an empty derivatives map, i.e. a constant value + */ + private SparseGradient(final double value, final double scale, + final Map derivatives) { + this.value = value; + this.derivatives = new HashMap(); + if (derivatives != null) { + for (final Map.Entry entry : derivatives.entrySet()) { + this.derivatives.put(entry.getKey(), scale * entry.getValue()); + } + } + } + + /** Factory method creating a constant. + * @param value value of the constant + * @return a new instance + */ + public static SparseGradient createConstant(final double value) { + return new SparseGradient(value, Collections. emptyMap()); + } + + /** Factory method creating an independent variable. + * @param idx index of the variable + * @param value value of the variable + * @return a new instance + */ + public static SparseGradient createVariable(final int idx, final double value) { + return new SparseGradient(value, Collections.singletonMap(idx, 1.0)); + } + + /** + * Find the number of variables. + * @return number of variables + */ + public int numVars() { + return derivatives.size(); + } + + /** + * Get the derivative with respect to a particular index variable. + * + * @param index index to differentiate with. + * @return derivative with respect to a particular index variable + */ + public double getDerivative(final int index) { + final Double out = derivatives.get(index); + return (out == null) ? 0.0 : out; + } + + /** + * Get the value of the function. + * @return value of the function. + */ + public double getValue() { + return value; + } + + /** {@inheritDoc} */ + public double getReal() { + return value; + } + + /** {@inheritDoc} */ + public SparseGradient add(final SparseGradient a) { + final SparseGradient out = new SparseGradient(value + a.value, derivatives); + for (Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = out.derivatives.get(id); + if (old == null) { + out.derivatives.put(id, entry.getValue()); + } else { + out.derivatives.put(id, old + entry.getValue()); + } + } + + return out; + } + + /** + * Add in place. + *

+ * This method is designed to be faster when used multiple times in a loop. + *

+ *

+ * The instance is changed here, in order to not change the + * instance the {@link #add(SparseGradient)} method should + * be used. + *

+ * @param a instance to add + */ + public void addInPlace(final SparseGradient a) { + value += a.value; + for (final Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = derivatives.get(id); + if (old == null) { + derivatives.put(id, entry.getValue()); + } else { + derivatives.put(id, old + entry.getValue()); + } + } + } + + /** {@inheritDoc} */ + public SparseGradient add(final double c) { + final SparseGradient out = new SparseGradient(value + c, derivatives); + return out; + } + + /** {@inheritDoc} */ + public SparseGradient subtract(final SparseGradient a) { + final SparseGradient out = new SparseGradient(value - a.value, derivatives); + for (Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = out.derivatives.get(id); + if (old == null) { + out.derivatives.put(id, -entry.getValue()); + } else { + out.derivatives.put(id, old - entry.getValue()); + } + } + return out; + } + + /** {@inheritDoc} */ + public SparseGradient subtract(double c) { + return new SparseGradient(value - c, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient multiply(final SparseGradient a) { + final SparseGradient out = + new SparseGradient(value * a.value, Collections. emptyMap()); + + // Derivatives. + for (Map.Entry entry : derivatives.entrySet()) { + out.derivatives.put(entry.getKey(), a.value * entry.getValue()); + } + for (Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = out.derivatives.get(id); + if (old == null) { + out.derivatives.put(id, value * entry.getValue()); + } else { + out.derivatives.put(id, old + value * entry.getValue()); + } + } + return out; + } + + /** + * Multiply in place. + *

+ * This method is designed to be faster when used multiple times in a loop. + *

+ *

+ * The instance is changed here, in order to not change the + * instance the {@link #add(SparseGradient)} method should + * be used. + *

+ * @param a instance to multiply + */ + public void multiplyInPlace(final SparseGradient a) { + // Derivatives. + for (Map.Entry entry : derivatives.entrySet()) { + derivatives.put(entry.getKey(), a.value * entry.getValue()); + } + for (Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = derivatives.get(id); + if (old == null) { + derivatives.put(id, value * entry.getValue()); + } else { + derivatives.put(id, old + value * entry.getValue()); + } + } + value *= a.value; + } + + /** {@inheritDoc} */ + public SparseGradient multiply(final double c) { + return new SparseGradient(value * c, c, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient multiply(final int n) { + return new SparseGradient(value * n, n, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient divide(final SparseGradient a) { + final SparseGradient out = new SparseGradient(value / a.value, Collections. emptyMap()); + + // Derivatives. + for (Map.Entry entry : derivatives.entrySet()) { + out.derivatives.put(entry.getKey(), entry.getValue() / a.value); + } + for (Map.Entry entry : a.derivatives.entrySet()) { + final int id = entry.getKey(); + final Double old = out.derivatives.get(id); + if (old == null) { + out.derivatives.put(id, -out.value / a.value * entry.getValue()); + } else { + out.derivatives.put(id, old - out.value / a.value * entry.getValue()); + } + } + return out; + } + + /** {@inheritDoc} */ + public SparseGradient divide(final double c) { + return new SparseGradient(value / c, 1.0 / c, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient negate() { + return new SparseGradient(-value, -1.0, derivatives); + } + + /** {@inheritDoc} */ + public Field getField() { + return new Field() { + + /** {@inheritDoc} */ + public SparseGradient getZero() { + return createConstant(0); + } + + /** {@inheritDoc} */ + public SparseGradient getOne() { + return createConstant(1); + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return SparseGradient.class; + } + + }; + } + + /** {@inheritDoc} */ + public SparseGradient remainder(final double a) { + return new SparseGradient(FastMath.IEEEremainder(value, a), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient remainder(final SparseGradient a) { + + // compute k such that lhs % rhs = lhs - k rhs + final double rem = FastMath.IEEEremainder(value, a.value); + final double k = FastMath.rint((value - rem) / a.value); + + return subtract(a.multiply(k)); + + } + + /** {@inheritDoc} */ + public SparseGradient abs() { + if (Double.doubleToLongBits(value) < 0) { + // we use the bits representation to also handle -0.0 + return negate(); + } else { + return this; + } + } + + /** {@inheritDoc} */ + public SparseGradient ceil() { + return createConstant(FastMath.ceil(value)); + } + + /** {@inheritDoc} */ + public SparseGradient floor() { + return createConstant(FastMath.floor(value)); + } + + /** {@inheritDoc} */ + public SparseGradient rint() { + return createConstant(FastMath.rint(value)); + } + + /** {@inheritDoc} */ + public long round() { + return FastMath.round(value); + } + + /** {@inheritDoc} */ + public SparseGradient signum() { + return createConstant(FastMath.signum(value)); + } + + /** {@inheritDoc} */ + public SparseGradient copySign(final SparseGradient sign) { + final long m = Double.doubleToLongBits(value); + final long s = Double.doubleToLongBits(sign.value); + if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** {@inheritDoc} */ + public SparseGradient copySign(final double sign) { + final long m = Double.doubleToLongBits(value); + final long s = Double.doubleToLongBits(sign); + if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** {@inheritDoc} */ + public SparseGradient scalb(final int n) { + final SparseGradient out = new SparseGradient(FastMath.scalb(value, n), Collections. emptyMap()); + for (Map.Entry entry : derivatives.entrySet()) { + out.derivatives.put(entry.getKey(), FastMath.scalb(entry.getValue(), n)); + } + return out; + } + + /** {@inheritDoc} */ + public SparseGradient hypot(final SparseGradient y) { + if (Double.isInfinite(value) || Double.isInfinite(y.value)) { + return createConstant(Double.POSITIVE_INFINITY); + } else if (Double.isNaN(value) || Double.isNaN(y.value)) { + return createConstant(Double.NaN); + } else { + + final int expX = FastMath.getExponent(value); + final int expY = FastMath.getExponent(y.value); + if (expX > expY + 27) { + // y is negligible with respect to x + return abs(); + } else if (expY > expX + 27) { + // x is negligible with respect to y + return y.abs(); + } else { + + // find an intermediate scale to avoid both overflow and underflow + final int middleExp = (expX + expY) / 2; + + // scale parameters without losing precision + final SparseGradient scaledX = scalb(-middleExp); + final SparseGradient scaledY = y.scalb(-middleExp); + + // compute scaled hypotenuse + final SparseGradient scaledH = + scaledX.multiply(scaledX).add(scaledY.multiply(scaledY)).sqrt(); + + // remove scaling + return scaledH.scalb(middleExp); + + } + + } + } + + /** + * Returns the hypotenuse of a triangle with sides {@code x} and {@code y} + * - sqrt(x2 +y2) + * avoiding intermediate overflow or underflow. + * + *
    + *
  • If either argument is infinite, then the result is positive infinity.
  • + *
  • else, if either argument is NaN then the result is NaN.
  • + *
+ * + * @param x a value + * @param y a value + * @return sqrt(x2 +y2) + */ + public static SparseGradient hypot(final SparseGradient x, final SparseGradient y) { + return x.hypot(y); + } + + /** {@inheritDoc} */ + public SparseGradient reciprocal() { + return new SparseGradient(1.0 / value, -1.0 / (value * value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient sqrt() { + final double sqrt = FastMath.sqrt(value); + return new SparseGradient(sqrt, 0.5 / sqrt, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient cbrt() { + final double cbrt = FastMath.cbrt(value); + return new SparseGradient(cbrt, 1.0 / (3 * cbrt * cbrt), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient rootN(final int n) { + if (n == 2) { + return sqrt(); + } else if (n == 3) { + return cbrt(); + } else { + final double root = FastMath.pow(value, 1.0 / n); + return new SparseGradient(root, 1.0 / (n * FastMath.pow(root, n - 1)), derivatives); + } + } + + /** {@inheritDoc} */ + public SparseGradient pow(final double p) { + return new SparseGradient(FastMath.pow(value, p), p * FastMath.pow(value, p - 1), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient pow(final int n) { + if (n == 0) { + return getField().getOne(); + } else { + final double valueNm1 = FastMath.pow(value, n - 1); + return new SparseGradient(value * valueNm1, n * valueNm1, derivatives); + } + } + + /** {@inheritDoc} */ + public SparseGradient pow(final SparseGradient e) { + return log().multiply(e).exp(); + } + + /** Compute ax where a is a double and x a {@link SparseGradient} + * @param a number to exponentiate + * @param x power to apply + * @return ax + */ + public static SparseGradient pow(final double a, final SparseGradient x) { + if (a == 0) { + if (x.value == 0) { + return x.compose(1.0, Double.NEGATIVE_INFINITY); + } else if (x.value < 0) { + return x.compose(Double.NaN, Double.NaN); + } else { + return x.getField().getZero(); + } + } else { + final double ax = FastMath.pow(a, x.value); + return new SparseGradient(ax, ax * FastMath.log(a), x.derivatives); + } + } + + /** {@inheritDoc} */ + public SparseGradient exp() { + final double e = FastMath.exp(value); + return new SparseGradient(e, e, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient expm1() { + return new SparseGradient(FastMath.expm1(value), FastMath.exp(value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient log() { + return new SparseGradient(FastMath.log(value), 1.0 / value, derivatives); + } + + /** Base 10 logarithm. + * @return base 10 logarithm of the instance + */ + public SparseGradient log10() { + return new SparseGradient(FastMath.log10(value), 1.0 / (FastMath.log(10.0) * value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient log1p() { + return new SparseGradient(FastMath.log1p(value), 1.0 / (1.0 + value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient cos() { + return new SparseGradient(FastMath.cos(value), -FastMath.sin(value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient sin() { + return new SparseGradient(FastMath.sin(value), FastMath.cos(value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient tan() { + final double t = FastMath.tan(value); + return new SparseGradient(t, 1 + t * t, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient acos() { + return new SparseGradient(FastMath.acos(value), -1.0 / FastMath.sqrt(1 - value * value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient asin() { + return new SparseGradient(FastMath.asin(value), 1.0 / FastMath.sqrt(1 - value * value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient atan() { + return new SparseGradient(FastMath.atan(value), 1.0 / (1 + value * value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient atan2(final SparseGradient x) { + + // compute r = sqrt(x^2+y^2) + final SparseGradient r = multiply(this).add(x.multiply(x)).sqrt(); + + final SparseGradient a; + if (x.value >= 0) { + + // compute atan2(y, x) = 2 atan(y / (r + x)) + a = divide(r.add(x)).atan().multiply(2); + + } else { + + // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x)) + final SparseGradient tmp = divide(r.subtract(x)).atan().multiply(-2); + a = tmp.add(tmp.value <= 0 ? -FastMath.PI : FastMath.PI); + + } + + // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly + a.value = FastMath.atan2(value, x.value); + + return a; + + } + + /** Two arguments arc tangent operation. + * @param y first argument of the arc tangent + * @param x second argument of the arc tangent + * @return atan2(y, x) + */ + public static SparseGradient atan2(final SparseGradient y, final SparseGradient x) { + return y.atan2(x); + } + + /** {@inheritDoc} */ + public SparseGradient cosh() { + return new SparseGradient(FastMath.cosh(value), FastMath.sinh(value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient sinh() { + return new SparseGradient(FastMath.sinh(value), FastMath.cosh(value), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient tanh() { + final double t = FastMath.tanh(value); + return new SparseGradient(t, 1 - t * t, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient acosh() { + return new SparseGradient(FastMath.acosh(value), 1.0 / FastMath.sqrt(value * value - 1.0), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient asinh() { + return new SparseGradient(FastMath.asinh(value), 1.0 / FastMath.sqrt(value * value + 1.0), derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient atanh() { + return new SparseGradient(FastMath.atanh(value), 1.0 / (1.0 - value * value), derivatives); + } + + /** Convert radians to degrees, with error of less than 0.5 ULP + * @return instance converted into degrees + */ + public SparseGradient toDegrees() { + return new SparseGradient(FastMath.toDegrees(value), FastMath.toDegrees(1.0), derivatives); + } + + /** Convert degrees to radians, with error of less than 0.5 ULP + * @return instance converted into radians + */ + public SparseGradient toRadians() { + return new SparseGradient(FastMath.toRadians(value), FastMath.toRadians(1.0), derivatives); + } + + /** Evaluate Taylor expansion of a sparse gradient. + * @param delta parameters offsets (Δx, Δy, ...) + * @return value of the Taylor expansion at x + Δx, y + Δy, ... + */ + public double taylor(final double ... delta) { + double y = value; + for (int i = 0; i < delta.length; ++i) { + y += delta[i] * getDerivative(i); + } + return y; + } + + /** Compute composition of the instance by a univariate function. + * @param f0 value of the function at (i.e. f({@link #getValue()})) + * @param f1 first derivative of the function at + * the current point (i.e. f'({@link #getValue()})) + * @return f(this) + */ + public SparseGradient compose(final double f0, final double f1) { + return new SparseGradient(f0, f1, derivatives); + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final SparseGradient[] a, + final SparseGradient[] b) + throws DimensionMismatchException { + + // compute a simple value, with all partial derivatives + SparseGradient out = a[0].getField().getZero(); + for (int i = 0; i < a.length; ++i) { + out = out.add(a[i].multiply(b[i])); + } + + // recompute an accurate value, taking care of cancellations + final double[] aDouble = new double[a.length]; + for (int i = 0; i < a.length; ++i) { + aDouble[i] = a[i].getValue(); + } + final double[] bDouble = new double[b.length]; + for (int i = 0; i < b.length; ++i) { + bDouble[i] = b[i].getValue(); + } + out.value = MathArrays.linearCombination(aDouble, bDouble); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final double[] a, final SparseGradient[] b) { + + // compute a simple value, with all partial derivatives + SparseGradient out = b[0].getField().getZero(); + for (int i = 0; i < a.length; ++i) { + out = out.add(b[i].multiply(a[i])); + } + + // recompute an accurate value, taking care of cancellations + final double[] bDouble = new double[b.length]; + for (int i = 0; i < b.length; ++i) { + bDouble[i] = b[i].getValue(); + } + out.value = MathArrays.linearCombination(a, bDouble); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1, + final SparseGradient a2, final SparseGradient b2) { + + // compute a simple value, with all partial derivatives + SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1.value, b1.value, a2.value, b2.value); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final double a1, final SparseGradient b1, + final double a2, final SparseGradient b2) { + + // compute a simple value, with all partial derivatives + SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1, b1.value, a2, b2.value); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1, + final SparseGradient a2, final SparseGradient b2, + final SparseGradient a3, final SparseGradient b3) { + + // compute a simple value, with all partial derivatives + SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1.value, b1.value, + a2.value, b2.value, + a3.value, b3.value); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final double a1, final SparseGradient b1, + final double a2, final SparseGradient b2, + final double a3, final SparseGradient b3) { + + // compute a simple value, with all partial derivatives + SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1, b1.value, + a2, b2.value, + a3, b3.value); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1, + final SparseGradient a2, final SparseGradient b2, + final SparseGradient a3, final SparseGradient b3, + final SparseGradient a4, final SparseGradient b4) { + + // compute a simple value, with all partial derivatives + SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1.value, b1.value, + a2.value, b2.value, + a3.value, b3.value, + a4.value, b4.value); + + return out; + + } + + /** {@inheritDoc} */ + public SparseGradient linearCombination(final double a1, final SparseGradient b1, + final double a2, final SparseGradient b2, + final double a3, final SparseGradient b3, + final double a4, final SparseGradient b4) { + + // compute a simple value, with all partial derivatives + SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4)); + + // recompute an accurate value, taking care of cancellations + out.value = MathArrays.linearCombination(a1, b1.value, + a2, b2.value, + a3, b3.value, + a4, b4.value); + + return out; + + } + + /** + * Test for the equality of two sparse gradients. + *

+ * Sparse gradients are considered equal if they have the same value + * and the same derivatives. + *

+ * @param other Object to test for equality to this + * @return true if two sparse gradients are equal + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof SparseGradient) { + final SparseGradient rhs = (SparseGradient)other; + if (!Precision.equals(value, rhs.value, 1)) { + return false; + } + if (derivatives.size() != rhs.derivatives.size()) { + return false; + } + for (final Map.Entry entry : derivatives.entrySet()) { + if (!rhs.derivatives.containsKey(entry.getKey())) { + return false; + } + if (!Precision.equals(entry.getValue(), rhs.derivatives.get(entry.getKey()), 1)) { + return false; + } + } + return true; + } + + return false; + + } + + /** + * Get a hashCode for the derivative structure. + * @return a hash code value for this object + * @since 3.2 + */ + @Override + public int hashCode() { + return 743 + 809 * MathUtils.hash(value) + 167 * derivatives.hashCode(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java new file mode 100644 index 000000000..09ba4da8d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** Interface for univariate functions derivatives. + *

This interface represents a simple function which computes + * both the value and the first derivative of a mathematical function. + * The derivative is computed with respect to the input variable.

+ * @see UnivariateDifferentiableFunction + * @see UnivariateFunctionDifferentiator + * @since 3.1 + */ +public interface UnivariateDifferentiableFunction extends UnivariateFunction { + + /** Simple mathematical function. + *

{@link UnivariateDifferentiableFunction} classes compute both the + * value and the first derivative of the function.

+ * @param t function input value + * @return function result + * @exception DimensionMismatchException if t is inconsistent with the + * function's free parameters or order + */ + DerivativeStructure value(DerivativeStructure t) + throws DimensionMismatchException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java new file mode 100644 index 000000000..15eca0426 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateMatrixFunction; + +/** + * Extension of {@link UnivariateMatrixFunction} representing a univariate differentiable matrix function. + * + * @since 3.1 + */ +public interface UnivariateDifferentiableMatrixFunction + extends UnivariateMatrixFunction { + + /** + * Compute the value for the function. + * @param x the point for which the function value should be computed + * @return the value + * @exception MathIllegalArgumentException if {@code x} does not + * satisfy the function's constraints (argument out of bound, or unsupported + * derivative order for example) + */ + DerivativeStructure[][] value(DerivativeStructure x) throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java new file mode 100644 index 000000000..69f145560 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateVectorFunction; + +/** + * Extension of {@link UnivariateVectorFunction} representing a univariate differentiable vectorial function. + * + * @since 3.1 + */ +public interface UnivariateDifferentiableVectorFunction + extends UnivariateVectorFunction { + + /** + * Compute the value for the function. + * @param x the point for which the function value should be computed + * @return the value + * @exception MathIllegalArgumentException if {@code x} does not + * satisfy the function's constraints (argument out of bound, or unsupported + * derivative order for example) + */ + DerivativeStructure[] value(DerivativeStructure x) throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java new file mode 100644 index 000000000..f218c3589 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** Interface defining the function differentiation operation. + * @since 3.1 + */ +public interface UnivariateFunctionDifferentiator { + + /** Create an implementation of a {@link UnivariateDifferentiableFunction + * differential} from a regular {@link UnivariateFunction function}. + * @param function function to differentiate + * @return differential function + */ + UnivariateDifferentiableFunction differentiate(UnivariateFunction function); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java new file mode 100644 index 000000000..dddd3231c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateMatrixFunction; + +/** Interface defining the function differentiation operation. + * @since 3.1 + */ +public interface UnivariateMatrixFunctionDifferentiator { + + /** Create an implementation of a {@link UnivariateDifferentiableMatrixFunction + * differential} from a regular {@link UnivariateMatrixFunction matrix function}. + * @param function function to differentiate + * @return differential function + */ + UnivariateDifferentiableMatrixFunction differentiate(UnivariateMatrixFunction function); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java new file mode 100644 index 000000000..6bf536a57 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateVectorFunction; + +/** Interface defining the function differentiation operation. + * @since 3.1 + */ +public interface UnivariateVectorFunctionDifferentiator { + + /** Create an implementation of a {@link UnivariateDifferentiableVectorFunction + * differential} from a regular {@link UnivariateVectorFunction vector function}. + * @param function function to differentiate + * @return differential function + */ + UnivariateDifferentiableVectorFunction differentiate(UnivariateVectorFunction function); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/package-info.java new file mode 100644 index 000000000..8ce37f3b4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/differentiation/package-info.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

+ * This package holds the main interfaces and basic building block classes + * dealing with differentiation. + * The core class is {@link com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure + * DerivativeStructure} which holds the value and the differentials of a function. This class + * handles some arbitrary number of free parameters and arbitrary differentiation order. It is used + * both as the input and the output type for the {@link + * com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction + * UnivariateDifferentiableFunction} interface. Any differentiable function should implement this + * interface. + *

+ *

+ * The {@link com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateFunctionDifferentiator + * UnivariateFunctionDifferentiator} interface defines a way to differentiate a simple {@link + * com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction UnivariateFunction} and get a {@link + * com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction + * UnivariateDifferentiableFunction}. + *

+ *

+ * Similar interfaces also exist for multivariate functions and for vector or matrix valued functions. + *

+ * + */ +package com.fr.third.org.apache.commons.math3.analysis.differentiation; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Abs.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Abs.java new file mode 100644 index 000000000..8d24d3f92 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Abs.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Absolute value function. + * + * @since 3.0 + */ +public class Abs implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.abs(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acos.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acos.java new file mode 100644 index 000000000..f56a4a169 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acos.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Arc-cosine function. + * + * @since 3.0 + */ +public class Acos implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.acos(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.acos(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acosh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acosh.java new file mode 100644 index 000000000..e78fcd958 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Acosh.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic arc-cosine function. + * + * @since 3.0 + */ +public class Acosh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.acosh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.acosh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Add.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Add.java new file mode 100644 index 000000000..0ba548d93 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Add.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; + +/** + * Add the two operands. + * + * @since 3.0 + */ +public class Add implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return x + y; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asin.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asin.java new file mode 100644 index 000000000..1969e0514 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asin.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Arc-sine function. + * + * @since 3.0 + */ +public class Asin implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.asin(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.asin(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asinh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asinh.java new file mode 100644 index 000000000..c44a267df --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Asinh.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic arc-sine function. + * + * @since 3.0 + */ +public class Asinh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.asinh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.asinh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan.java new file mode 100644 index 000000000..036e3399a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Arc-tangent function. + * + * @since 3.0 + */ +public class Atan implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.atan(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.atan(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan2.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan2.java new file mode 100644 index 000000000..1796aaf8b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atan2.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Arc-tangent function. + * + * @since 3.0 + */ +public class Atan2 implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return FastMath.atan2(x, y); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atanh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atanh.java new file mode 100644 index 000000000..09877ecaa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Atanh.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic arc-tangent function. + * + * @since 3.0 + */ +public class Atanh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.atanh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.atanh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cbrt.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cbrt.java new file mode 100644 index 000000000..06a53ddbc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cbrt.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Cube root function. + * + * @since 3.0 + */ +public class Cbrt implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.cbrt(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.cbrt(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ceil.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ceil.java new file mode 100644 index 000000000..e5bccb63f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ceil.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * {@code ceil} function. + * + * @since 3.0 + */ +public class Ceil implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.ceil(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Constant.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Constant.java new file mode 100644 index 000000000..87e844fd7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Constant.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; + +/** + * Constant function. + * + * @since 3.0 + */ +public class Constant implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Constant. */ + private final double c; + + /** + * @param c Constant. + */ + public Constant(double c) { + this.c = c; + } + + /** {@inheritDoc} */ + public double value(double x) { + return c; + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Constant(0); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return new DerivativeStructure(t.getFreeParameters(), t.getOrder(), c); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cos.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cos.java new file mode 100644 index 000000000..aaeed0c80 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cos.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Cosine function. + * + * @since 3.0 + */ +public class Cos implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.cos(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.cos(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cosh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cosh.java new file mode 100644 index 000000000..896b1decf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Cosh.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic cosine function. + * + * @since 3.0 + */ +public class Cosh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.cosh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Sinh(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.cosh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Divide.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Divide.java new file mode 100644 index 000000000..6e2de34f5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Divide.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; + +/** + * Divide the first operand by the second. + * + * @since 3.0 + */ +public class Divide implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return x / y; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Exp.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Exp.java new file mode 100644 index 000000000..734514ad1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Exp.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Exponential function. + * + * @since 3.0 + */ +public class Exp implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.exp(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.exp(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Expm1.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Expm1.java new file mode 100644 index 000000000..bec8131c4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Expm1.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * ex-1 function. + * + * @since 3.0 + */ +public class Expm1 implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.expm1(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.expm1(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Floor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Floor.java new file mode 100644 index 000000000..e4e7bb6e1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Floor.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * {@code floor} function. + * + * @since 3.0 + */ +public class Floor implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.floor(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Gaussian.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Gaussian.java new file mode 100644 index 000000000..1701510b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Gaussian.java @@ -0,0 +1,259 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * + * Gaussian function. + * + * @since 3.0 + */ +public class Gaussian implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Mean. */ + private final double mean; + /** Inverse of the standard deviation. */ + private final double is; + /** Inverse of twice the square of the standard deviation. */ + private final double i2s2; + /** Normalization factor. */ + private final double norm; + + /** + * Gaussian with given normalization factor, mean and standard deviation. + * + * @param norm Normalization factor. + * @param mean Mean. + * @param sigma Standard deviation. + * @throws NotStrictlyPositiveException if {@code sigma <= 0}. + */ + public Gaussian(double norm, + double mean, + double sigma) + throws NotStrictlyPositiveException { + if (sigma <= 0) { + throw new NotStrictlyPositiveException(sigma); + } + + this.norm = norm; + this.mean = mean; + this.is = 1 / sigma; + this.i2s2 = 0.5 * is * is; + } + + /** + * Normalized gaussian with given mean and standard deviation. + * + * @param mean Mean. + * @param sigma Standard deviation. + * @throws NotStrictlyPositiveException if {@code sigma <= 0}. + */ + public Gaussian(double mean, + double sigma) + throws NotStrictlyPositiveException { + this(1 / (sigma * FastMath.sqrt(2 * Math.PI)), mean, sigma); + } + + /** + * Normalized gaussian with zero mean and unit standard deviation. + */ + public Gaussian() { + this(0, 1); + } + + /** {@inheritDoc} */ + public double value(double x) { + return value(x - mean, norm, i2s2); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** + * Parametric function where the input array contains the parameters of + * the Gaussian, ordered as follows: + *
    + *
  • Norm
  • + *
  • Mean
  • + *
  • Standard deviation
  • + *
+ */ + public static class Parametric implements ParametricUnivariateFunction { + /** + * Computes the value of the Gaussian at {@code x}. + * + * @param x Value for which the function must be computed. + * @param param Values of norm, mean and standard deviation. + * @return the value of the function. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + * @throws NotStrictlyPositiveException if {@code param[2]} is negative. + */ + public double value(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + validateParameters(param); + + final double diff = x - param[1]; + final double i2s2 = 1 / (2 * param[2] * param[2]); + return Gaussian.value(diff, param[0], i2s2); + } + + /** + * Computes the value of the gradient at {@code x}. + * The components of the gradient vector are the partial + * derivatives of the function with respect to each of the + * parameters (norm, mean and standard deviation). + * + * @param x Value at which the gradient must be computed. + * @param param Values of norm, mean and standard deviation. + * @return the gradient vector at {@code x}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + * @throws NotStrictlyPositiveException if {@code param[2]} is negative. + */ + public double[] gradient(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + validateParameters(param); + + final double norm = param[0]; + final double diff = x - param[1]; + final double sigma = param[2]; + final double i2s2 = 1 / (2 * sigma * sigma); + + final double n = Gaussian.value(diff, 1, i2s2); + final double m = norm * n * 2 * i2s2 * diff; + final double s = m * diff / sigma; + + return new double[] { n, m, s }; + } + + /** + * Validates parameters to ensure they are appropriate for the evaluation of + * the {@link #value(double,double[])} and {@link #gradient(double,double[])} + * methods. + * + * @param param Values of norm, mean and standard deviation. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + * @throws NotStrictlyPositiveException if {@code param[2]} is negative. + */ + private void validateParameters(double[] param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + if (param == null) { + throw new NullArgumentException(); + } + if (param.length != 3) { + throw new DimensionMismatchException(param.length, 3); + } + if (param[2] <= 0) { + throw new NotStrictlyPositiveException(param[2]); + } + } + } + + /** + * @param xMinusMean {@code x - mean}. + * @param norm Normalization factor. + * @param i2s2 Inverse of twice the square of the standard deviation. + * @return the value of the Gaussian at {@code x}. + */ + private static double value(double xMinusMean, + double norm, + double i2s2) { + return norm * FastMath.exp(-xMinusMean * xMinusMean * i2s2); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) + throws DimensionMismatchException { + + final double u = is * (t.getValue() - mean); + double[] f = new double[t.getOrder() + 1]; + + // the nth order derivative of the Gaussian has the form: + // dn(g(x)/dxn = (norm / s^n) P_n(u) exp(-u^2/2) with u=(x-m)/s + // where P_n(u) is a degree n polynomial with same parity as n + // P_0(u) = 1, P_1(u) = -u, P_2(u) = u^2 - 1, P_3(u) = -u^3 + 3 u... + // the general recurrence relation for P_n is: + // P_n(u) = P_(n-1)'(u) - u P_(n-1)(u) + // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array + final double[] p = new double[f.length]; + p[0] = 1; + final double u2 = u * u; + double coeff = norm * FastMath.exp(-0.5 * u2); + if (coeff <= Precision.SAFE_MIN) { + Arrays.fill(f, 0.0); + } else { + f[0] = coeff; + for (int n = 1; n < f.length; ++n) { + + // update and evaluate polynomial P_n(x) + double v = 0; + p[n] = -p[n - 1]; + for (int k = n; k >= 0; k -= 2) { + v = v * u2 + p[k]; + if (k > 2) { + p[k - 2] = (k - 1) * p[k - 1] - p[k - 3]; + } else if (k == 2) { + p[0] = p[1]; + } + } + if ((n & 0x1) == 1) { + v *= u; + } + + coeff *= is; + f[n] = coeff * v; + + } + } + + return t.compose(f); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/HarmonicOscillator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/HarmonicOscillator.java new file mode 100644 index 000000000..8602093f6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/HarmonicOscillator.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * + * simple harmonic oscillator function. + * + * @since 3.0 + */ +public class HarmonicOscillator implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Amplitude. */ + private final double amplitude; + /** Angular frequency. */ + private final double omega; + /** Phase. */ + private final double phase; + + /** + * Harmonic oscillator function. + * + * @param amplitude Amplitude. + * @param omega Angular frequency. + * @param phase Phase. + */ + public HarmonicOscillator(double amplitude, + double omega, + double phase) { + this.amplitude = amplitude; + this.omega = omega; + this.phase = phase; + } + + /** {@inheritDoc} */ + public double value(double x) { + return value(omega * x + phase, amplitude); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** + * Parametric function where the input array contains the parameters of + * the harmonic oscillator function, ordered as follows: + *
    + *
  • Amplitude
  • + *
  • Angular frequency
  • + *
  • Phase
  • + *
+ */ + public static class Parametric implements ParametricUnivariateFunction { + /** + * Computes the value of the harmonic oscillator at {@code x}. + * + * @param x Value for which the function must be computed. + * @param param Values of norm, mean and standard deviation. + * @return the value of the function. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + */ + public double value(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + return HarmonicOscillator.value(x * param[1] + param[2], param[0]); + } + + /** + * Computes the value of the gradient at {@code x}. + * The components of the gradient vector are the partial + * derivatives of the function with respect to each of the + * parameters (amplitude, angular frequency and phase). + * + * @param x Value at which the gradient must be computed. + * @param param Values of amplitude, angular frequency and phase. + * @return the gradient vector at {@code x}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + */ + public double[] gradient(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + + final double amplitude = param[0]; + final double omega = param[1]; + final double phase = param[2]; + + final double xTimesOmegaPlusPhase = omega * x + phase; + final double a = HarmonicOscillator.value(xTimesOmegaPlusPhase, 1); + final double p = -amplitude * FastMath.sin(xTimesOmegaPlusPhase); + final double w = p * x; + + return new double[] { a, w, p }; + } + + /** + * Validates parameters to ensure they are appropriate for the evaluation of + * the {@link #value(double,double[])} and {@link #gradient(double,double[])} + * methods. + * + * @param param Values of norm, mean and standard deviation. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 3. + */ + private void validateParameters(double[] param) + throws NullArgumentException, + DimensionMismatchException { + if (param == null) { + throw new NullArgumentException(); + } + if (param.length != 3) { + throw new DimensionMismatchException(param.length, 3); + } + } + } + + /** + * @param xTimesOmegaPlusPhase {@code omega * x + phase}. + * @param amplitude Amplitude. + * @return the value of the harmonic oscillator function at {@code x}. + */ + private static double value(double xTimesOmegaPlusPhase, + double amplitude) { + return amplitude * FastMath.cos(xTimesOmegaPlusPhase); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) + throws DimensionMismatchException { + final double x = t.getValue(); + double[] f = new double[t.getOrder() + 1]; + + final double alpha = omega * x + phase; + f[0] = amplitude * FastMath.cos(alpha); + if (f.length > 1) { + f[1] = -amplitude * omega * FastMath.sin(alpha); + final double mo2 = - omega * omega; + for (int i = 2; i < f.length; ++i) { + f[i] = mo2 * f[i - 2]; + } + } + + return t.compose(f); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Identity.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Identity.java new file mode 100644 index 000000000..d0289bab1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Identity.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; + +/** + * Identity function. + * + * @since 3.0 + */ +public class Identity implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return x; + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Constant(1); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Inverse.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Inverse.java new file mode 100644 index 000000000..399cddc55 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Inverse.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; + +/** + * Inverse function. + * + * @since 3.0 + */ +public class Inverse implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return 1 / x; + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.reciprocal(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log.java new file mode 100644 index 000000000..e6789a11b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Natural logarithm function. + * + * @since 3.0 + */ +public class Log implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.log(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.log(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log10.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log10.java new file mode 100644 index 000000000..85de09616 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log10.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Base 10 logarithm function. + * + * @since 3.0 + */ +public class Log10 implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.log10(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.log10(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log1p.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log1p.java new file mode 100644 index 000000000..5962edb13 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Log1p.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * log(1 + p) function. + * + * @since 3.0 + */ +public class Log1p implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.log1p(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.log1p(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logistic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logistic.java new file mode 100644 index 000000000..a726aabab --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logistic.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * + * Generalised logistic function. + * + * @since 3.0 + */ +public class Logistic implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Lower asymptote. */ + private final double a; + /** Upper asymptote. */ + private final double k; + /** Growth rate. */ + private final double b; + /** Parameter that affects near which asymptote maximum growth occurs. */ + private final double oneOverN; + /** Parameter that affects the position of the curve along the ordinate axis. */ + private final double q; + /** Abscissa of maximum growth. */ + private final double m; + + /** + * @param k If {@code b > 0}, value of the function for x going towards +∞. + * If {@code b < 0}, value of the function for x going towards -∞. + * @param m Abscissa of maximum growth. + * @param b Growth rate. + * @param q Parameter that affects the position of the curve along the + * ordinate axis. + * @param a If {@code b > 0}, value of the function for x going towards -∞. + * If {@code b < 0}, value of the function for x going towards +∞. + * @param n Parameter that affects near which asymptote the maximum + * growth occurs. + * @throws NotStrictlyPositiveException if {@code n <= 0}. + */ + public Logistic(double k, + double m, + double b, + double q, + double a, + double n) + throws NotStrictlyPositiveException { + if (n <= 0) { + throw new NotStrictlyPositiveException(n); + } + + this.k = k; + this.m = m; + this.b = b; + this.q = q; + this.a = a; + oneOverN = 1 / n; + } + + /** {@inheritDoc} */ + public double value(double x) { + return value(m - x, k, b, q, a, oneOverN); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** + * Parametric function where the input array contains the parameters of + * the {@link Logistic#Logistic(double,double,double,double,double,double) + * logistic function}, ordered as follows: + *
    + *
  • k
  • + *
  • m
  • + *
  • b
  • + *
  • q
  • + *
  • a
  • + *
  • n
  • + *
+ */ + public static class Parametric implements ParametricUnivariateFunction { + /** + * Computes the value of the sigmoid at {@code x}. + * + * @param x Value for which the function must be computed. + * @param param Values for {@code k}, {@code m}, {@code b}, {@code q}, + * {@code a} and {@code n}. + * @return the value of the function. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 6. + * @throws NotStrictlyPositiveException if {@code param[5] <= 0}. + */ + public double value(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + validateParameters(param); + return Logistic.value(param[1] - x, param[0], + param[2], param[3], + param[4], 1 / param[5]); + } + + /** + * Computes the value of the gradient at {@code x}. + * The components of the gradient vector are the partial + * derivatives of the function with respect to each of the + * parameters. + * + * @param x Value at which the gradient must be computed. + * @param param Values for {@code k}, {@code m}, {@code b}, {@code q}, + * {@code a} and {@code n}. + * @return the gradient vector at {@code x}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 6. + * @throws NotStrictlyPositiveException if {@code param[5] <= 0}. + */ + public double[] gradient(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + validateParameters(param); + + final double b = param[2]; + final double q = param[3]; + + final double mMinusX = param[1] - x; + final double oneOverN = 1 / param[5]; + final double exp = FastMath.exp(b * mMinusX); + final double qExp = q * exp; + final double qExp1 = qExp + 1; + final double factor1 = (param[0] - param[4]) * oneOverN / FastMath.pow(qExp1, oneOverN); + final double factor2 = -factor1 / qExp1; + + // Components of the gradient. + final double gk = Logistic.value(mMinusX, 1, b, q, 0, oneOverN); + final double gm = factor2 * b * qExp; + final double gb = factor2 * mMinusX * qExp; + final double gq = factor2 * exp; + final double ga = Logistic.value(mMinusX, 0, b, q, 1, oneOverN); + final double gn = factor1 * FastMath.log(qExp1) * oneOverN; + + return new double[] { gk, gm, gb, gq, ga, gn }; + } + + /** + * Validates parameters to ensure they are appropriate for the evaluation of + * the {@link #value(double,double[])} and {@link #gradient(double,double[])} + * methods. + * + * @param param Values for {@code k}, {@code m}, {@code b}, {@code q}, + * {@code a} and {@code n}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 6. + * @throws NotStrictlyPositiveException if {@code param[5] <= 0}. + */ + private void validateParameters(double[] param) + throws NullArgumentException, + DimensionMismatchException, + NotStrictlyPositiveException { + if (param == null) { + throw new NullArgumentException(); + } + if (param.length != 6) { + throw new DimensionMismatchException(param.length, 6); + } + if (param[5] <= 0) { + throw new NotStrictlyPositiveException(param[5]); + } + } + } + + /** + * @param mMinusX {@code m - x}. + * @param k {@code k}. + * @param b {@code b}. + * @param q {@code q}. + * @param a {@code a}. + * @param oneOverN {@code 1 / n}. + * @return the value of the function. + */ + private static double value(double mMinusX, + double k, + double b, + double q, + double a, + double oneOverN) { + return a + (k - a) / FastMath.pow(1 + q * FastMath.exp(b * mMinusX), oneOverN); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.negate().add(m).multiply(b).exp().multiply(q).add(1).pow(oneOverN).reciprocal().multiply(k - a).add(a); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logit.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logit.java new file mode 100644 index 000000000..10174e23b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Logit.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * + * Logit function. + * It is the inverse of the {@link Sigmoid sigmoid} function. + * + * @since 3.0 + */ +public class Logit implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Lower bound. */ + private final double lo; + /** Higher bound. */ + private final double hi; + + /** + * Usual logit function, where the lower bound is 0 and the higher + * bound is 1. + */ + public Logit() { + this(0, 1); + } + + /** + * Logit function. + * + * @param lo Lower bound of the function domain. + * @param hi Higher bound of the function domain. + */ + public Logit(double lo, + double hi) { + this.lo = lo; + this.hi = hi; + } + + /** {@inheritDoc} */ + public double value(double x) + throws OutOfRangeException { + return value(x, lo, hi); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** + * Parametric function where the input array contains the parameters of + * the logit function, ordered as follows: + *
    + *
  • Lower bound
  • + *
  • Higher bound
  • + *
+ */ + public static class Parametric implements ParametricUnivariateFunction { + /** + * Computes the value of the logit at {@code x}. + * + * @param x Value for which the function must be computed. + * @param param Values of lower bound and higher bounds. + * @return the value of the function. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + public double value(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + return Logit.value(x, param[0], param[1]); + } + + /** + * Computes the value of the gradient at {@code x}. + * The components of the gradient vector are the partial + * derivatives of the function with respect to each of the + * parameters (lower bound and higher bound). + * + * @param x Value at which the gradient must be computed. + * @param param Values for lower and higher bounds. + * @return the gradient vector at {@code x}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + public double[] gradient(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + + final double lo = param[0]; + final double hi = param[1]; + + return new double[] { 1 / (lo - x), 1 / (hi - x) }; + } + + /** + * Validates parameters to ensure they are appropriate for the evaluation of + * the {@link #value(double,double[])} and {@link #gradient(double,double[])} + * methods. + * + * @param param Values for lower and higher bounds. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + private void validateParameters(double[] param) + throws NullArgumentException, + DimensionMismatchException { + if (param == null) { + throw new NullArgumentException(); + } + if (param.length != 2) { + throw new DimensionMismatchException(param.length, 2); + } + } + } + + /** + * @param x Value at which to compute the logit. + * @param lo Lower bound. + * @param hi Higher bound. + * @return the value of the logit function at {@code x}. + * @throws OutOfRangeException if {@code x < lo} or {@code x > hi}. + */ + private static double value(double x, + double lo, + double hi) + throws OutOfRangeException { + if (x < lo || x > hi) { + throw new OutOfRangeException(x, lo, hi); + } + return FastMath.log((x - lo) / (hi - x)); + } + + /** {@inheritDoc} + * @since 3.1 + * @exception OutOfRangeException if parameter is outside of function domain + */ + public DerivativeStructure value(final DerivativeStructure t) + throws OutOfRangeException { + final double x = t.getValue(); + if (x < lo || x > hi) { + throw new OutOfRangeException(x, lo, hi); + } + double[] f = new double[t.getOrder() + 1]; + + // function value + f[0] = FastMath.log((x - lo) / (hi - x)); + + if (Double.isInfinite(f[0])) { + + if (f.length > 1) { + f[1] = Double.POSITIVE_INFINITY; + } + // fill the array with infinities + // (for x close to lo the signs will flip between -inf and +inf, + // for x close to hi the signs will always be +inf) + // this is probably overkill, since the call to compose at the end + // of the method will transform most infinities into NaN ... + for (int i = 2; i < f.length; ++i) { + f[i] = f[i - 2]; + } + + } else { + + // function derivatives + final double invL = 1.0 / (x - lo); + double xL = invL; + final double invH = 1.0 / (hi - x); + double xH = invH; + for (int i = 1; i < f.length; ++i) { + f[i] = xL + xH; + xL *= -i * invL; + xH *= i * invH; + } + } + + return t.compose(f); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Max.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Max.java new file mode 100644 index 000000000..75f8e037a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Max.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Maximum function. + * + * @since 3.0 + */ +public class Max implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return FastMath.max(x, y); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Min.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Min.java new file mode 100644 index 000000000..56f01a751 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Min.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Minimum function. + * + * @since 3.0 + */ +public class Min implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return FastMath.min(x, y); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Minus.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Minus.java new file mode 100644 index 000000000..f9f787111 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Minus.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; + +/** + * Minus function. + * + * @since 3.0 + */ +public class Minus implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return -x; + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Constant(-1); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.negate(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Multiply.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Multiply.java new file mode 100644 index 000000000..f303e1c6a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Multiply.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; + +/** + * Multiply the two operands. + * + * @since 3.0 + */ +public class Multiply implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return x * y; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Pow.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Pow.java new file mode 100644 index 000000000..bdb4ea2fa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Pow.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Power function. + * + * @since 3.0 + */ +public class Pow implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return FastMath.pow(x, y); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Power.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Power.java new file mode 100644 index 000000000..5e2e32bdc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Power.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Power function. + * + * @since 3.0 + */ +public class Power implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Power. */ + private final double p; + + /** + * @param p Power. + */ + public Power(double p) { + this.p = p; + } + + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.pow(x, p); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.pow(p); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Rint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Rint.java new file mode 100644 index 000000000..82fb38842 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Rint.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * {@code rint} function. + * + * @since 3.0 + */ +public class Rint implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.rint(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sigmoid.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sigmoid.java new file mode 100644 index 000000000..7ed022a88 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sigmoid.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * + * Sigmoid function. + * It is the inverse of the {@link Logit logit} function. + * A more flexible version, the generalised logistic, is implemented + * by the {@link Logistic} class. + * + * @since 3.0 + */ +public class Sigmoid implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** Lower asymptote. */ + private final double lo; + /** Higher asymptote. */ + private final double hi; + + /** + * Usual sigmoid function, where the lower asymptote is 0 and the higher + * asymptote is 1. + */ + public Sigmoid() { + this(0, 1); + } + + /** + * Sigmoid function. + * + * @param lo Lower asymptote. + * @param hi Higher asymptote. + */ + public Sigmoid(double lo, + double hi) { + this.lo = lo; + this.hi = hi; + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} */ + public double value(double x) { + return value(x, lo, hi); + } + + /** + * Parametric function where the input array contains the parameters of + * the {@link Sigmoid#Sigmoid(double,double) sigmoid function}, ordered + * as follows: + *
    + *
  • Lower asymptote
  • + *
  • Higher asymptote
  • + *
+ */ + public static class Parametric implements ParametricUnivariateFunction { + /** + * Computes the value of the sigmoid at {@code x}. + * + * @param x Value for which the function must be computed. + * @param param Values of lower asymptote and higher asymptote. + * @return the value of the function. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + public double value(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + return Sigmoid.value(x, param[0], param[1]); + } + + /** + * Computes the value of the gradient at {@code x}. + * The components of the gradient vector are the partial + * derivatives of the function with respect to each of the + * parameters (lower asymptote and higher asymptote). + * + * @param x Value at which the gradient must be computed. + * @param param Values for lower asymptote and higher asymptote. + * @return the gradient vector at {@code x}. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + public double[] gradient(double x, double ... param) + throws NullArgumentException, + DimensionMismatchException { + validateParameters(param); + + final double invExp1 = 1 / (1 + FastMath.exp(-x)); + + return new double[] { 1 - invExp1, invExp1 }; + } + + /** + * Validates parameters to ensure they are appropriate for the evaluation of + * the {@link #value(double,double[])} and {@link #gradient(double,double[])} + * methods. + * + * @param param Values for lower and higher asymptotes. + * @throws NullArgumentException if {@code param} is {@code null}. + * @throws DimensionMismatchException if the size of {@code param} is + * not 2. + */ + private void validateParameters(double[] param) + throws NullArgumentException, + DimensionMismatchException { + if (param == null) { + throw new NullArgumentException(); + } + if (param.length != 2) { + throw new DimensionMismatchException(param.length, 2); + } + } + } + + /** + * @param x Value at which to compute the sigmoid. + * @param lo Lower asymptote. + * @param hi Higher asymptote. + * @return the value of the sigmoid function at {@code x}. + */ + private static double value(double x, + double lo, + double hi) { + return lo + (hi - lo) / (1 + FastMath.exp(-x)); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) + throws DimensionMismatchException { + + double[] f = new double[t.getOrder() + 1]; + final double exp = FastMath.exp(-t.getValue()); + if (Double.isInfinite(exp)) { + + // special handling near lower boundary, to avoid NaN + f[0] = lo; + Arrays.fill(f, 1, f.length, 0.0); + + } else { + + // the nth order derivative of sigmoid has the form: + // dn(sigmoid(x)/dxn = P_n(exp(-x)) / (1+exp(-x))^(n+1) + // where P_n(t) is a degree n polynomial with normalized higher term + // P_0(t) = 1, P_1(t) = t, P_2(t) = t^2 - t, P_3(t) = t^3 - 4 t^2 + t... + // the general recurrence relation for P_n is: + // P_n(x) = n t P_(n-1)(t) - t (1 + t) P_(n-1)'(t) + final double[] p = new double[f.length]; + + final double inv = 1 / (1 + exp); + double coeff = hi - lo; + for (int n = 0; n < f.length; ++n) { + + // update and evaluate polynomial P_n(t) + double v = 0; + p[n] = 1; + for (int k = n; k >= 0; --k) { + v = v * exp + p[k]; + if (k > 1) { + p[k - 1] = (n - k + 2) * p[k - 2] - (k - 1) * p[k - 1]; + } else { + p[0] = 0; + } + } + + coeff *= inv; + f[n] = coeff * v; + + } + + // fix function value + f[0] += lo; + + } + + return t.compose(f); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Signum.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Signum.java new file mode 100644 index 000000000..13ba918a1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Signum.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * {@code signum} function. + * + * @since 3.0 + */ +public class Signum implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.signum(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sin.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sin.java new file mode 100644 index 000000000..af1c780e2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sin.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Sine function. + * + * @since 3.0 + */ +public class Sin implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.sin(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Cos(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.sin(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinc.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinc.java new file mode 100644 index 000000000..809817251 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinc.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Sinc function, + * defined by + *

+ *   sinc(x) = 1            if x = 0,
+ *             sin(x) / x   otherwise.
+ * 
+ * + * @since 3.0 + */ +public class Sinc implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** + * Value below which the computations are done using Taylor series. + *

+ * The Taylor series for sinc even order derivatives are: + *

+     * d^(2n)sinc/dx^(2n)     = Sum_(k>=0) (-1)^(n+k) / ((2k)!(2n+2k+1)) x^(2k)
+     *                        = (-1)^n     [ 1/(2n+1) - x^2/(4n+6) + x^4/(48n+120) - x^6/(1440n+5040) + O(x^8) ]
+     * 
+ *

+ *

+ * The Taylor series for sinc odd order derivatives are: + *

+     * d^(2n+1)sinc/dx^(2n+1) = Sum_(k>=0) (-1)^(n+k+1) / ((2k+1)!(2n+2k+3)) x^(2k+1)
+     *                        = (-1)^(n+1) [ x/(2n+3) - x^3/(12n+30) + x^5/(240n+840) - x^7/(10080n+45360) + O(x^9) ]
+     * 
+ *

+ *

+ * So the ratio of the fourth term with respect to the first term + * is always smaller than x^6/720, for all derivative orders. + * This implies that neglecting this term and using only the first three terms induces + * a relative error bounded by x^6/720. The SHORTCUT value is chosen such that this + * relative error is below double precision accuracy when |x| <= SHORTCUT. + *

+ */ + private static final double SHORTCUT = 6.0e-3; + /** For normalized sinc function. */ + private final boolean normalized; + + /** + * The sinc function, {@code sin(x) / x}. + */ + public Sinc() { + this(false); + } + + /** + * Instantiates the sinc function. + * + * @param normalized If {@code true}, the function is + * sin(πx) / πx, otherwise {@code sin(x) / x}. + */ + public Sinc(boolean normalized) { + this.normalized = normalized; + } + + /** {@inheritDoc} */ + public double value(final double x) { + final double scaledX = normalized ? FastMath.PI * x : x; + if (FastMath.abs(scaledX) <= SHORTCUT) { + // use Taylor series + final double scaledX2 = scaledX * scaledX; + return ((scaledX2 - 20) * scaledX2 + 120) / 120; + } else { + // use definition expression + return FastMath.sin(scaledX) / scaledX; + } + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) + throws DimensionMismatchException { + + final double scaledX = (normalized ? FastMath.PI : 1) * t.getValue(); + final double scaledX2 = scaledX * scaledX; + + double[] f = new double[t.getOrder() + 1]; + + if (FastMath.abs(scaledX) <= SHORTCUT) { + + for (int i = 0; i < f.length; ++i) { + final int k = i / 2; + if ((i & 0x1) == 0) { + // even derivation order + f[i] = (((k & 0x1) == 0) ? 1 : -1) * + (1.0 / (i + 1) - scaledX2 * (1.0 / (2 * i + 6) - scaledX2 / (24 * i + 120))); + } else { + // odd derivation order + f[i] = (((k & 0x1) == 0) ? -scaledX : scaledX) * + (1.0 / (i + 2) - scaledX2 * (1.0 / (6 * i + 24) - scaledX2 / (120 * i + 720))); + } + } + + } else { + + final double inv = 1 / scaledX; + final double cos = FastMath.cos(scaledX); + final double sin = FastMath.sin(scaledX); + + f[0] = inv * sin; + + // the nth order derivative of sinc has the form: + // dn(sinc(x)/dxn = [S_n(x) sin(x) + C_n(x) cos(x)] / x^(n+1) + // where S_n(x) is an even polynomial with degree n-1 or n (depending on parity) + // and C_n(x) is an odd polynomial with degree n-1 or n (depending on parity) + // S_0(x) = 1, S_1(x) = -1, S_2(x) = -x^2 + 2, S_3(x) = 3x^2 - 6... + // C_0(x) = 0, C_1(x) = x, C_2(x) = -2x, C_3(x) = -x^3 + 6x... + // the general recurrence relations for S_n and C_n are: + // S_n(x) = x S_(n-1)'(x) - n S_(n-1)(x) - x C_(n-1)(x) + // C_n(x) = x C_(n-1)'(x) - n C_(n-1)(x) + x S_(n-1)(x) + // as per polynomials parity, we can store both S_n and C_n in the same array + final double[] sc = new double[f.length]; + sc[0] = 1; + + double coeff = inv; + for (int n = 1; n < f.length; ++n) { + + double s = 0; + double c = 0; + + // update and evaluate polynomials S_n(x) and C_n(x) + final int kStart; + if ((n & 0x1) == 0) { + // even derivation order, S_n is degree n and C_n is degree n-1 + sc[n] = 0; + kStart = n; + } else { + // odd derivation order, S_n is degree n-1 and C_n is degree n + sc[n] = sc[n - 1]; + c = sc[n]; + kStart = n - 1; + } + + // in this loop, k is always even + for (int k = kStart; k > 1; k -= 2) { + + // sine part + sc[k] = (k - n) * sc[k] - sc[k - 1]; + s = s * scaledX2 + sc[k]; + + // cosine part + sc[k - 1] = (k - 1 - n) * sc[k - 1] + sc[k -2]; + c = c * scaledX2 + sc[k - 1]; + + } + sc[0] *= -n; + s = s * scaledX2 + sc[0]; + + coeff *= inv; + f[n] = coeff * (s * sin + c * scaledX * cos); + + } + + } + + if (normalized) { + double scale = FastMath.PI; + for (int i = 1; i < f.length; ++i) { + f[i] *= scale; + scale *= FastMath.PI; + } + } + + return t.compose(f); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinh.java new file mode 100644 index 000000000..ce2055937 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sinh.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic sine function. + * + * @since 3.0 + */ +public class Sinh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.sinh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public DifferentiableUnivariateFunction derivative() { + return new Cosh(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.sinh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sqrt.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sqrt.java new file mode 100644 index 000000000..61f2435f3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Sqrt.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Square-root function. + * + * @since 3.0 + */ +public class Sqrt implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.sqrt(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.sqrt(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/StepFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/StepFunction.java new file mode 100644 index 000000000..a28b5041f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/StepFunction.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * + * Step function. + * + * @since 3.0 + */ +public class StepFunction implements UnivariateFunction { + /** Abscissae. */ + private final double[] abscissa; + /** Ordinates. */ + private final double[] ordinate; + + /** + * Builds a step function from a list of arguments and the corresponding + * values. Specifically, returns the function h(x) defined by

+     * h(x) = y[0] for all x < x[1]
+     *        y[1] for x[1] ≤ x < x[2]
+     *        ...
+     *        y[y.length - 1] for x ≥ x[x.length - 1]
+     * 
+ * The value of {@code x[0]} is ignored, but it must be strictly less than + * {@code x[1]}. + * + * @param x Domain values where the function changes value. + * @param y Values of the function. + * @throws NonMonotonicSequenceException + * if the {@code x} array is not sorted in strictly increasing order. + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + * @throws DimensionMismatchException if {@code x} and {@code y} do not + * have the same length. + */ + public StepFunction(double[] x, + double[] y) + throws NullArgumentException, NoDataException, + DimensionMismatchException, NonMonotonicSequenceException { + if (x == null || + y == null) { + throw new NullArgumentException(); + } + if (x.length == 0 || + y.length == 0) { + throw new NoDataException(); + } + if (y.length != x.length) { + throw new DimensionMismatchException(y.length, x.length); + } + MathArrays.checkOrder(x); + + abscissa = MathArrays.copyOf(x); + ordinate = MathArrays.copyOf(y); + } + + /** {@inheritDoc} */ + public double value(double x) { + int index = Arrays.binarySearch(abscissa, x); + double fx = 0; + + if (index < -1) { + // "x" is between "abscissa[-index-2]" and "abscissa[-index-1]". + fx = ordinate[-index-2]; + } else if (index >= 0) { + // "x" is exactly "abscissa[index]". + fx = ordinate[index]; + } else { + // Otherwise, "x" is smaller than the first value in "abscissa" + // (hence the returned value should be "ordinate[0]"). + fx = ordinate[0]; + } + + return fx; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Subtract.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Subtract.java new file mode 100644 index 000000000..3db8214ab --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Subtract.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; + +/** + * Subtract the second operand from the first. + * + * @since 3.0 + */ +public class Subtract implements BivariateFunction { + /** {@inheritDoc} */ + public double value(double x, double y) { + return x - y; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tan.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tan.java new file mode 100644 index 000000000..69d0f4530 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tan.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Tangent function. + * + * @since 3.0 + */ +public class Tan implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.tan(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.tan(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tanh.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tanh.java new file mode 100644 index 000000000..d0c8c3878 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Tanh.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Hyperbolic tangent function. + * + * @since 3.0 + */ +public class Tanh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.tanh(x); + } + + /** {@inheritDoc} + * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)} + */ + @Deprecated + public UnivariateFunction derivative() { + return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative(); + } + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + return t.tanh(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ulp.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ulp.java new file mode 100644 index 000000000..e0382eaf6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/Ulp.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.function; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * {@code ulp} function. + * + * @since 3.0 + */ +public class Ulp implements UnivariateFunction { + /** {@inheritDoc} */ + public double value(double x) { + return FastMath.ulp(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/package-info.java new file mode 100644 index 000000000..4884bb550 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/function/package-info.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

+ * The {@code function} package contains function objects that wrap the + * methods contained in {@link java.lang.Math}, as well as common + * mathematical functions such as the gaussian and sinc functions. + *

+ * + */ +package com.fr.third.org.apache.commons.math3.analysis.function; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.java new file mode 100644 index 000000000..5bc1991a1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.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 com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Provide a default implementation for several generic functions. + * + * @since 1.2 + */ +public abstract class BaseAbstractUnivariateIntegrator implements UnivariateIntegrator { + + /** Default absolute accuracy. */ + public static final double DEFAULT_ABSOLUTE_ACCURACY = 1.0e-15; + + /** Default relative accuracy. */ + public static final double DEFAULT_RELATIVE_ACCURACY = 1.0e-6; + + /** Default minimal iteration count. */ + public static final int DEFAULT_MIN_ITERATIONS_COUNT = 3; + + /** Default maximal iteration count. */ + public static final int DEFAULT_MAX_ITERATIONS_COUNT = Integer.MAX_VALUE; + + /** The iteration count. + * @deprecated as of 3.6, this field has been replaced with {@link #incrementCount()} + */ + @Deprecated + protected Incrementor iterations; + + /** The iteration count. */ + private IntegerSequence.Incrementor count; + + /** Maximum absolute error. */ + private final double absoluteAccuracy; + + /** Maximum relative error. */ + private final double relativeAccuracy; + + /** minimum number of iterations */ + private final int minimalIterationCount; + + /** The functions evaluation count. */ + private IntegerSequence.Incrementor evaluations; + + /** Function to integrate. */ + private UnivariateFunction function; + + /** Lower bound for the interval. */ + private double min; + + /** Upper bound for the interval. */ + private double max; + + /** + * Construct an integrator with given accuracies and iteration counts. + *

+ * The meanings of the various parameters are: + *

    + *
  • relative accuracy: + * this is used to stop iterations if the absolute accuracy can't be + * achieved due to large values or short mantissa length. If this + * should be the primary criterion for convergence rather then a + * safety measure, set the absolute accuracy to a ridiculously small value, + * like {@link Precision#SAFE_MIN Precision.SAFE_MIN}.
  • + *
  • absolute accuracy: + * The default is usually chosen so that results in the interval + * -10..-0.1 and +0.1..+10 can be found with a reasonable accuracy. If the + * expected absolute value of your results is of much smaller magnitude, set + * this to a smaller value.
  • + *
  • minimum number of iterations: + * minimal iteration is needed to avoid false early convergence, e.g. + * the sample points happen to be zeroes of the function. Users can + * use the default value or choose one that they see as appropriate.
  • + *
  • maximum number of iterations: + * usually a high iteration count indicates convergence problems. However, + * the "reasonable value" varies widely for different algorithms. Users are + * advised to use the default value supplied by the algorithm.
  • + *
+ * + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + */ + protected BaseAbstractUnivariateIntegrator(final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException { + + // accuracy settings + this.relativeAccuracy = relativeAccuracy; + this.absoluteAccuracy = absoluteAccuracy; + + // iterations count settings + if (minimalIterationCount <= 0) { + throw new NotStrictlyPositiveException(minimalIterationCount); + } + if (maximalIterationCount <= minimalIterationCount) { + throw new NumberIsTooSmallException(maximalIterationCount, minimalIterationCount, false); + } + this.minimalIterationCount = minimalIterationCount; + this.count = IntegerSequence.Incrementor.create().withMaximalCount(maximalIterationCount); + + @SuppressWarnings("deprecation") + Incrementor wrapped = + Incrementor.wrap(count); + this.iterations = wrapped; + + // prepare evaluations counter, but do not set it yet + evaluations = IntegerSequence.Incrementor.create(); + + } + + /** + * Construct an integrator with given accuracies. + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + */ + protected BaseAbstractUnivariateIntegrator(final double relativeAccuracy, + final double absoluteAccuracy) { + this(relativeAccuracy, absoluteAccuracy, + DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT); + } + + /** + * Construct an integrator with given iteration counts. + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + */ + protected BaseAbstractUnivariateIntegrator(final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException { + this(DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY, + minimalIterationCount, maximalIterationCount); + } + + /** {@inheritDoc} */ + public double getRelativeAccuracy() { + return relativeAccuracy; + } + + /** {@inheritDoc} */ + public double getAbsoluteAccuracy() { + return absoluteAccuracy; + } + + /** {@inheritDoc} */ + public int getMinimalIterationCount() { + return minimalIterationCount; + } + + /** {@inheritDoc} */ + public int getMaximalIterationCount() { + return count.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** {@inheritDoc} */ + public int getIterations() { + return count.getCount(); + } + + /** Increment the number of iterations. + * @exception MaxCountExceededException if the number of iterations + * exceeds the allowed maximum number + */ + protected void incrementCount() throws MaxCountExceededException { + count.increment(); + } + + /** + * @return the lower bound. + */ + protected double getMin() { + return min; + } + /** + * @return the upper bound. + */ + protected double getMax() { + return max; + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at specified point. + * @throws TooManyEvaluationsException if the maximal number of function + * evaluations is exceeded. + */ + protected double computeObjectiveValue(final double point) + throws TooManyEvaluationsException { + try { + evaluations.increment(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return function.value(point); + } + + /** + * Prepare for computation. + * Subclasses must call this method if they override any of the + * {@code solve} methods. + * + * @param maxEval Maximum number of evaluations. + * @param f the integrand function + * @param lower the min bound for the interval + * @param upper the upper bound for the interval + * @throws NullArgumentException if {@code f} is {@code null}. + * @throws MathIllegalArgumentException if {@code min >= max}. + */ + protected void setup(final int maxEval, + final UnivariateFunction f, + final double lower, final double upper) + throws NullArgumentException, MathIllegalArgumentException { + + // Checks. + MathUtils.checkNotNull(f); + UnivariateSolverUtils.verifyInterval(lower, upper); + + // Reset. + min = lower; + max = upper; + function = f; + evaluations = evaluations.withMaximalCount(maxEval).withStart(0); + count = count.withStart(0); + + } + + /** {@inheritDoc} */ + public double integrate(final int maxEval, final UnivariateFunction f, + final double lower, final double upper) + throws TooManyEvaluationsException, MaxCountExceededException, + MathIllegalArgumentException, NullArgumentException { + + // Initialization. + setup(maxEval, f, lower, upper); + + // Perform computation. + return doIntegrate(); + + } + + /** + * Method for implementing actual integration algorithms in derived + * classes. + * + * @return the root. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + * @throws MaxCountExceededException if the maximum iteration count is exceeded + * or the integrator detects convergence problems otherwise + */ + protected abstract double doIntegrate() + throws TooManyEvaluationsException, MaxCountExceededException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java new file mode 100644 index 000000000..c434a0745 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.integration.gauss.GaussIntegratorFactory; +import com.fr.third.org.apache.commons.math3.analysis.integration.gauss.GaussIntegrator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This algorithm divides the integration interval into equally-sized + * sub-interval and on each of them performs a + * + * Legendre-Gauss quadrature. + * Because of its non-adaptive nature, this algorithm can + * converge to a wrong value for the integral (for example, if the + * function is significantly different from zero toward the ends of the + * integration interval). + * In particular, a change of variables aimed at estimating integrals + * over infinite intervals as proposed + * + * here should be avoided when using this class. + * + * @since 3.1 + */ + +public class IterativeLegendreGaussIntegrator + extends BaseAbstractUnivariateIntegrator { + /** Factory that computes the points and weights. */ + private static final GaussIntegratorFactory FACTORY + = new GaussIntegratorFactory(); + /** Number of integration points (per interval). */ + private final int numberOfPoints; + + /** + * Builds an integrator with given accuracies and iterations counts. + * + * @param n Number of integration points. + * @param relativeAccuracy Relative accuracy of the result. + * @param absoluteAccuracy Absolute accuracy of the result. + * @param minimalIterationCount Minimum number of iterations. + * @param maximalIterationCount Maximum number of iterations. + * @throws NotStrictlyPositiveException if minimal number of iterations + * or number of points are not strictly positive. + * @throws NumberIsTooSmallException if maximal number of iterations + * is smaller than or equal to the minimal number of iterations. + */ + public IterativeLegendreGaussIntegrator(final int n, + final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + if (n <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_POINTS, n); + } + numberOfPoints = n; + } + + /** + * Builds an integrator with given accuracies. + * + * @param n Number of integration points. + * @param relativeAccuracy Relative accuracy of the result. + * @param absoluteAccuracy Absolute accuracy of the result. + * @throws NotStrictlyPositiveException if {@code n < 1}. + */ + public IterativeLegendreGaussIntegrator(final int n, + final double relativeAccuracy, + final double absoluteAccuracy) + throws NotStrictlyPositiveException { + this(n, relativeAccuracy, absoluteAccuracy, + DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT); + } + + /** + * Builds an integrator with given iteration counts. + * + * @param n Number of integration points. + * @param minimalIterationCount Minimum number of iterations. + * @param maximalIterationCount Maximum number of iterations. + * @throws NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive. + * @throws NumberIsTooSmallException if maximal number of iterations + * is smaller than or equal to the minimal number of iterations. + * @throws NotStrictlyPositiveException if {@code n < 1}. + */ + public IterativeLegendreGaussIntegrator(final int n, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException { + this(n, DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY, + minimalIterationCount, maximalIterationCount); + } + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException { + // Compute first estimate with a single step. + double oldt = stage(1); + + int n = 2; + while (true) { + // Improve integral with a larger number of steps. + final double t = stage(n); + + // Estimate the error. + final double delta = FastMath.abs(t - oldt); + final double limit = + FastMath.max(getAbsoluteAccuracy(), + getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5); + + // check convergence + if (getIterations() + 1 >= getMinimalIterationCount() && + delta <= limit) { + return t; + } + + // Prepare next iteration. + final double ratio = FastMath.min(4, FastMath.pow(delta / limit, 0.5 / numberOfPoints)); + n = FastMath.max((int) (ratio * n), n + 1); + oldt = t; + incrementCount(); + } + } + + /** + * Compute the n-th stage integral. + * + * @param n Number of steps. + * @return the value of n-th stage integral. + * @throws TooManyEvaluationsException if the maximum number of evaluations + * is exceeded. + */ + private double stage(final int n) + throws TooManyEvaluationsException { + // Function to be integrated is stored in the base class. + final UnivariateFunction f = new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double x) + throws MathIllegalArgumentException, TooManyEvaluationsException { + return computeObjectiveValue(x); + } + }; + + final double min = getMin(); + final double max = getMax(); + final double step = (max - min) / n; + + double sum = 0; + for (int i = 0; i < n; i++) { + // Integrate over each sub-interval [a, b]. + final double a = min + i * step; + final double b = a + step; + final GaussIntegrator g = FACTORY.legendreHighPrecision(numberOfPoints, a, b); + sum += g.integrate(f); + } + + return sum; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java new file mode 100644 index 000000000..0f4f52fdd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Legendre-Gauss quadrature formula. + *

+ * Legendre-Gauss integrators are efficient integrators that can + * accurately integrate functions with few function evaluations. A + * Legendre-Gauss integrator using an n-points quadrature formula can + * integrate 2n-1 degree polynomials exactly. + *

+ *

+ * 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. + *

+ *

+ * The evaluation abscissas xi are the roots of the degree n + * Legendre polynomial. The weights ai of the quadrature formula + * integrals from -1 to +1 ∫ Li2 where Li (x) = + * ∏ (x-xk)/(xi-xk) for k != i. + *

+ *

+ * @since 1.2 + * @deprecated As of 3.1 (to be removed in 4.0). Please use + * {@link IterativeLegendreGaussIntegrator} instead. + */ +@Deprecated +public class LegendreGaussIntegrator extends BaseAbstractUnivariateIntegrator { + + /** Abscissas for the 2 points method. */ + private static final double[] ABSCISSAS_2 = { + -1.0 / FastMath.sqrt(3.0), + 1.0 / FastMath.sqrt(3.0) + }; + + /** Weights for the 2 points method. */ + private static final double[] WEIGHTS_2 = { + 1.0, + 1.0 + }; + + /** Abscissas for the 3 points method. */ + private static final double[] ABSCISSAS_3 = { + -FastMath.sqrt(0.6), + 0.0, + FastMath.sqrt(0.6) + }; + + /** Weights for the 3 points method. */ + private static final double[] WEIGHTS_3 = { + 5.0 / 9.0, + 8.0 / 9.0, + 5.0 / 9.0 + }; + + /** Abscissas for the 4 points method. */ + private static final double[] ABSCISSAS_4 = { + -FastMath.sqrt((15.0 + 2.0 * FastMath.sqrt(30.0)) / 35.0), + -FastMath.sqrt((15.0 - 2.0 * FastMath.sqrt(30.0)) / 35.0), + FastMath.sqrt((15.0 - 2.0 * FastMath.sqrt(30.0)) / 35.0), + FastMath.sqrt((15.0 + 2.0 * FastMath.sqrt(30.0)) / 35.0) + }; + + /** Weights for the 4 points method. */ + private static final double[] WEIGHTS_4 = { + (90.0 - 5.0 * FastMath.sqrt(30.0)) / 180.0, + (90.0 + 5.0 * FastMath.sqrt(30.0)) / 180.0, + (90.0 + 5.0 * FastMath.sqrt(30.0)) / 180.0, + (90.0 - 5.0 * FastMath.sqrt(30.0)) / 180.0 + }; + + /** Abscissas for the 5 points method. */ + private static final double[] ABSCISSAS_5 = { + -FastMath.sqrt((35.0 + 2.0 * FastMath.sqrt(70.0)) / 63.0), + -FastMath.sqrt((35.0 - 2.0 * FastMath.sqrt(70.0)) / 63.0), + 0.0, + FastMath.sqrt((35.0 - 2.0 * FastMath.sqrt(70.0)) / 63.0), + FastMath.sqrt((35.0 + 2.0 * FastMath.sqrt(70.0)) / 63.0) + }; + + /** Weights for the 5 points method. */ + private static final double[] WEIGHTS_5 = { + (322.0 - 13.0 * FastMath.sqrt(70.0)) / 900.0, + (322.0 + 13.0 * FastMath.sqrt(70.0)) / 900.0, + 128.0 / 225.0, + (322.0 + 13.0 * FastMath.sqrt(70.0)) / 900.0, + (322.0 - 13.0 * FastMath.sqrt(70.0)) / 900.0 + }; + + /** Abscissas for the current method. */ + private final double[] abscissas; + + /** Weights for the current method. */ + private final double[] weights; + + /** + * Build a Legendre-Gauss integrator with given accuracies and iterations counts. + * @param n number of points desired (must be between 2 and 5 inclusive) + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * @exception MathIllegalArgumentException if number of points is out of [2; 5] + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + */ + public LegendreGaussIntegrator(final int n, + final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws MathIllegalArgumentException, NotStrictlyPositiveException, NumberIsTooSmallException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + switch(n) { + case 2 : + abscissas = ABSCISSAS_2; + weights = WEIGHTS_2; + break; + case 3 : + abscissas = ABSCISSAS_3; + weights = WEIGHTS_3; + break; + case 4 : + abscissas = ABSCISSAS_4; + weights = WEIGHTS_4; + break; + case 5 : + abscissas = ABSCISSAS_5; + weights = WEIGHTS_5; + break; + default : + throw new MathIllegalArgumentException( + LocalizedFormats.N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED, + n, 2, 5); + } + + } + + /** + * Build a Legendre-Gauss integrator with given accuracies. + * @param n number of points desired (must be between 2 and 5 inclusive) + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @exception MathIllegalArgumentException if number of points is out of [2; 5] + */ + public LegendreGaussIntegrator(final int n, + final double relativeAccuracy, + final double absoluteAccuracy) + throws MathIllegalArgumentException { + this(n, relativeAccuracy, absoluteAccuracy, + DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT); + } + + /** + * Build a Legendre-Gauss integrator with given iteration counts. + * @param n number of points desired (must be between 2 and 5 inclusive) + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * @exception MathIllegalArgumentException if number of points is out of [2; 5] + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + */ + public LegendreGaussIntegrator(final int n, + final int minimalIterationCount, + final int maximalIterationCount) + throws MathIllegalArgumentException { + this(n, DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY, + minimalIterationCount, maximalIterationCount); + } + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException { + + // compute first estimate with a single step + double oldt = stage(1); + + int n = 2; + while (true) { + + // improve integral with a larger number of steps + final double t = stage(n); + + // estimate error + final double delta = FastMath.abs(t - oldt); + final double limit = + FastMath.max(getAbsoluteAccuracy(), + getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5); + + // check convergence + if ((getIterations() + 1 >= getMinimalIterationCount()) && (delta <= limit)) { + return t; + } + + // prepare next iteration + double ratio = FastMath.min(4, FastMath.pow(delta / limit, 0.5 / abscissas.length)); + n = FastMath.max((int) (ratio * n), n + 1); + oldt = t; + incrementCount(); + + } + + } + + /** + * Compute the n-th stage integral. + * @param n number of steps + * @return the value of n-th stage integral + * @throws TooManyEvaluationsException if the maximum number of evaluations + * is exceeded. + */ + private double stage(final int n) + throws TooManyEvaluationsException { + + // set up the step for the current stage + final double step = (getMax() - getMin()) / n; + final double halfStep = step / 2.0; + + // integrate over all elementary steps + double midPoint = getMin() + halfStep; + double sum = 0.0; + for (int i = 0; i < n; ++i) { + for (int j = 0; j < abscissas.length; ++j) { + sum += weights[j] * computeObjectiveValue(midPoint + halfStep * abscissas[j]); + } + midPoint += step; + } + + return halfStep * sum; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java new file mode 100644 index 000000000..556802788 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Midpoint Rule for integration of real univariate functions. For + * reference, see Numerical Mathematics, ISBN 0387989595, + * chapter 9.2. + *

+ * The function should be integrable.

+ * + * @since 3.3 + */ +public class MidPointIntegrator extends BaseAbstractUnivariateIntegrator { + + /** Maximum number of iterations for midpoint. */ + public static final int MIDPOINT_MAX_ITERATIONS_COUNT = 64; + + /** + * Build a midpoint integrator with given accuracies and iterations counts. + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #MIDPOINT_MAX_ITERATIONS_COUNT} + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #MIDPOINT_MAX_ITERATIONS_COUNT} + */ + public MidPointIntegrator(final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > MIDPOINT_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + MIDPOINT_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Build a midpoint integrator with given iteration counts. + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #MIDPOINT_MAX_ITERATIONS_COUNT} + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #MIDPOINT_MAX_ITERATIONS_COUNT} + */ + public MidPointIntegrator(final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > MIDPOINT_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + MIDPOINT_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Construct a midpoint integrator with default settings. + * (max iteration count set to {@link #MIDPOINT_MAX_ITERATIONS_COUNT}) + */ + public MidPointIntegrator() { + super(DEFAULT_MIN_ITERATIONS_COUNT, MIDPOINT_MAX_ITERATIONS_COUNT); + } + + /** + * Compute the n-th stage integral of midpoint rule. + * This function should only be called by API integrate() in the package. + * To save time it does not verify arguments - caller does. + *

+ * The interval is divided equally into 2^n sections rather than an + * arbitrary m sections because this configuration can best utilize the + * already computed values.

+ * + * @param n the stage of 1/2 refinement. Must be larger than 0. + * @param previousStageResult Result from the previous call to the + * {@code stage} method. + * @param min Lower bound of the integration interval. + * @param diffMaxMin Difference between the lower bound and upper bound + * of the integration interval. + * @return the value of n-th stage integral + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + */ + private double stage(final int n, + double previousStageResult, + double min, + double diffMaxMin) + throws TooManyEvaluationsException { + + // number of new points in this stage + final long np = 1L << (n - 1); + double sum = 0; + + // spacing between adjacent new points + final double spacing = diffMaxMin / np; + + // the first new point + double x = min + 0.5 * spacing; + for (long i = 0; i < np; i++) { + sum += computeObjectiveValue(x); + x += spacing; + } + // add the new sum to previously calculated result + return 0.5 * (previousStageResult + sum * spacing); + } + + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException { + + final double min = getMin(); + final double diff = getMax() - min; + final double midPoint = min + 0.5 * diff; + + double oldt = diff * computeObjectiveValue(midPoint); + + while (true) { + incrementCount(); + final int i = getIterations(); + final double t = stage(i, oldt, min, diff); + if (i >= getMinimalIterationCount()) { + final double delta = FastMath.abs(t - oldt); + final double rLimit = + getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5; + if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) { + return t; + } + } + oldt = t; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/RombergIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/RombergIntegrator.java new file mode 100644 index 000000000..c6f0c57a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/RombergIntegrator.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Romberg Algorithm for integration of real univariate functions. For + * reference, see Introduction to Numerical Analysis, ISBN 038795452X, + * chapter 3. + *

+ * 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.

+ * + * @since 1.2 + */ +public class RombergIntegrator extends BaseAbstractUnivariateIntegrator { + + /** Maximal number of iterations for Romberg. */ + public static final int ROMBERG_MAX_ITERATIONS_COUNT = 32; + + /** + * Build a Romberg integrator with given accuracies and iterations counts. + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #ROMBERG_MAX_ITERATIONS_COUNT}) + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #ROMBERG_MAX_ITERATIONS_COUNT} + */ + public RombergIntegrator(final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > ROMBERG_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + ROMBERG_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Build a Romberg integrator with given iteration counts. + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #ROMBERG_MAX_ITERATIONS_COUNT}) + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #ROMBERG_MAX_ITERATIONS_COUNT} + */ + public RombergIntegrator(final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > ROMBERG_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + ROMBERG_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Construct a Romberg integrator with default settings + * (max iteration count set to {@link #ROMBERG_MAX_ITERATIONS_COUNT}) + */ + public RombergIntegrator() { + super(DEFAULT_MIN_ITERATIONS_COUNT, ROMBERG_MAX_ITERATIONS_COUNT); + } + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws TooManyEvaluationsException, MaxCountExceededException { + + final int m = getMaximalIterationCount() + 1; + double previousRow[] = new double[m]; + double currentRow[] = new double[m]; + + TrapezoidIntegrator qtrap = new TrapezoidIntegrator(); + currentRow[0] = qtrap.stage(this, 0); + incrementCount(); + double olds = currentRow[0]; + while (true) { + + final int i = getIterations(); + + // switch rows + final double[] tmpRow = previousRow; + previousRow = currentRow; + currentRow = tmpRow; + + currentRow[0] = qtrap.stage(this, i); + incrementCount(); + for (int j = 1; j <= i; j++) { + // Richardson extrapolation coefficient + final double r = (1L << (2 * j)) - 1; + final double tIJm1 = currentRow[j - 1]; + currentRow[j] = tIJm1 + (tIJm1 - previousRow[j - 1]) / r; + } + final double s = currentRow[i]; + if (i >= getMinimalIterationCount()) { + final double delta = FastMath.abs(s - olds); + final double rLimit = getRelativeAccuracy() * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5; + if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) { + return s; + } + } + olds = s; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java new file mode 100644 index 000000000..4f9905f50 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements + * Simpson's Rule for integration of real univariate functions. For + * reference, see Introduction to Numerical Analysis, ISBN 038795452X, + * chapter 3. + *

+ * This implementation employs the basic trapezoid rule to calculate Simpson's + * rule.

+ * + * @since 1.2 + */ +public class SimpsonIntegrator extends BaseAbstractUnivariateIntegrator { + + /** Maximal number of iterations for Simpson. */ + public static final int SIMPSON_MAX_ITERATIONS_COUNT = 64; + + /** + * Build a Simpson integrator with given accuracies and iterations counts. + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #SIMPSON_MAX_ITERATIONS_COUNT}) + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #SIMPSON_MAX_ITERATIONS_COUNT} + */ + public SimpsonIntegrator(final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > SIMPSON_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + SIMPSON_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Build a Simpson integrator with given iteration counts. + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #SIMPSON_MAX_ITERATIONS_COUNT}) + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #SIMPSON_MAX_ITERATIONS_COUNT} + */ + public SimpsonIntegrator(final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > SIMPSON_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + SIMPSON_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Construct an integrator with default settings. + * (max iteration count set to {@link #SIMPSON_MAX_ITERATIONS_COUNT}) + */ + public SimpsonIntegrator() { + super(DEFAULT_MIN_ITERATIONS_COUNT, SIMPSON_MAX_ITERATIONS_COUNT); + } + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws TooManyEvaluationsException, MaxCountExceededException { + + TrapezoidIntegrator qtrap = new TrapezoidIntegrator(); + if (getMinimalIterationCount() == 1) { + return (4 * qtrap.stage(this, 1) - qtrap.stage(this, 0)) / 3.0; + } + + // Simpson's rule requires at least two trapezoid stages. + double olds = 0; + double oldt = qtrap.stage(this, 0); + while (true) { + final double t = qtrap.stage(this, getIterations()); + incrementCount(); + final double s = (4 * t - oldt) / 3.0; + if (getIterations() >= getMinimalIterationCount()) { + final double delta = FastMath.abs(s - olds); + final double rLimit = + getRelativeAccuracy() * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5; + if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) { + return s; + } + } + olds = s; + oldt = t; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java new file mode 100644 index 000000000..a35a7b370 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Trapezoid Rule for integration of real univariate functions. For + * reference, see Introduction to Numerical Analysis, ISBN 038795452X, + * chapter 3. + *

+ * The function should be integrable.

+ * + * @since 1.2 + */ +public class TrapezoidIntegrator extends BaseAbstractUnivariateIntegrator { + + /** Maximum number of iterations for trapezoid. */ + public static final int TRAPEZOID_MAX_ITERATIONS_COUNT = 64; + + /** Intermediate result. */ + private double s; + + /** + * Build a trapezoid integrator with given accuracies and iterations counts. + * @param relativeAccuracy relative accuracy of the result + * @param absoluteAccuracy absolute accuracy of the result + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT} + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #TRAPEZOID_MAX_ITERATIONS_COUNT} + */ + public TrapezoidIntegrator(final double relativeAccuracy, + final double absoluteAccuracy, + final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > TRAPEZOID_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + TRAPEZOID_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Build a trapezoid integrator with given iteration counts. + * @param minimalIterationCount minimum number of iterations + * @param maximalIterationCount maximum number of iterations + * (must be less than or equal to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT} + * @exception NotStrictlyPositiveException if minimal number of iterations + * is not strictly positive + * @exception NumberIsTooSmallException if maximal number of iterations + * is lesser than or equal to the minimal number of iterations + * @exception NumberIsTooLargeException if maximal number of iterations + * is greater than {@link #TRAPEZOID_MAX_ITERATIONS_COUNT} + */ + public TrapezoidIntegrator(final int minimalIterationCount, + final int maximalIterationCount) + throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException { + super(minimalIterationCount, maximalIterationCount); + if (maximalIterationCount > TRAPEZOID_MAX_ITERATIONS_COUNT) { + throw new NumberIsTooLargeException(maximalIterationCount, + TRAPEZOID_MAX_ITERATIONS_COUNT, false); + } + } + + /** + * Construct a trapezoid integrator with default settings. + * (max iteration count set to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT}) + */ + public TrapezoidIntegrator() { + super(DEFAULT_MIN_ITERATIONS_COUNT, TRAPEZOID_MAX_ITERATIONS_COUNT); + } + + /** + * Compute the n-th stage integral of trapezoid rule. This function + * should only be called by API integrate() in the package. + * To save time it does not verify arguments - caller does. + *

+ * The interval is divided equally into 2^n sections rather than an + * arbitrary m sections because this configuration can best utilize the + * already computed values.

+ * + * @param baseIntegrator integrator holding integration parameters + * @param n the stage of 1/2 refinement, n = 0 is no refinement + * @return the value of n-th stage integral + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + */ + double stage(final BaseAbstractUnivariateIntegrator baseIntegrator, final int n) + throws TooManyEvaluationsException { + + if (n == 0) { + final double max = baseIntegrator.getMax(); + final double min = baseIntegrator.getMin(); + s = 0.5 * (max - min) * + (baseIntegrator.computeObjectiveValue(min) + + baseIntegrator.computeObjectiveValue(max)); + return s; + } else { + final long np = 1L << (n-1); // number of new points in this stage + double sum = 0; + final double max = baseIntegrator.getMax(); + final double min = baseIntegrator.getMin(); + // spacing between adjacent new points + final double spacing = (max - min) / np; + double x = min + 0.5 * spacing; // the first new point + for (long i = 0; i < np; i++) { + sum += baseIntegrator.computeObjectiveValue(x); + x += spacing; + } + // add the new sum to previously calculated result + s = 0.5 * (s + sum * spacing); + return s; + } + } + + /** {@inheritDoc} */ + @Override + protected double doIntegrate() + throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException { + + double oldt = stage(this, 0); + incrementCount(); + while (true) { + final int i = getIterations(); + final double t = stage(this, i); + if (i >= getMinimalIterationCount()) { + final double delta = FastMath.abs(t - oldt); + final double rLimit = + getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5; + if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) { + return t; + } + } + oldt = t; + incrementCount(); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java new file mode 100644 index 000000000..46b12cc2e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Interface for univariate real integration algorithms. + * + * @since 1.2 + */ +public interface UnivariateIntegrator { + + /** + * Get the relative accuracy. + * + * @return the accuracy + */ + double getRelativeAccuracy(); + + /** + * Get the absolute accuracy. + * + * @return the accuracy + */ + double getAbsoluteAccuracy(); + + /** + * Get the min limit for the number of iterations. + * + * @return the actual min limit + */ + int getMinimalIterationCount(); + + /** + * Get the upper limit for the number of iterations. + * + * @return the actual upper limit + */ + int getMaximalIterationCount(); + + /** + * Integrate the function in the given interval. + * + * @param maxEval Maximum number of evaluations. + * @param f the integrand function + * @param min the lower bound for the interval + * @param max the upper bound for the interval + * @return the value of integral + * @throws TooManyEvaluationsException if the maximum number of function + * evaluations is exceeded + * @throws MaxCountExceededException if the maximum iteration count is exceeded + * or the integrator detects convergence problems otherwise + * @throws MathIllegalArgumentException if {@code min > max} or the endpoints do not + * satisfy the requirements specified by the integrator + * @throws NullArgumentException if {@code f} is {@code null}. + */ + double integrate(int maxEval, UnivariateFunction f, double min, + double max) + throws TooManyEvaluationsException, MaxCountExceededException, + MathIllegalArgumentException, NullArgumentException; + + /** + * Get the number of function evaluations of the last run of the integrator. + * + * @return number of function evaluations + */ + int getEvaluations(); + + /** + * Get the number of iterations of the last run of the integrator. + * + * @return number of iterations + */ + int getIterations(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.java new file mode 100644 index 000000000..54d240fbd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.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 com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import java.util.Map; +import java.util.TreeMap; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Base class for rules that determines the integration nodes and their + * weights. + * Subclasses must implement the {@link #computeRule(int) computeRule} method. + * + * @param Type of the number used to represent the points and weights of + * the quadrature rules. + * + * @since 3.1 + */ +public abstract class BaseRuleFactory { + /** List of points and weights, indexed by the order of the rule. */ + private final Map> pointsAndWeights + = new TreeMap>(); + /** Cache for double-precision rules. */ + private final Map> pointsAndWeightsDouble + = new TreeMap>(); + + /** + * Gets a copy of the quadrature rule with the given number of integration + * points. + * + * @param numberOfPoints Number of integration points. + * @return a copy of the integration rule. + * @throws NotStrictlyPositiveException if {@code numberOfPoints < 1}. + * @throws DimensionMismatchException if the elements of the rule pair do not + * have the same length. + */ + public Pair getRule(int numberOfPoints) + throws NotStrictlyPositiveException, DimensionMismatchException { + + if (numberOfPoints <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_POINTS, + numberOfPoints); + } + + // Try to obtain the rule from the cache. + Pair cached = pointsAndWeightsDouble.get(numberOfPoints); + + if (cached == null) { + // Rule not computed yet. + + // Compute the rule. + final Pair rule = getRuleInternal(numberOfPoints); + cached = convertToDouble(rule); + + // Cache it. + pointsAndWeightsDouble.put(numberOfPoints, cached); + } + + // Return a copy. + return new Pair(cached.getFirst().clone(), + cached.getSecond().clone()); + } + + /** + * Gets a rule. + * Synchronization ensures that rules will be computed and added to the + * cache at most once. + * The returned rule is a reference into the cache. + * + * @param numberOfPoints Order of the rule to be retrieved. + * @return the points and weights corresponding to the given order. + * @throws DimensionMismatchException if the elements of the rule pair do not + * have the same length. + */ + protected synchronized Pair getRuleInternal(int numberOfPoints) + throws DimensionMismatchException { + final Pair rule = pointsAndWeights.get(numberOfPoints); + if (rule == null) { + addRule(computeRule(numberOfPoints)); + // The rule should be available now. + return getRuleInternal(numberOfPoints); + } + return rule; + } + + /** + * Stores a rule. + * + * @param rule Rule to be stored. + * @throws DimensionMismatchException if the elements of the pair do not + * have the same length. + */ + protected void addRule(Pair rule) throws DimensionMismatchException { + if (rule.getFirst().length != rule.getSecond().length) { + throw new DimensionMismatchException(rule.getFirst().length, + rule.getSecond().length); + } + + pointsAndWeights.put(rule.getFirst().length, rule); + } + + /** + * Computes the rule for the given order. + * + * @param numberOfPoints Order of the rule to be computed. + * @return the computed rule. + * @throws DimensionMismatchException if the elements of the pair do not + * have the same length. + */ + protected abstract Pair computeRule(int numberOfPoints) + throws DimensionMismatchException; + + /** + * Converts the from the actual {@code Number} type to {@code double} + * + * @param Type of the number used to represent the points and + * weights of the quadrature rules. + * @param rule Points and weights. + * @return points and weights as {@code double}s. + */ + private static Pair convertToDouble(Pair rule) { + final T[] pT = rule.getFirst(); + final T[] wT = rule.getSecond(); + + final int len = pT.length; + final double[] pD = new double[len]; + final double[] wD = new double[len]; + + for (int i = 0; i < len; i++) { + pD[i] = pT[i].doubleValue(); + wD[i] = wT[i].doubleValue(); + } + + return new Pair(pD, wD); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java new file mode 100644 index 000000000..bdde3b57f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Class that implements the Gaussian rule for + * {@link #integrate(UnivariateFunction) integrating} a weighted + * function. + * + * @since 3.1 + */ +public class GaussIntegrator { + /** Nodes. */ + private final double[] points; + /** Nodes weights. */ + private final double[] weights; + + /** + * Creates an integrator from the given {@code points} and {@code weights}. + * The integration interval is defined by the first and last value of + * {@code points} which must be sorted in increasing order. + * + * @param points Integration points. + * @param weights Weights of the corresponding integration nodes. + * @throws NonMonotonicSequenceException if the {@code points} are not + * sorted in increasing order. + * @throws DimensionMismatchException if points and weights don't have the same length + */ + public GaussIntegrator(double[] points, + double[] weights) + throws NonMonotonicSequenceException, DimensionMismatchException { + if (points.length != weights.length) { + throw new DimensionMismatchException(points.length, + weights.length); + } + + MathArrays.checkOrder(points, MathArrays.OrderDirection.INCREASING, true, true); + + this.points = points.clone(); + this.weights = weights.clone(); + } + + /** + * Creates an integrator from the given pair of points (first element of + * the pair) and weights (second element of the pair. + * + * @param pointsAndWeights Integration points and corresponding weights. + * @throws NonMonotonicSequenceException if the {@code points} are not + * sorted in increasing order. + * + * @see #GaussIntegrator(double[], double[]) + */ + public GaussIntegrator(Pair pointsAndWeights) + throws NonMonotonicSequenceException { + this(pointsAndWeights.getFirst(), pointsAndWeights.getSecond()); + } + + /** + * Returns an estimate of the integral of {@code f(x) * w(x)}, + * where {@code w} is a weight function that depends on the actual + * flavor of the Gauss integration scheme. + * The algorithm uses the points and associated weights, as passed + * to the {@link #GaussIntegrator(double[],double[]) constructor}. + * + * @param f Function to integrate. + * @return the integral of the weighted function. + */ + public double integrate(UnivariateFunction f) { + double s = 0; + double c = 0; + for (int i = 0; i < points.length; i++) { + final double x = points[i]; + final double w = weights[i]; + final double y = w * f.value(x) - c; + final double t = s + y; + c = (t - s) - y; + s = t; + } + return s; + } + + /** + * @return the order of the integration rule (the number of integration + * points). + */ + public int getNumberOfPoints() { + return points.length; + } + + /** + * Gets the integration point at the given index. + * The index must be in the valid range but no check is performed. + * @param index index of the integration point + * @return the integration point. + */ + public double getPoint(int index) { + return points[index]; + } + + /** + * Gets the weight of the integration point at the given index. + * The index must be in the valid range but no check is performed. + * @param index index of the integration point + * @return the weight. + */ + public double getWeight(int index) { + return weights[index]; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java new file mode 100644 index 000000000..e03717c14 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import java.math.BigDecimal; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Class that provides different ways to compute the nodes and weights to be + * used by the {@link GaussIntegrator Gaussian integration rule}. + * + * @since 3.1 + */ +public class GaussIntegratorFactory { + /** Generator of Gauss-Legendre integrators. */ + private final BaseRuleFactory legendre = new LegendreRuleFactory(); + /** Generator of Gauss-Legendre integrators. */ + private final BaseRuleFactory legendreHighPrecision = new LegendreHighPrecisionRuleFactory(); + /** Generator of Gauss-Hermite integrators. */ + private final BaseRuleFactory hermite = new HermiteRuleFactory(); + + /** + * Creates a Gauss-Legendre integrator of the given order. + * The call to the + * {@link GaussIntegrator#integrate(UnivariateFunction) + * integrate} method will perform an integration on the natural interval + * {@code [-1 , 1]}. + * + * @param numberOfPoints Order of the integration rule. + * @return a Gauss-Legendre integrator. + */ + public GaussIntegrator legendre(int numberOfPoints) { + return new GaussIntegrator(getRule(legendre, numberOfPoints)); + } + + /** + * Creates a Gauss-Legendre integrator of the given order. + * The call to the + * {@link GaussIntegrator#integrate(UnivariateFunction) + * integrate} method will perform an integration on the given interval. + * + * @param numberOfPoints Order of the integration rule. + * @param lowerBound Lower bound of the integration interval. + * @param upperBound Upper bound of the integration interval. + * @return a Gauss-Legendre integrator. + * @throws NotStrictlyPositiveException if number of points is not positive + */ + public GaussIntegrator legendre(int numberOfPoints, + double lowerBound, + double upperBound) + throws NotStrictlyPositiveException { + return new GaussIntegrator(transform(getRule(legendre, numberOfPoints), + lowerBound, upperBound)); + } + + /** + * Creates a Gauss-Legendre integrator of the given order. + * The call to the + * {@link GaussIntegrator#integrate(UnivariateFunction) + * integrate} method will perform an integration on the natural interval + * {@code [-1 , 1]}. + * + * @param numberOfPoints Order of the integration rule. + * @return a Gauss-Legendre integrator. + * @throws NotStrictlyPositiveException if number of points is not positive + */ + public GaussIntegrator legendreHighPrecision(int numberOfPoints) + throws NotStrictlyPositiveException { + return new GaussIntegrator(getRule(legendreHighPrecision, numberOfPoints)); + } + + /** + * Creates an integrator of the given order, and whose call to the + * {@link GaussIntegrator#integrate(UnivariateFunction) + * integrate} method will perform an integration on the given interval. + * + * @param numberOfPoints Order of the integration rule. + * @param lowerBound Lower bound of the integration interval. + * @param upperBound Upper bound of the integration interval. + * @return a Gauss-Legendre integrator. + * @throws NotStrictlyPositiveException if number of points is not positive + */ + public GaussIntegrator legendreHighPrecision(int numberOfPoints, + double lowerBound, + double upperBound) + throws NotStrictlyPositiveException { + return new GaussIntegrator(transform(getRule(legendreHighPrecision, numberOfPoints), + lowerBound, upperBound)); + } + + /** + * Creates a Gauss-Hermite integrator of the given order. + * The call to the + * {@link SymmetricGaussIntegrator#integrate(UnivariateFunction) + * integrate} method will perform a weighted integration on the interval + * \([-\infty, +\infty]\): the computed value is the improper integral of + * \(e^{-x^2}f(x)\) + * where \(f(x)\) is the function passed to the + * {@link SymmetricGaussIntegrator#integrate(UnivariateFunction) + * integrate} method. + * + * @param numberOfPoints Order of the integration rule. + * @return a Gauss-Hermite integrator. + */ + public SymmetricGaussIntegrator hermite(int numberOfPoints) { + return new SymmetricGaussIntegrator(getRule(hermite, numberOfPoints)); + } + + /** + * @param factory Integration rule factory. + * @param numberOfPoints Order of the integration rule. + * @return the integration nodes and weights. + * @throws NotStrictlyPositiveException if number of points is not positive + * @throws DimensionMismatchException if the elements of the rule pair do not + * have the same length. + */ + private static Pair getRule(BaseRuleFactory factory, + int numberOfPoints) + throws NotStrictlyPositiveException, DimensionMismatchException { + return factory.getRule(numberOfPoints); + } + + /** + * Performs a change of variable so that the integration can be performed + * on an arbitrary interval {@code [a, b]}. + * It is assumed that the natural interval is {@code [-1, 1]}. + * + * @param rule Original points and weights. + * @param a Lower bound of the integration interval. + * @param b Lower bound of the integration interval. + * @return the points and weights adapted to the new interval. + */ + private static Pair transform(Pair rule, + double a, + double b) { + final double[] points = rule.getFirst(); + final double[] weights = rule.getSecond(); + + // Scaling + final double scale = (b - a) / 2; + final double shift = a + scale; + + for (int i = 0; i < points.length; i++) { + points[i] = points[i] * scale + shift; + weights[i] *= scale; + } + + return new Pair(points, weights); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java new file mode 100644 index 000000000..6838e0b67 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.Pair; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Factory that creates a + * + * Gauss-type quadrature rule using Hermite polynomials + * of the first kind. + * Such a quadrature rule allows the calculation of improper integrals + * of a function + *

+ * \(f(x) e^{-x^2}\) + *

+ * Recurrence relation and weights computation follow + * + * Abramowitz and Stegun, 1964. + *

+ * The coefficients of the standard Hermite polynomials grow very rapidly. + * In order to avoid overflows, each Hermite polynomial is normalized with + * respect to the underlying scalar product. + * The initial interval for the application of the bisection method is + * based on the roots of the previous Hermite polynomial (interlacing). + * Upper and lower bounds of these roots are provided by

+ *
+ * I. Krasikov, + * Nonnegative quadratic forms and bounds on orthogonal polynomials, + * Journal of Approximation theory 111, 31-49 + *
+ * + * @since 3.3 + */ +public class HermiteRuleFactory extends BaseRuleFactory { + /** π1/2 */ + private static final double SQRT_PI = 1.77245385090551602729; + /** π-1/4 */ + private static final double H0 = 7.5112554446494248286e-1; + /** π-1/4 √2 */ + private static final double H1 = 1.0622519320271969145; + + /** {@inheritDoc} */ + @Override + protected Pair computeRule(int numberOfPoints) + throws DimensionMismatchException { + + if (numberOfPoints == 1) { + // Break recursion. + return new Pair(new Double[] { 0d }, + new Double[] { SQRT_PI }); + } + + // Get previous rule. + // If it has not been computed yet it will trigger a recursive call + // to this method. + final int lastNumPoints = numberOfPoints - 1; + final Double[] previousPoints = getRuleInternal(lastNumPoints).getFirst(); + + // Compute next rule. + final Double[] points = new Double[numberOfPoints]; + final Double[] weights = new Double[numberOfPoints]; + + final double sqrtTwoTimesLastNumPoints = FastMath.sqrt(2 * lastNumPoints); + final double sqrtTwoTimesNumPoints = FastMath.sqrt(2 * numberOfPoints); + + // Find i-th root of H[n+1] by bracketing. + final int iMax = numberOfPoints / 2; + for (int i = 0; i < iMax; i++) { + // Lower-bound of the interval. + double a = (i == 0) ? -sqrtTwoTimesLastNumPoints : previousPoints[i - 1].doubleValue(); + // Upper-bound of the interval. + double b = (iMax == 1) ? -0.5 : previousPoints[i].doubleValue(); + + // H[j-1](a) + double hma = H0; + // H[j](a) + double ha = H1 * a; + // H[j-1](b) + double hmb = H0; + // H[j](b) + double hb = H1 * b; + for (int j = 1; j < numberOfPoints; j++) { + // Compute H[j+1](a) and H[j+1](b) + final double jp1 = j + 1; + final double s = FastMath.sqrt(2 / jp1); + final double sm = FastMath.sqrt(j / jp1); + final double hpa = s * a * ha - sm * hma; + final double hpb = s * b * hb - sm * hmb; + hma = ha; + ha = hpa; + hmb = hb; + hb = hpb; + } + + // Now ha = H[n+1](a), and hma = H[n](a) (same holds for b). + // Middle of the interval. + double c = 0.5 * (a + b); + // P[j-1](c) + double hmc = H0; + // P[j](c) + double hc = H1 * c; + boolean done = false; + while (!done) { + done = b - a <= Math.ulp(c); + hmc = H0; + hc = H1 * c; + for (int j = 1; j < numberOfPoints; j++) { + // Compute H[j+1](c) + final double jp1 = j + 1; + final double s = FastMath.sqrt(2 / jp1); + final double sm = FastMath.sqrt(j / jp1); + final double hpc = s * c * hc - sm * hmc; + hmc = hc; + hc = hpc; + } + // Now h = H[n+1](c) and hm = H[n](c). + if (!done) { + if (ha * hc < 0) { + b = c; + hmb = hmc; + hb = hc; + } else { + a = c; + hma = hmc; + ha = hc; + } + c = 0.5 * (a + b); + } + } + final double d = sqrtTwoTimesNumPoints * hmc; + final double w = 2 / (d * d); + + points[i] = c; + weights[i] = w; + + final int idx = lastNumPoints - i; + points[idx] = -c; + weights[idx] = w; + } + + // If "numberOfPoints" is odd, 0 is a root. + // Note: as written, the test for oddness will work for negative + // integers too (although it is not necessary here), preventing + // a FindBugs warning. + if (numberOfPoints % 2 != 0) { + double hm = H0; + for (int j = 1; j < numberOfPoints; j += 2) { + final double jp1 = j + 1; + hm = -FastMath.sqrt(j / jp1) * hm; + } + final double d = sqrtTwoTimesNumPoints * hm; + final double w = 2 / (d * d); + + points[iMax] = 0d; + weights[iMax] = w; + } + + return new Pair(points, weights); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java new file mode 100644 index 000000000..a751c76d5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import java.math.BigDecimal; +import java.math.MathContext; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Factory that creates Gauss-type quadrature rule using Legendre polynomials. + * In this implementation, the lower and upper bounds of the natural interval + * of integration are -1 and 1, respectively. + * The Legendre polynomials are evaluated using the recurrence relation + * presented in + * Abramowitz and Stegun, 1964. + * + * @since 3.1 + */ +public class LegendreHighPrecisionRuleFactory extends BaseRuleFactory { + /** Settings for enhanced precision computations. */ + private final MathContext mContext; + /** The number {@code 2}. */ + private final BigDecimal two; + /** The number {@code -1}. */ + private final BigDecimal minusOne; + /** The number {@code 0.5}. */ + private final BigDecimal oneHalf; + + /** + * Default precision is {@link MathContext#DECIMAL128 DECIMAL128}. + */ + public LegendreHighPrecisionRuleFactory() { + this(MathContext.DECIMAL128); + } + + /** + * @param mContext Precision setting for computing the quadrature rules. + */ + public LegendreHighPrecisionRuleFactory(MathContext mContext) { + this.mContext = mContext; + two = new BigDecimal("2", mContext); + minusOne = new BigDecimal("-1", mContext); + oneHalf = new BigDecimal("0.5", mContext); + } + + /** {@inheritDoc} */ + @Override + protected Pair computeRule(int numberOfPoints) + throws DimensionMismatchException { + + if (numberOfPoints == 1) { + // Break recursion. + return new Pair(new BigDecimal[] { BigDecimal.ZERO }, + new BigDecimal[] { two }); + } + + // Get previous rule. + // If it has not been computed yet it will trigger a recursive call + // to this method. + final BigDecimal[] previousPoints = getRuleInternal(numberOfPoints - 1).getFirst(); + + // Compute next rule. + final BigDecimal[] points = new BigDecimal[numberOfPoints]; + final BigDecimal[] weights = new BigDecimal[numberOfPoints]; + + // Find i-th root of P[n+1] by bracketing. + final int iMax = numberOfPoints / 2; + for (int i = 0; i < iMax; i++) { + // Lower-bound of the interval. + BigDecimal a = (i == 0) ? minusOne : previousPoints[i - 1]; + // Upper-bound of the interval. + BigDecimal b = (iMax == 1) ? BigDecimal.ONE : previousPoints[i]; + // P[j-1](a) + BigDecimal pma = BigDecimal.ONE; + // P[j](a) + BigDecimal pa = a; + // P[j-1](b) + BigDecimal pmb = BigDecimal.ONE; + // P[j](b) + BigDecimal pb = b; + for (int j = 1; j < numberOfPoints; j++) { + final BigDecimal b_two_j_p_1 = new BigDecimal(2 * j + 1, mContext); + final BigDecimal b_j = new BigDecimal(j, mContext); + final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext); + + // Compute P[j+1](a) + // ppa = ((2 * j + 1) * a * pa - j * pma) / (j + 1); + + BigDecimal tmp1 = a.multiply(b_two_j_p_1, mContext); + tmp1 = pa.multiply(tmp1, mContext); + BigDecimal tmp2 = pma.multiply(b_j, mContext); + // P[j+1](a) + BigDecimal ppa = tmp1.subtract(tmp2, mContext); + ppa = ppa.divide(b_j_p_1, mContext); + + // Compute P[j+1](b) + // ppb = ((2 * j + 1) * b * pb - j * pmb) / (j + 1); + + tmp1 = b.multiply(b_two_j_p_1, mContext); + tmp1 = pb.multiply(tmp1, mContext); + tmp2 = pmb.multiply(b_j, mContext); + // P[j+1](b) + BigDecimal ppb = tmp1.subtract(tmp2, mContext); + ppb = ppb.divide(b_j_p_1, mContext); + + pma = pa; + pa = ppa; + pmb = pb; + pb = ppb; + } + // Now pa = P[n+1](a), and pma = P[n](a). Same holds for b. + // Middle of the interval. + BigDecimal c = a.add(b, mContext).multiply(oneHalf, mContext); + // P[j-1](c) + BigDecimal pmc = BigDecimal.ONE; + // P[j](c) + BigDecimal pc = c; + boolean done = false; + while (!done) { + BigDecimal tmp1 = b.subtract(a, mContext); + BigDecimal tmp2 = c.ulp().multiply(BigDecimal.TEN, mContext); + done = tmp1.compareTo(tmp2) <= 0; + pmc = BigDecimal.ONE; + pc = c; + for (int j = 1; j < numberOfPoints; j++) { + final BigDecimal b_two_j_p_1 = new BigDecimal(2 * j + 1, mContext); + final BigDecimal b_j = new BigDecimal(j, mContext); + final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext); + + // Compute P[j+1](c) + tmp1 = c.multiply(b_two_j_p_1, mContext); + tmp1 = pc.multiply(tmp1, mContext); + tmp2 = pmc.multiply(b_j, mContext); + // P[j+1](c) + BigDecimal ppc = tmp1.subtract(tmp2, mContext); + ppc = ppc.divide(b_j_p_1, mContext); + + pmc = pc; + pc = ppc; + } + // Now pc = P[n+1](c) and pmc = P[n](c). + if (!done) { + if (pa.signum() * pc.signum() <= 0) { + b = c; + pmb = pmc; + pb = pc; + } else { + a = c; + pma = pmc; + pa = pc; + } + c = a.add(b, mContext).multiply(oneHalf, mContext); + } + } + final BigDecimal nP = new BigDecimal(numberOfPoints, mContext); + BigDecimal tmp1 = pmc.subtract(c.multiply(pc, mContext), mContext); + tmp1 = tmp1.multiply(nP); + tmp1 = tmp1.pow(2, mContext); + BigDecimal tmp2 = c.pow(2, mContext); + tmp2 = BigDecimal.ONE.subtract(tmp2, mContext); + tmp2 = tmp2.multiply(two, mContext); + tmp2 = tmp2.divide(tmp1, mContext); + + points[i] = c; + weights[i] = tmp2; + + final int idx = numberOfPoints - i - 1; + points[idx] = c.negate(mContext); + weights[idx] = tmp2; + } + // If "numberOfPoints" is odd, 0 is a root. + // Note: as written, the test for oddness will work for negative + // integers too (although it is not necessary here), preventing + // a FindBugs warning. + if (numberOfPoints % 2 != 0) { + BigDecimal pmc = BigDecimal.ONE; + for (int j = 1; j < numberOfPoints; j += 2) { + final BigDecimal b_j = new BigDecimal(j, mContext); + final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext); + + // pmc = -j * pmc / (j + 1); + pmc = pmc.multiply(b_j, mContext); + pmc = pmc.divide(b_j_p_1, mContext); + pmc = pmc.negate(mContext); + } + + // 2 / pow(numberOfPoints * pmc, 2); + final BigDecimal nP = new BigDecimal(numberOfPoints, mContext); + BigDecimal tmp1 = pmc.multiply(nP, mContext); + tmp1 = tmp1.pow(2, mContext); + BigDecimal tmp2 = two.divide(tmp1, mContext); + + points[iMax] = BigDecimal.ZERO; + weights[iMax] = tmp2; + } + + return new Pair(points, weights); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java new file mode 100644 index 000000000..c5f988393 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Factory that creates Gauss-type quadrature rule using Legendre polynomials. + * In this implementation, the lower and upper bounds of the natural interval + * of integration are -1 and 1, respectively. + * The Legendre polynomials are evaluated using the recurrence relation + * presented in + * Abramowitz and Stegun, 1964. + * + * @since 3.1 + */ +public class LegendreRuleFactory extends BaseRuleFactory { + /** {@inheritDoc} */ + @Override + protected Pair computeRule(int numberOfPoints) + throws DimensionMismatchException { + + if (numberOfPoints == 1) { + // Break recursion. + return new Pair(new Double[] { 0d }, + new Double[] { 2d }); + } + + // Get previous rule. + // If it has not been computed yet it will trigger a recursive call + // to this method. + final Double[] previousPoints = getRuleInternal(numberOfPoints - 1).getFirst(); + + // Compute next rule. + final Double[] points = new Double[numberOfPoints]; + final Double[] weights = new Double[numberOfPoints]; + + // Find i-th root of P[n+1] by bracketing. + final int iMax = numberOfPoints / 2; + for (int i = 0; i < iMax; i++) { + // Lower-bound of the interval. + double a = (i == 0) ? -1 : previousPoints[i - 1].doubleValue(); + // Upper-bound of the interval. + double b = (iMax == 1) ? 1 : previousPoints[i].doubleValue(); + // P[j-1](a) + double pma = 1; + // P[j](a) + double pa = a; + // P[j-1](b) + double pmb = 1; + // P[j](b) + double pb = b; + for (int j = 1; j < numberOfPoints; j++) { + final int two_j_p_1 = 2 * j + 1; + final int j_p_1 = j + 1; + // P[j+1](a) + final double ppa = (two_j_p_1 * a * pa - j * pma) / j_p_1; + // P[j+1](b) + final double ppb = (two_j_p_1 * b * pb - j * pmb) / j_p_1; + pma = pa; + pa = ppa; + pmb = pb; + pb = ppb; + } + // Now pa = P[n+1](a), and pma = P[n](a) (same holds for b). + // Middle of the interval. + double c = 0.5 * (a + b); + // P[j-1](c) + double pmc = 1; + // P[j](c) + double pc = c; + boolean done = false; + while (!done) { + done = b - a <= Math.ulp(c); + pmc = 1; + pc = c; + for (int j = 1; j < numberOfPoints; j++) { + // P[j+1](c) + final double ppc = ((2 * j + 1) * c * pc - j * pmc) / (j + 1); + pmc = pc; + pc = ppc; + } + // Now pc = P[n+1](c) and pmc = P[n](c). + if (!done) { + if (pa * pc <= 0) { + b = c; + pmb = pmc; + pb = pc; + } else { + a = c; + pma = pmc; + pa = pc; + } + c = 0.5 * (a + b); + } + } + final double d = numberOfPoints * (pmc - c * pc); + final double w = 2 * (1 - c * c) / (d * d); + + points[i] = c; + weights[i] = w; + + final int idx = numberOfPoints - i - 1; + points[idx] = -c; + weights[idx] = w; + } + // If "numberOfPoints" is odd, 0 is a root. + // Note: as written, the test for oddness will work for negative + // integers too (although it is not necessary here), preventing + // a FindBugs warning. + if (numberOfPoints % 2 != 0) { + double pmc = 1; + for (int j = 1; j < numberOfPoints; j += 2) { + pmc = -j * pmc / (j + 1); + } + final double d = numberOfPoints * pmc; + final double w = 2 / (d * d); + + points[iMax] = 0d; + weights[iMax] = w; + } + + return new Pair(points, weights); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java new file mode 100644 index 000000000..95fd49484 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * This class's implements {@link #integrate(UnivariateFunction) integrate} + * method assuming that the integral is symmetric about 0. + * This allows to reduce numerical errors. + * + * @since 3.3 + */ +public class SymmetricGaussIntegrator extends GaussIntegrator { + /** + * Creates an integrator from the given {@code points} and {@code weights}. + * The integration interval is defined by the first and last value of + * {@code points} which must be sorted in increasing order. + * + * @param points Integration points. + * @param weights Weights of the corresponding integration nodes. + * @throws NonMonotonicSequenceException if the {@code points} are not + * sorted in increasing order. + * @throws DimensionMismatchException if points and weights don't have the same length + */ + public SymmetricGaussIntegrator(double[] points, + double[] weights) + throws NonMonotonicSequenceException, DimensionMismatchException { + super(points, weights); + } + + /** + * Creates an integrator from the given pair of points (first element of + * the pair) and weights (second element of the pair. + * + * @param pointsAndWeights Integration points and corresponding weights. + * @throws NonMonotonicSequenceException if the {@code points} are not + * sorted in increasing order. + * + * @see #SymmetricGaussIntegrator(double[], double[]) + */ + public SymmetricGaussIntegrator(Pair pointsAndWeights) + throws NonMonotonicSequenceException { + this(pointsAndWeights.getFirst(), pointsAndWeights.getSecond()); + } + + /** + * {@inheritDoc} + */ + @Override + public double integrate(UnivariateFunction f) { + final int ruleLength = getNumberOfPoints(); + + if (ruleLength == 1) { + return getWeight(0) * f.value(0d); + } + + final int iMax = ruleLength / 2; + double s = 0; + double c = 0; + for (int i = 0; i < iMax; i++) { + final double p = getPoint(i); + final double w = getWeight(i); + + final double f1 = f.value(p); + final double f2 = f.value(-p); + + final double y = w * (f1 + f2) - c; + final double t = s + y; + + c = (t - s) - y; + s = t; + } + + if (ruleLength % 2 != 0) { + final double w = getWeight(iMax); + + final double y = w * f.value(0d) - c; + final double t = s + y; + + s = t; + } + + return s; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/package-info.java new file mode 100644 index 000000000..fc977af70 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/gauss/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Gauss family of quadrature schemes. + * + */ +package com.fr.third.org.apache.commons.math3.analysis.integration.gauss; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/package-info.java new file mode 100644 index 000000000..3b96cf1f3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/integration/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Numerical integration (quadrature) algorithms for univariate real functions. + * + */ +package com.fr.third.org.apache.commons.math3.analysis.integration; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java new file mode 100644 index 000000000..bab23a1d8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Computes a cubic spline interpolation for the data set using the Akima + * algorithm, as originally formulated by Hiroshi Akima in his 1970 paper + * "A New Method of Interpolation and Smooth Curve Fitting Based on Local Procedures." + * J. ACM 17, 4 (October 1970), 589-602. DOI=10.1145/321607.321609 + * http://doi.acm.org/10.1145/321607.321609 + *

+ * This implementation is based on the Akima implementation in the CubicSpline + * class in the Math.NET Numerics library. The method referenced is + * CubicSpline.InterpolateAkimaSorted + *

+ *

+ * The {@link #interpolate(double[], double[]) interpolate} method returns a + * {@link PolynomialSplineFunction} consisting of n cubic polynomials, defined + * over the subintervals determined by the x values, {@code x[0] < x[i] ... < x[n]}. + * The Akima algorithm requires that {@code n >= 5}. + *

+ */ +public class AkimaSplineInterpolator + implements UnivariateInterpolator { + /** The minimum number of points that are needed to compute the function. */ + private static final int MINIMUM_NUMBER_POINTS = 5; + + /** + * Computes an interpolating function for the data set. + * + * @param xvals the arguments for the interpolation points + * @param yvals the values for the interpolation points + * @return a function which interpolates the data set + * @throws DimensionMismatchException if {@code xvals} and {@code yvals} have + * different sizes. + * @throws NonMonotonicSequenceException if {@code xvals} is not sorted in + * strict increasing order. + * @throws NumberIsTooSmallException if the size of {@code xvals} is smaller + * than 5. + */ + public PolynomialSplineFunction interpolate(double[] xvals, + double[] yvals) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + if (xvals == null || + yvals == null) { + throw new NullArgumentException(); + } + + if (xvals.length != yvals.length) { + throw new DimensionMismatchException(xvals.length, yvals.length); + } + + if (xvals.length < MINIMUM_NUMBER_POINTS) { + throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS, + xvals.length, + MINIMUM_NUMBER_POINTS, true); + } + + MathArrays.checkOrder(xvals); + + final int numberOfDiffAndWeightElements = xvals.length - 1; + + final double[] differences = new double[numberOfDiffAndWeightElements]; + final double[] weights = new double[numberOfDiffAndWeightElements]; + + for (int i = 0; i < differences.length; i++) { + differences[i] = (yvals[i + 1] - yvals[i]) / (xvals[i + 1] - xvals[i]); + } + + for (int i = 1; i < weights.length; i++) { + weights[i] = FastMath.abs(differences[i] - differences[i - 1]); + } + + // Prepare Hermite interpolation scheme. + final double[] firstDerivatives = new double[xvals.length]; + + for (int i = 2; i < firstDerivatives.length - 2; i++) { + final double wP = weights[i + 1]; + final double wM = weights[i - 1]; + if (Precision.equals(wP, 0.0) && + Precision.equals(wM, 0.0)) { + final double xv = xvals[i]; + final double xvP = xvals[i + 1]; + final double xvM = xvals[i - 1]; + firstDerivatives[i] = (((xvP - xv) * differences[i - 1]) + ((xv - xvM) * differences[i])) / (xvP - xvM); + } else { + firstDerivatives[i] = ((wP * differences[i - 1]) + (wM * differences[i])) / (wP + wM); + } + } + + firstDerivatives[0] = differentiateThreePoint(xvals, yvals, 0, 0, 1, 2); + firstDerivatives[1] = differentiateThreePoint(xvals, yvals, 1, 0, 1, 2); + firstDerivatives[xvals.length - 2] = differentiateThreePoint(xvals, yvals, xvals.length - 2, + xvals.length - 3, xvals.length - 2, + xvals.length - 1); + firstDerivatives[xvals.length - 1] = differentiateThreePoint(xvals, yvals, xvals.length - 1, + xvals.length - 3, xvals.length - 2, + xvals.length - 1); + + return interpolateHermiteSorted(xvals, yvals, firstDerivatives); + } + + /** + * Three point differentiation helper, modeled off of the same method in the + * Math.NET CubicSpline class. This is used by both the Apache Math and the + * Math.NET Akima Cubic Spline algorithms + * + * @param xvals x values to calculate the numerical derivative with + * @param yvals y values to calculate the numerical derivative with + * @param indexOfDifferentiation index of the elemnt we are calculating the derivative around + * @param indexOfFirstSample index of the first element to sample for the three point method + * @param indexOfSecondsample index of the second element to sample for the three point method + * @param indexOfThirdSample index of the third element to sample for the three point method + * @return the derivative + */ + private double differentiateThreePoint(double[] xvals, double[] yvals, + int indexOfDifferentiation, + int indexOfFirstSample, + int indexOfSecondsample, + int indexOfThirdSample) { + final double x0 = yvals[indexOfFirstSample]; + final double x1 = yvals[indexOfSecondsample]; + final double x2 = yvals[indexOfThirdSample]; + + final double t = xvals[indexOfDifferentiation] - xvals[indexOfFirstSample]; + final double t1 = xvals[indexOfSecondsample] - xvals[indexOfFirstSample]; + final double t2 = xvals[indexOfThirdSample] - xvals[indexOfFirstSample]; + + final double a = (x2 - x0 - (t2 / t1 * (x1 - x0))) / (t2 * t2 - t1 * t2); + final double b = (x1 - x0 - a * t1 * t1) / t1; + + return (2 * a * t) + b; + } + + /** + * Creates a Hermite cubic spline interpolation from the set of (x,y) value + * pairs and their derivatives. This is modeled off of the + * InterpolateHermiteSorted method in the Math.NET CubicSpline class. + * + * @param xvals x values for interpolation + * @param yvals y values for interpolation + * @param firstDerivatives first derivative values of the function + * @return polynomial that fits the function + */ + private PolynomialSplineFunction interpolateHermiteSorted(double[] xvals, + double[] yvals, + double[] firstDerivatives) { + if (xvals.length != yvals.length) { + throw new DimensionMismatchException(xvals.length, yvals.length); + } + + if (xvals.length != firstDerivatives.length) { + throw new DimensionMismatchException(xvals.length, + firstDerivatives.length); + } + + final int minimumLength = 2; + if (xvals.length < minimumLength) { + throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS, + xvals.length, minimumLength, + true); + } + + final int size = xvals.length - 1; + final PolynomialFunction[] polynomials = new PolynomialFunction[size]; + final double[] coefficients = new double[4]; + + for (int i = 0; i < polynomials.length; i++) { + final double w = xvals[i + 1] - xvals[i]; + final double w2 = w * w; + + final double yv = yvals[i]; + final double yvP = yvals[i + 1]; + + final double fd = firstDerivatives[i]; + final double fdP = firstDerivatives[i + 1]; + + coefficients[0] = yv; + coefficients[1] = firstDerivatives[i]; + coefficients[2] = (3 * (yvP - yv) / w - 2 * fd - fdP) / w; + coefficients[3] = (2 * (yv - yvP) / w + fd + fdP) / w2; + polynomials[i] = new PolynomialFunction(coefficients); + } + + return new PolynomialSplineFunction(xvals, polynomials); + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java new file mode 100644 index 000000000..55217eb33 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Function that implements the + * + * bicubic spline interpolation. + * + * @since 3.4 + */ +public class BicubicInterpolatingFunction + implements BivariateFunction { + /** Number of coefficients. */ + private static final int NUM_COEFF = 16; + /** + * Matrix to compute the spline coefficients from the function values + * and function derivatives values + */ + private static final double[][] AINV = { + { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 }, + { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 }, + { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 }, + { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 }, + { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 }, + { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 }, + { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 }, + { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 }, + { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 }, + { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 }, + { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 } + }; + + /** Samples x-coordinates */ + private final double[] xval; + /** Samples y-coordinates */ + private final double[] yval; + /** Set of cubic splines patching the whole data grid */ + private final BicubicFunction[][] splines; + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. + * @param dFdX Values of the partial derivative of function with respect + * to x on every grid point. + * @param dFdY Values of the partial derivative of function with respect + * to y on every grid point. + * @param d2FdXdY Values of the cross partial derivative of function on + * every grid point. + * @throws DimensionMismatchException if the various arrays do not contain + * the expected number of elements. + * @throws NonMonotonicSequenceException if {@code x} or {@code y} are + * not strictly increasing. + * @throws NoDataException if any of the arrays has zero length. + */ + public BicubicInterpolatingFunction(double[] x, + double[] y, + double[][] f, + double[][] dFdX, + double[][] dFdY, + double[][] d2FdXdY) + throws DimensionMismatchException, + NoDataException, + NonMonotonicSequenceException { + final int xLen = x.length; + final int yLen = y.length; + + if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) { + throw new NoDataException(); + } + if (xLen != f.length) { + throw new DimensionMismatchException(xLen, f.length); + } + if (xLen != dFdX.length) { + throw new DimensionMismatchException(xLen, dFdX.length); + } + if (xLen != dFdY.length) { + throw new DimensionMismatchException(xLen, dFdY.length); + } + if (xLen != d2FdXdY.length) { + throw new DimensionMismatchException(xLen, d2FdXdY.length); + } + + MathArrays.checkOrder(x); + MathArrays.checkOrder(y); + + xval = x.clone(); + yval = y.clone(); + + final int lastI = xLen - 1; + final int lastJ = yLen - 1; + splines = new BicubicFunction[lastI][lastJ]; + + for (int i = 0; i < lastI; i++) { + if (f[i].length != yLen) { + throw new DimensionMismatchException(f[i].length, yLen); + } + if (dFdX[i].length != yLen) { + throw new DimensionMismatchException(dFdX[i].length, yLen); + } + if (dFdY[i].length != yLen) { + throw new DimensionMismatchException(dFdY[i].length, yLen); + } + if (d2FdXdY[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdY[i].length, yLen); + } + final int ip1 = i + 1; + final double xR = xval[ip1] - xval[i]; + for (int j = 0; j < lastJ; j++) { + final int jp1 = j + 1; + final double yR = yval[jp1] - yval[j]; + final double xRyR = xR * yR; + final double[] beta = new double[] { + f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1], + dFdX[i][j] * xR, dFdX[ip1][j] * xR, dFdX[i][jp1] * xR, dFdX[ip1][jp1] * xR, + dFdY[i][j] * yR, dFdY[ip1][j] * yR, dFdY[i][jp1] * yR, dFdY[ip1][jp1] * yR, + d2FdXdY[i][j] * xRyR, d2FdXdY[ip1][j] * xRyR, d2FdXdY[i][jp1] * xRyR, d2FdXdY[ip1][jp1] * xRyR + }; + + splines[i][j] = new BicubicFunction(computeSplineCoefficients(beta)); + } + } + } + + /** + * {@inheritDoc} + */ + public double value(double x, double y) + throws OutOfRangeException { + final int i = searchIndex(x, xval); + final int j = searchIndex(y, yval); + + final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]); + final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]); + + return splines[i][j].value(xN, yN); + } + + /** + * Indicates whether a point is within the interpolation range. + * + * @param x First coordinate. + * @param y Second coordinate. + * @return {@code true} if (x, y) is a valid point. + */ + public boolean isValidPoint(double x, double y) { + if (x < xval[0] || + x > xval[xval.length - 1] || + y < yval[0] || + y > yval[yval.length - 1]) { + return false; + } else { + return true; + } + } + + /** + * @param c Coordinate. + * @param val Coordinate samples. + * @return the index in {@code val} corresponding to the interval + * containing {@code c}. + * @throws OutOfRangeException if {@code c} is out of the + * range defined by the boundary values of {@code val}. + */ + private int searchIndex(double c, double[] val) { + final int r = Arrays.binarySearch(val, c); + + if (r == -1 || + r == -val.length - 1) { + throw new OutOfRangeException(c, val[0], val[val.length - 1]); + } + + if (r < 0) { + // "c" in within an interpolation sub-interval: Return the + // index of the sample at the lower end of the sub-interval. + return -r - 2; + } + final int last = val.length - 1; + if (r == last) { + // "c" is the last sample of the range: Return the index + // of the sample at the lower end of the last sub-interval. + return last - 1; + } + + // "c" is another sample point. + return r; + } + + /** + * Compute the spline coefficients from the list of function values and + * function partial derivatives values at the four corners of a grid + * element. They must be specified in the following order: + *
    + *
  • f(0,0)
  • + *
  • f(1,0)
  • + *
  • f(0,1)
  • + *
  • f(1,1)
  • + *
  • fx(0,0)
  • + *
  • fx(1,0)
  • + *
  • fx(0,1)
  • + *
  • fx(1,1)
  • + *
  • fy(0,0)
  • + *
  • fy(1,0)
  • + *
  • fy(0,1)
  • + *
  • fy(1,1)
  • + *
  • fxy(0,0)
  • + *
  • fxy(1,0)
  • + *
  • fxy(0,1)
  • + *
  • fxy(1,1)
  • + *
+ * where the subscripts indicate the partial derivative with respect to + * the corresponding variable(s). + * + * @param beta List of function values and function partial derivatives + * values. + * @return the spline coefficients. + */ + private double[] computeSplineCoefficients(double[] beta) { + final double[] a = new double[NUM_COEFF]; + + for (int i = 0; i < NUM_COEFF; i++) { + double result = 0; + final double[] row = AINV[i]; + for (int j = 0; j < NUM_COEFF; j++) { + result += row[j] * beta[j]; + } + a[i] = result; + } + + return a; + } +} + +/** + * Bicubic function. + */ +class BicubicFunction implements BivariateFunction { + /** Number of points. */ + private static final short N = 4; + /** Coefficients */ + private final double[][] a; + + /** + * Simple constructor. + * + * @param coeff Spline coefficients. + */ + BicubicFunction(double[] coeff) { + a = new double[N][N]; + for (int j = 0; j < N; j++) { + final double[] aJ = a[j]; + for (int i = 0; i < N; i++) { + aJ[i] = coeff[i * N + j]; + } + } + } + + /** + * {@inheritDoc} + */ + public double value(double x, double y) { + if (x < 0 || x > 1) { + throw new OutOfRangeException(x, 0, 1); + } + if (y < 0 || y > 1) { + throw new OutOfRangeException(y, 0, 1); + } + + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = {1, x, x2, x3}; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = {1, y, y2, y3}; + + return apply(pX, pY, a); + } + + /** + * Compute the value of the bicubic polynomial. + * + * @param pX Powers of the x-coordinate. + * @param pY Powers of the y-coordinate. + * @param coeff Spline coefficients. + * @return the interpolated value. + */ + private double apply(double[] pX, double[] pY, double[][] coeff) { + double result = 0; + for (int i = 0; i < N; i++) { + final double r = MathArrays.linearCombination(coeff[i], pY); + result += r * pX[i]; + } + + return result; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java new file mode 100644 index 000000000..68f80014e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Generates a {@link BicubicInterpolatingFunction bicubic interpolating + * function}. + *

+ * Caveat: Because the interpolation scheme requires that derivatives be + * specified at the sample points, those are approximated with finite + * differences (using the 2-points symmetric formulae). + * Since their values are undefined at the borders of the provided + * interpolation ranges, the interpolated values will be wrong at the + * edges of the patch. + * The {@code interpolate} method will return a function that overrides + * {@link BicubicInterpolatingFunction#isValidPoint(double,double)} to + * indicate points where the interpolation will be inaccurate. + *

+ * + * @since 3.4 + */ +public class BicubicInterpolator + implements BivariateGridInterpolator { + /** + * {@inheritDoc} + */ + public BicubicInterpolatingFunction interpolate(final double[] xval, + final double[] yval, + final double[][] fval) + throws NoDataException, DimensionMismatchException, + NonMonotonicSequenceException, NumberIsTooSmallException { + if (xval.length == 0 || yval.length == 0 || fval.length == 0) { + throw new NoDataException(); + } + if (xval.length != fval.length) { + throw new DimensionMismatchException(xval.length, fval.length); + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + + final int xLen = xval.length; + final int yLen = yval.length; + + // Approximation to the partial derivatives using finite differences. + final double[][] dFdX = new double[xLen][yLen]; + final double[][] dFdY = new double[xLen][yLen]; + final double[][] d2FdXdY = new double[xLen][yLen]; + for (int i = 1; i < xLen - 1; i++) { + final int nI = i + 1; + final int pI = i - 1; + + final double nX = xval[nI]; + final double pX = xval[pI]; + + final double deltaX = nX - pX; + + for (int j = 1; j < yLen - 1; j++) { + final int nJ = j + 1; + final int pJ = j - 1; + + final double nY = yval[nJ]; + final double pY = yval[pJ]; + + final double deltaY = nY - pY; + + dFdX[i][j] = (fval[nI][j] - fval[pI][j]) / deltaX; + dFdY[i][j] = (fval[i][nJ] - fval[i][pJ]) / deltaY; + + final double deltaXY = deltaX * deltaY; + + d2FdXdY[i][j] = (fval[nI][nJ] - fval[nI][pJ] - fval[pI][nJ] + fval[pI][pJ]) / deltaXY; + } + } + + // Create the interpolating function. + return new BicubicInterpolatingFunction(xval, yval, fval, + dFdX, dFdY, d2FdXdY) { + /** {@inheritDoc} */ + @Override + public boolean isValidPoint(double x, double y) { + if (x < xval[1] || + x > xval[xval.length - 2] || + y < yval[1] || + y > yval[yval.length - 2]) { + return false; + } else { + return true; + } + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java new file mode 100644 index 000000000..f3eecf720 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java @@ -0,0 +1,642 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Function that implements the + * + * bicubic spline interpolation. Due to numerical accuracy issues this should not + * be used. + * + * @since 2.1 + * @deprecated as of 3.4 replaced by + * {@link PiecewiseBicubicSplineInterpolatingFunction} + */ +@Deprecated +public class BicubicSplineInterpolatingFunction + implements BivariateFunction { + /** Number of coefficients. */ + private static final int NUM_COEFF = 16; + /** + * Matrix to compute the spline coefficients from the function values + * and function derivatives values + */ + private static final double[][] AINV = { + { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 }, + { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 }, + { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 }, + { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 }, + { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 }, + { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 }, + { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 }, + { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 }, + { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 }, + { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 }, + { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 } + }; + + /** Samples x-coordinates */ + private final double[] xval; + /** Samples y-coordinates */ + private final double[] yval; + /** Set of cubic splines patching the whole data grid */ + private final BicubicSplineFunction[][] splines; + /** + * Partial derivatives. + * The value of the first index determines the kind of derivatives: + * 0 = first partial derivatives wrt x + * 1 = first partial derivatives wrt y + * 2 = second partial derivatives wrt x + * 3 = second partial derivatives wrt y + * 4 = cross partial derivatives + */ + private final BivariateFunction[][][] partialDerivatives; + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. + * @param dFdX Values of the partial derivative of function with respect + * to x on every grid point. + * @param dFdY Values of the partial derivative of function with respect + * to y on every grid point. + * @param d2FdXdY Values of the cross partial derivative of function on + * every grid point. + * @throws DimensionMismatchException if the various arrays do not contain + * the expected number of elements. + * @throws NonMonotonicSequenceException if {@code x} or {@code y} are + * not strictly increasing. + * @throws NoDataException if any of the arrays has zero length. + */ + public BicubicSplineInterpolatingFunction(double[] x, + double[] y, + double[][] f, + double[][] dFdX, + double[][] dFdY, + double[][] d2FdXdY) + throws DimensionMismatchException, + NoDataException, + NonMonotonicSequenceException { + this(x, y, f, dFdX, dFdY, d2FdXdY, false); + } + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. + * @param dFdX Values of the partial derivative of function with respect + * to x on every grid point. + * @param dFdY Values of the partial derivative of function with respect + * to y on every grid point. + * @param d2FdXdY Values of the cross partial derivative of function on + * every grid point. + * @param initializeDerivatives Whether to initialize the internal data + * needed for calling any of the methods that compute the partial derivatives + * this function. + * @throws DimensionMismatchException if the various arrays do not contain + * the expected number of elements. + * @throws NonMonotonicSequenceException if {@code x} or {@code y} are + * not strictly increasing. + * @throws NoDataException if any of the arrays has zero length. + * + * @see #partialDerivativeX(double,double) + * @see #partialDerivativeY(double,double) + * @see #partialDerivativeXX(double,double) + * @see #partialDerivativeYY(double,double) + * @see #partialDerivativeXY(double,double) + */ + public BicubicSplineInterpolatingFunction(double[] x, + double[] y, + double[][] f, + double[][] dFdX, + double[][] dFdY, + double[][] d2FdXdY, + boolean initializeDerivatives) + throws DimensionMismatchException, + NoDataException, + NonMonotonicSequenceException { + final int xLen = x.length; + final int yLen = y.length; + + if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) { + throw new NoDataException(); + } + if (xLen != f.length) { + throw new DimensionMismatchException(xLen, f.length); + } + if (xLen != dFdX.length) { + throw new DimensionMismatchException(xLen, dFdX.length); + } + if (xLen != dFdY.length) { + throw new DimensionMismatchException(xLen, dFdY.length); + } + if (xLen != d2FdXdY.length) { + throw new DimensionMismatchException(xLen, d2FdXdY.length); + } + + MathArrays.checkOrder(x); + MathArrays.checkOrder(y); + + xval = x.clone(); + yval = y.clone(); + + final int lastI = xLen - 1; + final int lastJ = yLen - 1; + splines = new BicubicSplineFunction[lastI][lastJ]; + + for (int i = 0; i < lastI; i++) { + if (f[i].length != yLen) { + throw new DimensionMismatchException(f[i].length, yLen); + } + if (dFdX[i].length != yLen) { + throw new DimensionMismatchException(dFdX[i].length, yLen); + } + if (dFdY[i].length != yLen) { + throw new DimensionMismatchException(dFdY[i].length, yLen); + } + if (d2FdXdY[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdY[i].length, yLen); + } + final int ip1 = i + 1; + for (int j = 0; j < lastJ; j++) { + final int jp1 = j + 1; + final double[] beta = new double[] { + f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1], + dFdX[i][j], dFdX[ip1][j], dFdX[i][jp1], dFdX[ip1][jp1], + dFdY[i][j], dFdY[ip1][j], dFdY[i][jp1], dFdY[ip1][jp1], + d2FdXdY[i][j], d2FdXdY[ip1][j], d2FdXdY[i][jp1], d2FdXdY[ip1][jp1] + }; + + splines[i][j] = new BicubicSplineFunction(computeSplineCoefficients(beta), + initializeDerivatives); + } + } + + if (initializeDerivatives) { + // Compute all partial derivatives. + partialDerivatives = new BivariateFunction[5][lastI][lastJ]; + + for (int i = 0; i < lastI; i++) { + for (int j = 0; j < lastJ; j++) { + final BicubicSplineFunction bcs = splines[i][j]; + partialDerivatives[0][i][j] = bcs.partialDerivativeX(); + partialDerivatives[1][i][j] = bcs.partialDerivativeY(); + partialDerivatives[2][i][j] = bcs.partialDerivativeXX(); + partialDerivatives[3][i][j] = bcs.partialDerivativeYY(); + partialDerivatives[4][i][j] = bcs.partialDerivativeXY(); + } + } + } else { + // Partial derivative methods cannot be used. + partialDerivatives = null; + } + } + + /** + * {@inheritDoc} + */ + public double value(double x, double y) + throws OutOfRangeException { + final int i = searchIndex(x, xval); + final int j = searchIndex(y, yval); + + final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]); + final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]); + + return splines[i][j].value(xN, yN); + } + + /** + * Indicates whether a point is within the interpolation range. + * + * @param x First coordinate. + * @param y Second coordinate. + * @return {@code true} if (x, y) is a valid point. + * @since 3.3 + */ + public boolean isValidPoint(double x, double y) { + if (x < xval[0] || + x > xval[xval.length - 1] || + y < yval[0] || + y > yval[yval.length - 1]) { + return false; + } else { + return true; + } + } + + /** + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the first partial derivative with + * respect to x. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + public double partialDerivativeX(double x, double y) + throws OutOfRangeException { + return partialDerivative(0, x, y); + } + /** + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the first partial derivative with + * respect to y. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + public double partialDerivativeY(double x, double y) + throws OutOfRangeException { + return partialDerivative(1, x, y); + } + /** + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the second partial derivative with + * respect to x. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + public double partialDerivativeXX(double x, double y) + throws OutOfRangeException { + return partialDerivative(2, x, y); + } + /** + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the second partial derivative with + * respect to y. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + public double partialDerivativeYY(double x, double y) + throws OutOfRangeException { + return partialDerivative(3, x, y); + } + /** + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the second partial cross-derivative. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + public double partialDerivativeXY(double x, double y) + throws OutOfRangeException { + return partialDerivative(4, x, y); + } + + /** + * @param which First index in {@link #partialDerivatives}. + * @param x x-coordinate. + * @param y y-coordinate. + * @return the value at point (x, y) of the selected partial derivative. + * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside + * the range defined by the boundary values of {@code xval} (resp. + * {@code yval}). + * @throws NullPointerException if the internal data were not initialized + * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][], + * double[][],double[][],double[][],boolean) constructor}). + */ + private double partialDerivative(int which, double x, double y) + throws OutOfRangeException { + final int i = searchIndex(x, xval); + final int j = searchIndex(y, yval); + + final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]); + final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]); + + return partialDerivatives[which][i][j].value(xN, yN); + } + + /** + * @param c Coordinate. + * @param val Coordinate samples. + * @return the index in {@code val} corresponding to the interval + * containing {@code c}. + * @throws OutOfRangeException if {@code c} is out of the + * range defined by the boundary values of {@code val}. + */ + private int searchIndex(double c, double[] val) { + final int r = Arrays.binarySearch(val, c); + + if (r == -1 || + r == -val.length - 1) { + throw new OutOfRangeException(c, val[0], val[val.length - 1]); + } + + if (r < 0) { + // "c" in within an interpolation sub-interval: Return the + // index of the sample at the lower end of the sub-interval. + return -r - 2; + } + final int last = val.length - 1; + if (r == last) { + // "c" is the last sample of the range: Return the index + // of the sample at the lower end of the last sub-interval. + return last - 1; + } + + // "c" is another sample point. + return r; + } + + /** + * Compute the spline coefficients from the list of function values and + * function partial derivatives values at the four corners of a grid + * element. They must be specified in the following order: + *
    + *
  • f(0,0)
  • + *
  • f(1,0)
  • + *
  • f(0,1)
  • + *
  • f(1,1)
  • + *
  • fx(0,0)
  • + *
  • fx(1,0)
  • + *
  • fx(0,1)
  • + *
  • fx(1,1)
  • + *
  • fy(0,0)
  • + *
  • fy(1,0)
  • + *
  • fy(0,1)
  • + *
  • fy(1,1)
  • + *
  • fxy(0,0)
  • + *
  • fxy(1,0)
  • + *
  • fxy(0,1)
  • + *
  • fxy(1,1)
  • + *
+ * where the subscripts indicate the partial derivative with respect to + * the corresponding variable(s). + * + * @param beta List of function values and function partial derivatives + * values. + * @return the spline coefficients. + */ + private double[] computeSplineCoefficients(double[] beta) { + final double[] a = new double[NUM_COEFF]; + + for (int i = 0; i < NUM_COEFF; i++) { + double result = 0; + final double[] row = AINV[i]; + for (int j = 0; j < NUM_COEFF; j++) { + result += row[j] * beta[j]; + } + a[i] = result; + } + + return a; + } +} + +/** + * 2D-spline function. + * + */ +class BicubicSplineFunction implements BivariateFunction { + /** Number of points. */ + private static final short N = 4; + /** Coefficients */ + private final double[][] a; + /** First partial derivative along x. */ + private final BivariateFunction partialDerivativeX; + /** First partial derivative along y. */ + private final BivariateFunction partialDerivativeY; + /** Second partial derivative along x. */ + private final BivariateFunction partialDerivativeXX; + /** Second partial derivative along y. */ + private final BivariateFunction partialDerivativeYY; + /** Second crossed partial derivative. */ + private final BivariateFunction partialDerivativeXY; + + /** + * Simple constructor. + * + * @param coeff Spline coefficients. + */ + BicubicSplineFunction(double[] coeff) { + this(coeff, false); + } + + /** + * Simple constructor. + * + * @param coeff Spline coefficients. + * @param initializeDerivatives Whether to initialize the internal data + * needed for calling any of the methods that compute the partial derivatives + * this function. + */ + BicubicSplineFunction(double[] coeff, boolean initializeDerivatives) { + a = new double[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + a[i][j] = coeff[i * N + j]; + } + } + + if (initializeDerivatives) { + // Compute all partial derivatives functions. + final double[][] aX = new double[N][N]; + final double[][] aY = new double[N][N]; + final double[][] aXX = new double[N][N]; + final double[][] aYY = new double[N][N]; + final double[][] aXY = new double[N][N]; + + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + final double c = a[i][j]; + aX[i][j] = i * c; + aY[i][j] = j * c; + aXX[i][j] = (i - 1) * aX[i][j]; + aYY[i][j] = (j - 1) * aY[i][j]; + aXY[i][j] = j * aX[i][j]; + } + } + + partialDerivativeX = new BivariateFunction() { + /** {@inheritDoc} */ + public double value(double x, double y) { + final double x2 = x * x; + final double[] pX = {0, 1, x, x2}; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = {1, y, y2, y3}; + + return apply(pX, pY, aX); + } + }; + partialDerivativeY = new BivariateFunction() { + /** {@inheritDoc} */ + public double value(double x, double y) { + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = {1, x, x2, x3}; + + final double y2 = y * y; + final double[] pY = {0, 1, y, y2}; + + return apply(pX, pY, aY); + } + }; + partialDerivativeXX = new BivariateFunction() { + /** {@inheritDoc} */ + public double value(double x, double y) { + final double[] pX = {0, 0, 1, x}; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = {1, y, y2, y3}; + + return apply(pX, pY, aXX); + } + }; + partialDerivativeYY = new BivariateFunction() { + /** {@inheritDoc} */ + public double value(double x, double y) { + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = {1, x, x2, x3}; + + final double[] pY = {0, 0, 1, y}; + + return apply(pX, pY, aYY); + } + }; + partialDerivativeXY = new BivariateFunction() { + /** {@inheritDoc} */ + public double value(double x, double y) { + final double x2 = x * x; + final double[] pX = {0, 1, x, x2}; + + final double y2 = y * y; + final double[] pY = {0, 1, y, y2}; + + return apply(pX, pY, aXY); + } + }; + } else { + partialDerivativeX = null; + partialDerivativeY = null; + partialDerivativeXX = null; + partialDerivativeYY = null; + partialDerivativeXY = null; + } + } + + /** + * {@inheritDoc} + */ + public double value(double x, double y) { + if (x < 0 || x > 1) { + throw new OutOfRangeException(x, 0, 1); + } + if (y < 0 || y > 1) { + throw new OutOfRangeException(y, 0, 1); + } + + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = {1, x, x2, x3}; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = {1, y, y2, y3}; + + return apply(pX, pY, a); + } + + /** + * Compute the value of the bicubic polynomial. + * + * @param pX Powers of the x-coordinate. + * @param pY Powers of the y-coordinate. + * @param coeff Spline coefficients. + * @return the interpolated value. + */ + private double apply(double[] pX, double[] pY, double[][] coeff) { + double result = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + result += coeff[i][j] * pX[i] * pY[j]; + } + } + + return result; + } + + /** + * @return the partial derivative wrt {@code x}. + */ + public BivariateFunction partialDerivativeX() { + return partialDerivativeX; + } + /** + * @return the partial derivative wrt {@code y}. + */ + public BivariateFunction partialDerivativeY() { + return partialDerivativeY; + } + /** + * @return the second partial derivative wrt {@code x}. + */ + public BivariateFunction partialDerivativeXX() { + return partialDerivativeXX; + } + /** + * @return the second partial derivative wrt {@code y}. + */ + public BivariateFunction partialDerivativeYY() { + return partialDerivativeYY; + } + /** + * @return the second partial cross-derivative. + */ + public BivariateFunction partialDerivativeXY() { + return partialDerivativeXY; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java new file mode 100644 index 000000000..b6b0cd726 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Generates a bicubic interpolating function. Due to numerical accuracy issues this should not + * be used. + * + * @since 2.2 + * @deprecated as of 3.4 replaced by {@link PiecewiseBicubicSplineInterpolator} + */ +@Deprecated +public class BicubicSplineInterpolator + implements BivariateGridInterpolator { + /** Whether to initialize internal data used to compute the analytical + derivatives of the splines. */ + private final boolean initializeDerivatives; + + /** + * Default constructor. + * The argument {@link #BicubicSplineInterpolator(boolean) initializeDerivatives} + * is set to {@code false}. + */ + public BicubicSplineInterpolator() { + this(false); + } + + /** + * Creates an interpolator. + * + * @param initializeDerivatives Whether to initialize the internal data + * needed for calling any of the methods that compute the partial derivatives + * of the {@link BicubicSplineInterpolatingFunction function} returned from + * the call to {@link #interpolate(double[],double[],double[][]) interpolate}. + */ + public BicubicSplineInterpolator(boolean initializeDerivatives) { + this.initializeDerivatives = initializeDerivatives; + } + + /** + * {@inheritDoc} + */ + public BicubicSplineInterpolatingFunction interpolate(final double[] xval, + final double[] yval, + final double[][] fval) + throws NoDataException, DimensionMismatchException, + NonMonotonicSequenceException, NumberIsTooSmallException { + if (xval.length == 0 || yval.length == 0 || fval.length == 0) { + throw new NoDataException(); + } + if (xval.length != fval.length) { + throw new DimensionMismatchException(xval.length, fval.length); + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + + final int xLen = xval.length; + final int yLen = yval.length; + + // Samples (first index is y-coordinate, i.e. subarray variable is x) + // 0 <= i < xval.length + // 0 <= j < yval.length + // fX[j][i] = f(xval[i], yval[j]) + final double[][] fX = new double[yLen][xLen]; + for (int i = 0; i < xLen; i++) { + if (fval[i].length != yLen) { + throw new DimensionMismatchException(fval[i].length, yLen); + } + + for (int j = 0; j < yLen; j++) { + fX[j][i] = fval[i][j]; + } + } + + final SplineInterpolator spInterpolator = new SplineInterpolator(); + + // For each line y[j] (0 <= j < yLen), construct a 1D spline with + // respect to variable x + final PolynomialSplineFunction[] ySplineX = new PolynomialSplineFunction[yLen]; + for (int j = 0; j < yLen; j++) { + ySplineX[j] = spInterpolator.interpolate(xval, fX[j]); + } + + // For each line x[i] (0 <= i < xLen), construct a 1D spline with + // respect to variable y generated by array fY_1[i] + final PolynomialSplineFunction[] xSplineY = new PolynomialSplineFunction[xLen]; + for (int i = 0; i < xLen; i++) { + xSplineY[i] = spInterpolator.interpolate(yval, fval[i]); + } + + // Partial derivatives with respect to x at the grid knots + final double[][] dFdX = new double[xLen][yLen]; + for (int j = 0; j < yLen; j++) { + final UnivariateFunction f = ySplineX[j].derivative(); + for (int i = 0; i < xLen; i++) { + dFdX[i][j] = f.value(xval[i]); + } + } + + // Partial derivatives with respect to y at the grid knots + final double[][] dFdY = new double[xLen][yLen]; + for (int i = 0; i < xLen; i++) { + final UnivariateFunction f = xSplineY[i].derivative(); + for (int j = 0; j < yLen; j++) { + dFdY[i][j] = f.value(yval[j]); + } + } + + // Cross partial derivatives + final double[][] d2FdXdY = new double[xLen][yLen]; + for (int i = 0; i < xLen ; i++) { + final int nI = nextIndex(i, xLen); + final int pI = previousIndex(i); + for (int j = 0; j < yLen; j++) { + final int nJ = nextIndex(j, yLen); + final int pJ = previousIndex(j); + d2FdXdY[i][j] = (fval[nI][nJ] - fval[nI][pJ] - + fval[pI][nJ] + fval[pI][pJ]) / + ((xval[nI] - xval[pI]) * (yval[nJ] - yval[pJ])); + } + } + + // Create the interpolating splines + return new BicubicSplineInterpolatingFunction(xval, yval, fval, + dFdX, dFdY, d2FdXdY, + initializeDerivatives); + } + + /** + * Computes the next index of an array, clipping if necessary. + * It is assumed (but not checked) that {@code i >= 0}. + * + * @param i Index. + * @param max Upper limit of the array. + * @return the next index. + */ + private int nextIndex(int i, int max) { + final int index = i + 1; + return index < max ? index : index - 1; + } + /** + * Computes the previous index of an array, clipping if necessary. + * It is assumed (but not checked) that {@code i} is smaller than the size + * of the array. + * + * @param i Index. + * @return the previous index. + */ + private int previousIndex(int i) { + final int index = i - 1; + return index >= 0 ? index : 0; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java new file mode 100644 index 000000000..3f61916a4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; + +/** + * Interface representing a bivariate real interpolating function where the + * sample points must be specified on a regular grid. + * + */ +public interface BivariateGridInterpolator { + /** + * Compute an interpolating function for the dataset. + * + * @param xval All the x-coordinates of the interpolation points, sorted + * in increasing order. + * @param yval All the y-coordinates of the interpolation points, sorted + * in increasing order. + * @param fval The values of the interpolation points on all the grid knots: + * {@code fval[i][j] = f(xval[i], yval[j])}. + * @return a function which interpolates the dataset. + * @throws NoDataException if any of the arrays has zero length. + * @throws DimensionMismatchException if the array lengths are inconsistent. + * @throws NonMonotonicSequenceException if the array is not sorted. + * @throws NumberIsTooSmallException if the number of points is too small for + * the order of the interpolation + */ + BivariateFunction interpolate(double[] xval, double[] yval, + double[][] fval) + throws NoDataException, DimensionMismatchException, + NonMonotonicSequenceException, NumberIsTooSmallException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.java new file mode 100644 index 000000000..4f62132a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.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 com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunctionNewtonForm; + +/** + * Implements the + * Divided Difference Algorithm for interpolation of real univariate + * functions. For reference, see Introduction to Numerical Analysis, + * ISBN 038795452X, chapter 2. + *

+ * The actual code of Neville's evaluation is in PolynomialFunctionLagrangeForm, + * this class provides an easy-to-use interface to it.

+ * + * @since 1.2 + */ +public class DividedDifferenceInterpolator + implements UnivariateInterpolator, Serializable { + /** serializable version identifier */ + private static final long serialVersionUID = 107049519551235069L; + + /** + * Compute an interpolating function for the dataset. + * + * @param x Interpolating points array. + * @param y Interpolating values array. + * @return a function which interpolates the dataset. + * @throws DimensionMismatchException if the array lengths are different. + * @throws NumberIsTooSmallException if the number of points is less than 2. + * @throws NonMonotonicSequenceException if {@code x} is not sorted in + * strictly increasing order. + */ + public PolynomialFunctionNewtonForm interpolate(double x[], double y[]) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + /** + * a[] and c[] are defined in the general formula of Newton form: + * p(x) = a[0] + a[1](x-c[0]) + a[2](x-c[0])(x-c[1]) + ... + + * a[n](x-c[0])(x-c[1])...(x-c[n-1]) + */ + PolynomialFunctionLagrangeForm.verifyInterpolationArray(x, y, true); + + /** + * When used for interpolation, the Newton form formula becomes + * p(x) = f[x0] + f[x0,x1](x-x0) + f[x0,x1,x2](x-x0)(x-x1) + ... + + * f[x0,x1,...,x[n-1]](x-x0)(x-x1)...(x-x[n-2]) + * Therefore, a[k] = f[x0,x1,...,xk], c[k] = x[k]. + *

+ * Note x[], y[], a[] have the same length but c[]'s size is one less.

+ */ + final double[] c = new double[x.length-1]; + System.arraycopy(x, 0, c, 0, c.length); + + final double[] a = computeDividedDifference(x, y); + return new PolynomialFunctionNewtonForm(a, c); + } + + /** + * Return a copy of the divided difference array. + *

+ * The divided difference array is defined recursively by

+     * f[x0] = f(x0)
+     * f[x0,x1,...,xk] = (f[x1,...,xk] - f[x0,...,x[k-1]]) / (xk - x0)
+     * 
+ *

+ * The computational complexity is \(O(n^2)\) where \(n\) is the common + * length of {@code x} and {@code y}.

+ * + * @param x Interpolating points array. + * @param y Interpolating values array. + * @return a fresh copy of the divided difference array. + * @throws DimensionMismatchException if the array lengths are different. + * @throws NumberIsTooSmallException if the number of points is less than 2. + * @throws NonMonotonicSequenceException + * if {@code x} is not sorted in strictly increasing order. + */ + protected static double[] computeDividedDifference(final double x[], final double y[]) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + PolynomialFunctionLagrangeForm.verifyInterpolationArray(x, y, true); + + final double[] divdiff = y.clone(); // initialization + + final int n = x.length; + final double[] a = new double [n]; + a[0] = divdiff[0]; + for (int i = 1; i < n; i++) { + for (int j = 0; j < n-i; j++) { + final double denominator = x[j+i] - x[j]; + divdiff[j] = (divdiff[j+1] - divdiff[j]) / denominator; + } + a[i] = divdiff[0]; + } + + return a; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java new file mode 100644 index 000000000..d9a8b7e80 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** Polynomial interpolator using both sample values and sample derivatives. + *

+ * The interpolation polynomials match all sample points, including both values + * and provided derivatives. There is one polynomial for each component of + * the values vector. All polynomials have the same degree. The degree of the + * polynomials depends on the number of points and number of derivatives at each + * point. For example the interpolation polynomials for n sample points without + * any derivatives all have degree n-1. The interpolation polynomials for n + * sample points with the two extreme points having value and first derivative + * and the remaining points having value only all have degree n+1. The + * interpolation polynomial for n sample points with value, first and second + * derivative for all points all have degree 3n-1. + *

+ * + * @param Type of the field elements. + * + * @since 3.2 + */ +public class FieldHermiteInterpolator> { + + /** Sample abscissae. */ + private final List abscissae; + + /** Top diagonal of the divided differences array. */ + private final List topDiagonal; + + /** Bottom diagonal of the divided differences array. */ + private final List bottomDiagonal; + + /** Create an empty interpolator. + */ + public FieldHermiteInterpolator() { + this.abscissae = new ArrayList(); + this.topDiagonal = new ArrayList(); + this.bottomDiagonal = new ArrayList(); + } + + /** Add a sample point. + *

+ * This method must be called once for each sample point. It is allowed to + * mix some calls with values only with calls with values and first + * derivatives. + *

+ *

+ * The point abscissae for all calls must be different. + *

+ * @param x abscissa of the sample point + * @param value value and derivatives of the sample point + * (if only one row is passed, it is the value, if two rows are + * passed the first one is the value and the second the derivative + * and so on) + * @exception ZeroException if the abscissa difference between added point + * and a previous point is zero (i.e. the two points are at same abscissa) + * @exception MathArithmeticException if the number of derivatives is larger + * than 20, which prevents computation of a factorial + * @throws DimensionMismatchException if derivative structures are inconsistent + * @throws NullArgumentException if x is null + */ + public void addSamplePoint(final T x, final T[] ... value) + throws ZeroException, MathArithmeticException, + DimensionMismatchException, NullArgumentException { + + MathUtils.checkNotNull(x); + T factorial = x.getField().getOne(); + for (int i = 0; i < value.length; ++i) { + + final T[] y = value[i].clone(); + if (i > 1) { + factorial = factorial.multiply(i); + final T inv = factorial.reciprocal(); + for (int j = 0; j < y.length; ++j) { + y[j] = y[j].multiply(inv); + } + } + + // update the bottom diagonal of the divided differences array + final int n = abscissae.size(); + bottomDiagonal.add(n - i, y); + T[] bottom0 = y; + for (int j = i; j < n; ++j) { + final T[] bottom1 = bottomDiagonal.get(n - (j + 1)); + if (x.equals(abscissae.get(n - (j + 1)))) { + throw new ZeroException(LocalizedFormats.DUPLICATED_ABSCISSA_DIVISION_BY_ZERO, x); + } + final T inv = x.subtract(abscissae.get(n - (j + 1))).reciprocal(); + for (int k = 0; k < y.length; ++k) { + bottom1[k] = inv.multiply(bottom0[k].subtract(bottom1[k])); + } + bottom0 = bottom1; + } + + // update the top diagonal of the divided differences array + topDiagonal.add(bottom0.clone()); + + // update the abscissae array + abscissae.add(x); + + } + + } + + /** Interpolate value at a specified abscissa. + * @param x interpolation abscissa + * @return interpolated value + * @exception NoDataException if sample is empty + * @throws NullArgumentException if x is null + */ + public T[] value(T x) throws NoDataException, NullArgumentException { + + // safety check + MathUtils.checkNotNull(x); + if (abscissae.isEmpty()) { + throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE); + } + + final T[] value = MathArrays.buildArray(x.getField(), topDiagonal.get(0).length); + T valueCoeff = x.getField().getOne(); + for (int i = 0; i < topDiagonal.size(); ++i) { + T[] dividedDifference = topDiagonal.get(i); + for (int k = 0; k < value.length; ++k) { + value[k] = value[k].add(dividedDifference[k].multiply(valueCoeff)); + } + final T deltaX = x.subtract(abscissae.get(i)); + valueCoeff = valueCoeff.multiply(deltaX); + } + + return value; + + } + + /** Interpolate value and first derivatives at a specified abscissa. + * @param x interpolation abscissa + * @param order maximum derivation order + * @return interpolated value and derivatives (value in row 0, + * 1st derivative in row 1, ... nth derivative in row n) + * @exception NoDataException if sample is empty + * @throws NullArgumentException if x is null + */ + public T[][] derivatives(T x, int order) throws NoDataException, NullArgumentException { + + // safety check + MathUtils.checkNotNull(x); + if (abscissae.isEmpty()) { + throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE); + } + + final T zero = x.getField().getZero(); + final T one = x.getField().getOne(); + final T[] tj = MathArrays.buildArray(x.getField(), order + 1); + tj[0] = zero; + for (int i = 0; i < order; ++i) { + tj[i + 1] = tj[i].add(one); + } + + final T[][] derivatives = + MathArrays.buildArray(x.getField(), order + 1, topDiagonal.get(0).length); + final T[] valueCoeff = MathArrays.buildArray(x.getField(), order + 1); + valueCoeff[0] = x.getField().getOne(); + for (int i = 0; i < topDiagonal.size(); ++i) { + T[] dividedDifference = topDiagonal.get(i); + final T deltaX = x.subtract(abscissae.get(i)); + for (int j = order; j >= 0; --j) { + for (int k = 0; k < derivatives[j].length; ++k) { + derivatives[j][k] = + derivatives[j][k].add(dividedDifference[k].multiply(valueCoeff[j])); + } + valueCoeff[j] = valueCoeff[j].multiply(deltaX); + if (j > 0) { + valueCoeff[j] = valueCoeff[j].add(tj[j].multiply(valueCoeff[j - 1])); + } + } + } + + return derivatives; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java new file mode 100644 index 000000000..2e427eeef --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; + +/** Polynomial interpolator using both sample values and sample derivatives. + *

+ * The interpolation polynomials match all sample points, including both values + * and provided derivatives. There is one polynomial for each component of + * the values vector. All polynomials have the same degree. The degree of the + * polynomials depends on the number of points and number of derivatives at each + * point. For example the interpolation polynomials for n sample points without + * any derivatives all have degree n-1. The interpolation polynomials for n + * sample points with the two extreme points having value and first derivative + * and the remaining points having value only all have degree n+1. The + * interpolation polynomial for n sample points with value, first and second + * derivative for all points all have degree 3n-1. + *

+ * + * @since 3.1 + */ +public class HermiteInterpolator implements UnivariateDifferentiableVectorFunction { + + /** Sample abscissae. */ + private final List abscissae; + + /** Top diagonal of the divided differences array. */ + private final List topDiagonal; + + /** Bottom diagonal of the divided differences array. */ + private final List bottomDiagonal; + + /** Create an empty interpolator. + */ + public HermiteInterpolator() { + this.abscissae = new ArrayList(); + this.topDiagonal = new ArrayList(); + this.bottomDiagonal = new ArrayList(); + } + + /** Add a sample point. + *

+ * This method must be called once for each sample point. It is allowed to + * mix some calls with values only with calls with values and first + * derivatives. + *

+ *

+ * The point abscissae for all calls must be different. + *

+ * @param x abscissa of the sample point + * @param value value and derivatives of the sample point + * (if only one row is passed, it is the value, if two rows are + * passed the first one is the value and the second the derivative + * and so on) + * @exception ZeroException if the abscissa difference between added point + * and a previous point is zero (i.e. the two points are at same abscissa) + * @exception MathArithmeticException if the number of derivatives is larger + * than 20, which prevents computation of a factorial + */ + public void addSamplePoint(final double x, final double[] ... value) + throws ZeroException, MathArithmeticException { + + for (int i = 0; i < value.length; ++i) { + + final double[] y = value[i].clone(); + if (i > 1) { + double inv = 1.0 / CombinatoricsUtils.factorial(i); + for (int j = 0; j < y.length; ++j) { + y[j] *= inv; + } + } + + // update the bottom diagonal of the divided differences array + final int n = abscissae.size(); + bottomDiagonal.add(n - i, y); + double[] bottom0 = y; + for (int j = i; j < n; ++j) { + final double[] bottom1 = bottomDiagonal.get(n - (j + 1)); + final double inv = 1.0 / (x - abscissae.get(n - (j + 1))); + if (Double.isInfinite(inv)) { + throw new ZeroException(LocalizedFormats.DUPLICATED_ABSCISSA_DIVISION_BY_ZERO, x); + } + for (int k = 0; k < y.length; ++k) { + bottom1[k] = inv * (bottom0[k] - bottom1[k]); + } + bottom0 = bottom1; + } + + // update the top diagonal of the divided differences array + topDiagonal.add(bottom0.clone()); + + // update the abscissae array + abscissae.add(x); + + } + + } + + /** Compute the interpolation polynomials. + * @return interpolation polynomials array + * @exception NoDataException if sample is empty + */ + public PolynomialFunction[] getPolynomials() + throws NoDataException { + + // safety check + checkInterpolation(); + + // iteration initialization + final PolynomialFunction zero = polynomial(0); + PolynomialFunction[] polynomials = new PolynomialFunction[topDiagonal.get(0).length]; + for (int i = 0; i < polynomials.length; ++i) { + polynomials[i] = zero; + } + PolynomialFunction coeff = polynomial(1); + + // build the polynomials by iterating on the top diagonal of the divided differences array + for (int i = 0; i < topDiagonal.size(); ++i) { + double[] tdi = topDiagonal.get(i); + for (int k = 0; k < polynomials.length; ++k) { + polynomials[k] = polynomials[k].add(coeff.multiply(polynomial(tdi[k]))); + } + coeff = coeff.multiply(polynomial(-abscissae.get(i), 1.0)); + } + + return polynomials; + + } + + /** Interpolate value at a specified abscissa. + *

+ * Calling this method is equivalent to call the {@link PolynomialFunction#value(double) + * value} methods of all polynomials returned by {@link #getPolynomials() getPolynomials}, + * except it does not build the intermediate polynomials, so this method is faster and + * numerically more stable. + *

+ * @param x interpolation abscissa + * @return interpolated value + * @exception NoDataException if sample is empty + */ + public double[] value(double x) + throws NoDataException { + + // safety check + checkInterpolation(); + + final double[] value = new double[topDiagonal.get(0).length]; + double valueCoeff = 1; + for (int i = 0; i < topDiagonal.size(); ++i) { + double[] dividedDifference = topDiagonal.get(i); + for (int k = 0; k < value.length; ++k) { + value[k] += dividedDifference[k] * valueCoeff; + } + final double deltaX = x - abscissae.get(i); + valueCoeff *= deltaX; + } + + return value; + + } + + /** Interpolate value at a specified abscissa. + *

+ * Calling this method is equivalent to call the {@link + * PolynomialFunction#value(DerivativeStructure) value} methods of all polynomials + * returned by {@link #getPolynomials() getPolynomials}, except it does not build the + * intermediate polynomials, so this method is faster and numerically more stable. + *

+ * @param x interpolation abscissa + * @return interpolated value + * @exception NoDataException if sample is empty + */ + public DerivativeStructure[] value(final DerivativeStructure x) + throws NoDataException { + + // safety check + checkInterpolation(); + + final DerivativeStructure[] value = new DerivativeStructure[topDiagonal.get(0).length]; + Arrays.fill(value, x.getField().getZero()); + DerivativeStructure valueCoeff = x.getField().getOne(); + for (int i = 0; i < topDiagonal.size(); ++i) { + double[] dividedDifference = topDiagonal.get(i); + for (int k = 0; k < value.length; ++k) { + value[k] = value[k].add(valueCoeff.multiply(dividedDifference[k])); + } + final DerivativeStructure deltaX = x.subtract(abscissae.get(i)); + valueCoeff = valueCoeff.multiply(deltaX); + } + + return value; + + } + + /** Check interpolation can be performed. + * @exception NoDataException if interpolation cannot be performed + * because sample is empty + */ + private void checkInterpolation() throws NoDataException { + if (abscissae.isEmpty()) { + throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE); + } + } + + /** Create a polynomial from its coefficients. + * @param c polynomials coefficients + * @return polynomial + */ + private PolynomialFunction polynomial(double ... c) { + return new PolynomialFunction(c); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java new file mode 100644 index 000000000..27cf5139e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.List; +import java.util.ArrayList; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm. + * + * @since 3.6 + */ +public class InterpolatingMicrosphere { + /** Microsphere. */ + private final List microsphere; + /** Microsphere data. */ + private final List microsphereData; + /** Space dimension. */ + private final int dimension; + /** Number of surface elements. */ + private final int size; + /** Maximum fraction of the facets that can be dark. */ + private final double maxDarkFraction; + /** Lowest non-zero illumination. */ + private final double darkThreshold; + /** Background value. */ + private final double background; + + /** + * Create an unitialiazed sphere. + * Sub-classes are responsible for calling the {@code add(double[]) add} + * method in order to initialize all the sphere's facets. + * + * @param dimension Dimension of the data space. + * @param size Number of surface elements of the sphere. + * @param maxDarkFraction Maximum fraction of the facets that can be dark. + * If the fraction of "non-illuminated" facets is larger, no estimation + * of the value will be performed, and the {@code background} value will + * be returned instead. + * @param darkThreshold Value of the illumination below which a facet is + * considered dark. + * @param background Value returned when the {@code maxDarkFraction} + * threshold is exceeded. + * @throws NotStrictlyPositiveException if {@code dimension <= 0} + * or {@code size <= 0}. + * @throws NotPositiveException if {@code darkThreshold < 0}. + * @throws OutOfRangeException if {@code maxDarkFraction} does not + * belong to the interval {@code [0, 1]}. + */ + protected InterpolatingMicrosphere(int dimension, + int size, + double maxDarkFraction, + double darkThreshold, + double background) { + if (dimension <= 0) { + throw new NotStrictlyPositiveException(dimension); + } + if (size <= 0) { + throw new NotStrictlyPositiveException(size); + } + if (maxDarkFraction < 0 || + maxDarkFraction > 1) { + throw new OutOfRangeException(maxDarkFraction, 0, 1); + } + if (darkThreshold < 0) { + throw new NotPositiveException(darkThreshold); + } + + this.dimension = dimension; + this.size = size; + this.maxDarkFraction = maxDarkFraction; + this.darkThreshold = darkThreshold; + this.background = background; + microsphere = new ArrayList(size); + microsphereData = new ArrayList(size); + } + + /** + * Create a sphere from randomly sampled vectors. + * + * @param dimension Dimension of the data space. + * @param size Number of surface elements of the sphere. + * @param rand Unit vector generator for creating the microsphere. + * @param maxDarkFraction Maximum fraction of the facets that can be dark. + * If the fraction of "non-illuminated" facets is larger, no estimation + * of the value will be performed, and the {@code background} value will + * be returned instead. + * @param darkThreshold Value of the illumination below which a facet + * is considered dark. + * @param background Value returned when the {@code maxDarkFraction} + * threshold is exceeded. + * @throws DimensionMismatchException if the size of the generated + * vectors does not match the dimension set in the constructor. + * @throws NotStrictlyPositiveException if {@code dimension <= 0} + * or {@code size <= 0}. + * @throws NotPositiveException if {@code darkThreshold < 0}. + * @throws OutOfRangeException if {@code maxDarkFraction} does not + * belong to the interval {@code [0, 1]}. + */ + public InterpolatingMicrosphere(int dimension, + int size, + double maxDarkFraction, + double darkThreshold, + double background, + UnitSphereRandomVectorGenerator rand) { + this(dimension, size, maxDarkFraction, darkThreshold, background); + + // Generate the microsphere normals, assuming that a number of + // randomly generated normals will represent a sphere. + for (int i = 0; i < size; i++) { + add(rand.nextVector(), false); + } + } + + /** + * Copy constructor. + * + * @param other Instance to copy. + */ + protected InterpolatingMicrosphere(InterpolatingMicrosphere other) { + dimension = other.dimension; + size = other.size; + maxDarkFraction = other.maxDarkFraction; + darkThreshold = other.darkThreshold; + background = other.background; + + // Field can be shared. + microsphere = other.microsphere; + + // Field must be copied. + microsphereData = new ArrayList(size); + for (FacetData fd : other.microsphereData) { + microsphereData.add(new FacetData(fd.illumination(), fd.sample())); + } + } + + /** + * Perform a copy. + * + * @return a copy of this instance. + */ + public InterpolatingMicrosphere copy() { + return new InterpolatingMicrosphere(this); + } + + /** + * Get the space dimensionality. + * + * @return the number of space dimensions. + */ + public int getDimension() { + return dimension; + } + + /** + * Get the size of the sphere. + * + * @return the number of surface elements of the microspshere. + */ + public int getSize() { + return size; + } + + /** + * Estimate the value at the requested location. + * This microsphere is placed at the given {@code point}, contribution + * of the given {@code samplePoints} to each sphere facet is computed + * (illumination) and the interpolation is performed (integration of + * the illumination). + * + * @param point Interpolation point. + * @param samplePoints Sampling data points. + * @param sampleValues Sampling data values at the corresponding + * {@code samplePoints}. + * @param exponent Exponent used in the power law that computes + * the weights (distance dimming factor) of the sample data. + * @param noInterpolationTolerance When the distance between the + * {@code point} and one of the {@code samplePoints} is less than + * this value, no interpolation will be performed, and the value + * of the sample will just be returned. + * @return the estimated value at the given {@code point}. + * @throws NotPositiveException if {@code exponent < 0}. + */ + public double value(double[] point, + double[][] samplePoints, + double[] sampleValues, + double exponent, + double noInterpolationTolerance) { + if (exponent < 0) { + throw new NotPositiveException(exponent); + } + + clear(); + + // Contribution of each sample point to the illumination of the + // microsphere's facets. + final int numSamples = samplePoints.length; + for (int i = 0; i < numSamples; i++) { + // Vector between interpolation point and current sample point. + final double[] diff = MathArrays.ebeSubtract(samplePoints[i], point); + final double diffNorm = MathArrays.safeNorm(diff); + + if (FastMath.abs(diffNorm) < noInterpolationTolerance) { + // No need to interpolate, as the interpolation point is + // actually (very close to) one of the sampled points. + return sampleValues[i]; + } + + final double weight = FastMath.pow(diffNorm, -exponent); + illuminate(diff, sampleValues[i], weight); + } + + return interpolate(); + } + + /** + * Replace {@code i}-th facet of the microsphere. + * Method for initializing the microsphere facets. + * + * @param normal Facet's normal vector. + * @param copy Whether to copy the given array. + * @throws DimensionMismatchException if the length of {@code n} + * does not match the space dimension. + * @throws MaxCountExceededException if the method has been called + * more times than the size of the sphere. + */ + protected void add(double[] normal, + boolean copy) { + if (microsphere.size() >= size) { + throw new MaxCountExceededException(size); + } + if (normal.length > dimension) { + throw new DimensionMismatchException(normal.length, dimension); + } + + microsphere.add(new Facet(copy ? normal.clone() : normal)); + microsphereData.add(new FacetData(0d, 0d)); + } + + /** + * Interpolation. + * + * @return the value estimated from the current illumination of the + * microsphere. + */ + private double interpolate() { + // Number of non-illuminated facets. + int darkCount = 0; + + double value = 0; + double totalWeight = 0; + for (FacetData fd : microsphereData) { + final double iV = fd.illumination(); + if (iV != 0d) { + value += iV * fd.sample(); + totalWeight += iV; + } else { + ++darkCount; + } + } + + final double darkFraction = darkCount / (double) size; + + return darkFraction <= maxDarkFraction ? + value / totalWeight : + background; + } + + /** + * Illumination. + * + * @param sampleDirection Vector whose origin is at the interpolation + * point and tail is at the sample location. + * @param sampleValue Data value of the sample. + * @param weight Weight. + */ + private void illuminate(double[] sampleDirection, + double sampleValue, + double weight) { + for (int i = 0; i < size; i++) { + final double[] n = microsphere.get(i).getNormal(); + final double cos = MathArrays.cosAngle(n, sampleDirection); + + if (cos > 0) { + final double illumination = cos * weight; + + if (illumination > darkThreshold && + illumination > microsphereData.get(i).illumination()) { + microsphereData.set(i, new FacetData(illumination, sampleValue)); + } + } + } + } + + /** + * Reset the all the {@link Facet facets} data to zero. + */ + private void clear() { + for (int i = 0; i < size; i++) { + microsphereData.set(i, new FacetData(0d, 0d)); + } + } + + /** + * Microsphere "facet" (surface element). + */ + private static class Facet { + /** Normal vector characterizing a surface element. */ + private final double[] normal; + + /** + * @param n Normal vector characterizing a surface element + * of the microsphere. No copy is made. + */ + Facet(double[] n) { + normal = n; + } + + /** + * Return a reference to the vector normal to this facet. + * + * @return the normal vector. + */ + public double[] getNormal() { + return normal; + } + } + + /** + * Data associated with each {@link Facet}. + */ + private static class FacetData { + /** Illumination received from the sample. */ + private final double illumination; + /** Data value of the sample. */ + private final double sample; + + /** + * @param illumination Illumination. + * @param sample Data value. + */ + FacetData(double illumination, double sample) { + this.illumination = illumination; + this.sample = sample; + } + + /** + * Get the illumination. + * @return the illumination. + */ + public double illumination() { + return illumination; + } + + /** + * Get the data value. + * @return the data value. + */ + public double sample() { + return sample; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.java new file mode 100644 index 000000000..ea977f665 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.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 com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm. + * For 2D interpolation, this class constructs the microsphere as a series of + * evenly spaced facets (rather than generating random normals as in the + * base implementation). + * + * @since 3.6 + */ +public class InterpolatingMicrosphere2D extends InterpolatingMicrosphere { + /** Space dimension. */ + private static final int DIMENSION = 2; + + /** + * Create a sphere from vectors regularly sampled around a circle. + * + * @param size Number of surface elements of the sphere. + * @param maxDarkFraction Maximum fraction of the facets that can be dark. + * If the fraction of "non-illuminated" facets is larger, no estimation + * of the value will be performed, and the {@code background} value will + * be returned instead. + * @param darkThreshold Value of the illumination below which a facet is + * considered dark. + * @param background Value returned when the {@code maxDarkFraction} + * threshold is exceeded. + * @throws NotStrictlyPositiveException + * if {@code size <= 0}. + * @throws NotPositiveException if + * {@code darkThreshold < 0}. + * @throws OutOfRangeException if + * {@code maxDarkFraction} does not belong to the interval {@code [0, 1]}. + */ + public InterpolatingMicrosphere2D(int size, + double maxDarkFraction, + double darkThreshold, + double background) { + super(DIMENSION, size, maxDarkFraction, darkThreshold, background); + + // Generate the microsphere normals. + for (int i = 0; i < size; i++) { + final double angle = i * MathUtils.TWO_PI / size; + + add(new double[] { FastMath.cos(angle), + FastMath.sin(angle) }, + false); + } + } + + /** + * Copy constructor. + * + * @param other Instance to copy. + */ + protected InterpolatingMicrosphere2D(InterpolatingMicrosphere2D other) { + super(other); + } + + /** + * Perform a copy. + * + * @return a copy of this instance. + */ + @Override + public InterpolatingMicrosphere2D copy() { + return new InterpolatingMicrosphere2D(this); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java new file mode 100644 index 000000000..a38974d71 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implements a linear function for interpolation of real univariate functions. + * + */ +public class LinearInterpolator implements UnivariateInterpolator { + /** + * Computes a linear interpolating function for the data set. + * + * @param x the arguments for the interpolation points + * @param y the values for the interpolation points + * @return a function which interpolates the data set + * @throws DimensionMismatchException if {@code x} and {@code y} + * have different sizes. + * @throws NonMonotonicSequenceException if {@code x} is not sorted in + * strict increasing order. + * @throws NumberIsTooSmallException if the size of {@code x} is smaller + * than 2. + */ + public PolynomialSplineFunction interpolate(double x[], double y[]) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + if (x.length != y.length) { + throw new DimensionMismatchException(x.length, y.length); + } + + if (x.length < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS, + x.length, 2, true); + } + + // Number of intervals. The number of data points is n + 1. + int n = x.length - 1; + + MathArrays.checkOrder(x); + + // Slope of the lines between the datapoints. + final double m[] = new double[n]; + for (int i = 0; i < n; i++) { + m[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]); + } + + final PolynomialFunction polynomials[] = new PolynomialFunction[n]; + final double coefficients[] = new double[2]; + for (int i = 0; i < n; i++) { + coefficients[0] = y[i]; + coefficients[1] = m[i]; + polynomials[i] = new PolynomialFunction(coefficients); + } + + return new PolynomialSplineFunction(x, polynomials); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java new file mode 100644 index 000000000..f10e2ad64 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java @@ -0,0 +1,473 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implements the + * Local Regression Algorithm (also Loess, Lowess) for interpolation of + * real univariate functions. + *

+ * For reference, see + * + * William S. Cleveland - Robust Locally Weighted Regression and Smoothing + * Scatterplots + *

+ * This class implements both the loess method and serves as an interpolation + * adapter to it, allowing one to build a spline on the obtained loess fit. + * + * @since 2.0 + */ +public class LoessInterpolator + implements UnivariateInterpolator, Serializable { + /** Default value of the bandwidth parameter. */ + public static final double DEFAULT_BANDWIDTH = 0.3; + /** Default value of the number of robustness iterations. */ + public static final int DEFAULT_ROBUSTNESS_ITERS = 2; + /** + * Default value for accuracy. + * @since 2.1 + */ + public static final double DEFAULT_ACCURACY = 1e-12; + /** serializable version identifier. */ + private static final long serialVersionUID = 5204927143605193821L; + /** + * The bandwidth parameter: when computing the loess fit at + * a particular point, this fraction of source points closest + * to the current point is taken into account for computing + * a least-squares regression. + *

+ * 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. + *

+ * 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; + } + + /** + * Construct a new {@link LoessInterpolator} + * with given bandwidth and number of robustness iterations. + *

+ * Calling this constructor is equivalent to calling {link {@link + * #LoessInterpolator(double, int, double) LoessInterpolator(bandwidth, + * robustnessIters, LoessInterpolator.DEFAULT_ACCURACY)} + *

+ * + * @param bandwidth when computing the loess fit at + * a particular point, this fraction of source points closest + * to the current point is taken into account for computing + * a least-squares regression. + * A sensible value is usually 0.25 to 0.5, the default value is + * {@link #DEFAULT_BANDWIDTH}. + * @param robustnessIters This many robustness iterations are done. + * A sensible value is usually 0 (just the initial fit without any + * robustness iterations) to 4, the default value is + * {@link #DEFAULT_ROBUSTNESS_ITERS}. + + * @see #LoessInterpolator(double, int, double) + */ + public LoessInterpolator(double bandwidth, int robustnessIters) { + this(bandwidth, robustnessIters, DEFAULT_ACCURACY); + } + + /** + * Construct a new {@link LoessInterpolator} + * with given bandwidth, number of robustness iterations and accuracy. + * + * @param bandwidth when computing the loess fit at + * a particular point, this fraction of source points closest + * to the current point is taken into account for computing + * a least-squares regression. + * A sensible value is usually 0.25 to 0.5, the default value is + * {@link #DEFAULT_BANDWIDTH}. + * @param robustnessIters This many robustness iterations are done. + * A sensible value is usually 0 (just the initial fit without any + * robustness iterations) to 4, the default value is + * {@link #DEFAULT_ROBUSTNESS_ITERS}. + * @param accuracy If the median residual at a certain robustness iteration + * is less than this amount, no more iterations are done. + * @throws OutOfRangeException if bandwidth does not lie in the interval [0,1]. + * @throws NotPositiveException if {@code robustnessIters} is negative. + * @see #LoessInterpolator(double, int) + * @since 2.1 + */ + public LoessInterpolator(double bandwidth, int robustnessIters, double accuracy) + throws OutOfRangeException, + NotPositiveException { + if (bandwidth < 0 || + bandwidth > 1) { + throw new OutOfRangeException(LocalizedFormats.BANDWIDTH, bandwidth, 0, 1); + } + this.bandwidth = bandwidth; + if (robustnessIters < 0) { + throw new NotPositiveException(LocalizedFormats.ROBUSTNESS_ITERATIONS, robustnessIters); + } + this.robustnessIters = robustnessIters; + this.accuracy = accuracy; + } + + /** + * Compute an interpolating function by performing a loess fit + * on the data at the original abscissae and then building a cubic spline + * with a + * {@link SplineInterpolator} + * on the resulting fit. + * + * @param xval the arguments for the interpolation points + * @param yval the values for the interpolation points + * @return A cubic spline built upon a loess fit to the data at the original abscissae + * @throws NonMonotonicSequenceException if {@code xval} not sorted in + * strictly increasing order. + * @throws DimensionMismatchException if {@code xval} and {@code yval} have + * different sizes. + * @throws NoDataException if {@code xval} or {@code yval} has zero size. + * @throws NotFiniteNumberException if any of the arguments and values are + * not finite real numbers. + * @throws NumberIsTooSmallException if the bandwidth is too small to + * accomodate the size of the input data (i.e. the bandwidth must be + * larger than 2/n). + */ + public final PolynomialSplineFunction interpolate(final double[] xval, + final double[] yval) + throws NonMonotonicSequenceException, + DimensionMismatchException, + NoDataException, + NotFiniteNumberException, + NumberIsTooSmallException { + return new SplineInterpolator().interpolate(xval, smooth(xval, yval)); + } + + /** + * Compute a weighted loess fit on the data at the original abscissae. + * + * @param xval Arguments for the interpolation points. + * @param yval Values for the interpolation points. + * @param weights point weights: coefficients by which the robustness weight + * of a point is multiplied. + * @return the values of the loess fit at corresponding original abscissae. + * @throws NonMonotonicSequenceException if {@code xval} not sorted in + * strictly increasing order. + * @throws DimensionMismatchException if {@code xval} and {@code yval} have + * different sizes. + * @throws NoDataException if {@code xval} or {@code yval} has zero size. + * @throws NotFiniteNumberException if any of the arguments and values are + not finite real numbers. + * @throws NumberIsTooSmallException if the bandwidth is too small to + * accomodate the size of the input data (i.e. the bandwidth must be + * larger than 2/n). + * @since 2.1 + */ + public final double[] smooth(final double[] xval, final double[] yval, + final double[] weights) + throws NonMonotonicSequenceException, + DimensionMismatchException, + NoDataException, + NotFiniteNumberException, + NumberIsTooSmallException { + if (xval.length != yval.length) { + throw new DimensionMismatchException(xval.length, yval.length); + } + + final int n = xval.length; + + if (n == 0) { + throw new NoDataException(); + } + + checkAllFiniteReal(xval); + checkAllFiniteReal(yval); + checkAllFiniteReal(weights); + + MathArrays.checkOrder(xval); + + if (n == 1) { + return new double[]{yval[0]}; + } + + if (n == 2) { + return new double[]{yval[0], yval[1]}; + } + + int bandwidthInPoints = (int) (bandwidth * n); + + if (bandwidthInPoints < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.BANDWIDTH, + bandwidthInPoints, 2, true); + } + + final double[] res = new double[n]; + + final double[] residuals = new double[n]; + final double[] sortedResiduals = new double[n]; + + final double[] robustnessWeights = new double[n]; + + // Do an initial fit and 'robustnessIters' robustness iterations. + // This is equivalent to doing 'robustnessIters+1' robustness iterations + // starting with all robustness weights set to 1. + Arrays.fill(robustnessWeights, 1); + + for (int iter = 0; iter <= robustnessIters; ++iter) { + final int[] bandwidthInterval = {0, bandwidthInPoints - 1}; + // At each x, compute a local weighted linear regression + for (int i = 0; i < n; ++i) { + final double x = xval[i]; + + // Find out the interval of source points on which + // a regression is to be made. + if (i > 0) { + updateBandwidthInterval(xval, weights, i, bandwidthInterval); + } + + final int ileft = bandwidthInterval[0]; + final int iright = bandwidthInterval[1]; + + // Compute the point of the bandwidth interval that is + // farthest from x + final int edge; + if (xval[i] - xval[ileft] > xval[iright] - xval[i]) { + edge = ileft; + } else { + edge = iright; + } + + // Compute a least-squares linear fit weighted by + // the product of robustness weights and the tricube + // weight function. + // See http://en.wikipedia.org/wiki/Linear_regression + // (section "Univariate linear case") + // and http://en.wikipedia.org/wiki/Weighted_least_squares + // (section "Weighted least squares") + double sumWeights = 0; + double sumX = 0; + double sumXSquared = 0; + double sumY = 0; + double sumXY = 0; + double denom = FastMath.abs(1.0 / (xval[edge] - x)); + for (int k = ileft; k <= iright; ++k) { + final double xk = xval[k]; + final double yk = yval[k]; + final double dist = (k < i) ? x - xk : xk - x; + final double w = tricube(dist * denom) * robustnessWeights[k] * weights[k]; + final double xkw = xk * w; + sumWeights += w; + sumX += xkw; + sumXSquared += xk * xkw; + sumY += yk * w; + sumXY += yk * xkw; + } + + final double meanX = sumX / sumWeights; + final double meanY = sumY / sumWeights; + final double meanXY = sumXY / sumWeights; + final double meanXSquared = sumXSquared / sumWeights; + + final double beta; + if (FastMath.sqrt(FastMath.abs(meanXSquared - meanX * meanX)) < accuracy) { + beta = 0; + } else { + beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX); + } + + final double alpha = meanY - beta * meanX; + + res[i] = beta * x + alpha; + residuals[i] = FastMath.abs(yval[i] - res[i]); + } + + // No need to recompute the robustness weights at the last + // iteration, they won't be needed anymore + if (iter == robustnessIters) { + break; + } + + // Recompute the robustness weights. + + // Find the median residual. + // An arraycopy and a sort are completely tractable here, + // because the preceding loop is a lot more expensive + System.arraycopy(residuals, 0, sortedResiduals, 0, n); + Arrays.sort(sortedResiduals); + final double medianResidual = sortedResiduals[n / 2]; + + if (FastMath.abs(medianResidual) < accuracy) { + break; + } + + for (int i = 0; i < n; ++i) { + final double arg = residuals[i] / (6 * medianResidual); + if (arg >= 1) { + robustnessWeights[i] = 0; + } else { + final double w = 1 - arg * arg; + robustnessWeights[i] = w * w; + } + } + } + + return res; + } + + /** + * Compute a loess fit on the data at the original abscissae. + * + * @param xval the arguments for the interpolation points + * @param yval the values for the interpolation points + * @return values of the loess fit at corresponding original abscissae + * @throws NonMonotonicSequenceException if {@code xval} not sorted in + * strictly increasing order. + * @throws DimensionMismatchException if {@code xval} and {@code yval} have + * different sizes. + * @throws NoDataException if {@code xval} or {@code yval} has zero size. + * @throws NotFiniteNumberException if any of the arguments and values are + * not finite real numbers. + * @throws NumberIsTooSmallException if the bandwidth is too small to + * accomodate the size of the input data (i.e. the bandwidth must be + * larger than 2/n). + */ + public final double[] smooth(final double[] xval, final double[] yval) + throws NonMonotonicSequenceException, + DimensionMismatchException, + NoDataException, + NotFiniteNumberException, + NumberIsTooSmallException { + if (xval.length != yval.length) { + throw new DimensionMismatchException(xval.length, yval.length); + } + + final double[] unitWeights = new double[xval.length]; + Arrays.fill(unitWeights, 1.0); + + return smooth(xval, yval, unitWeights); + } + + /** + * Given an index interval into xval that embraces a certain number of + * points closest to {@code xval[i-1]}, update the interval so that it + * embraces the same number of points closest to {@code xval[i]}, + * ignoring zero weights. + * + * @param xval Arguments array. + * @param weights Weights array. + * @param i Index around which the new interval should be computed. + * @param bandwidthInterval a two-element array {left, right} such that: + * {@code (left==0 or xval[i] - xval[left-1] > xval[right] - xval[i])} + * and + * {@code (right==xval.length-1 or xval[right+1] - xval[i] > xval[i] - xval[left])}. + * The array will be updated. + */ + private static void updateBandwidthInterval(final double[] xval, final double[] weights, + final int i, + final int[] bandwidthInterval) { + final int left = bandwidthInterval[0]; + final int right = bandwidthInterval[1]; + + // The right edge should be adjusted if the next point to the right + // is closer to xval[i] than the leftmost point of the current interval + int nextRight = nextNonzero(weights, right); + if (nextRight < xval.length && xval[nextRight] - xval[i] < xval[i] - xval[left]) { + int nextLeft = nextNonzero(weights, bandwidthInterval[0]); + bandwidthInterval[0] = nextLeft; + bandwidthInterval[1] = nextRight; + } + } + + /** + * Return the smallest index {@code j} such that + * {@code j > i && (j == weights.length || weights[j] != 0)}. + * + * @param weights Weights array. + * @param i Index from which to start search. + * @return the smallest compliant index. + */ + private static int nextNonzero(final double[] weights, final int i) { + int j = i + 1; + while(j < weights.length && weights[j] == 0) { + ++j; + } + return j; + } + + /** + * Compute the + * tricube + * weight function + * + * @param x Argument. + * @return (1 - |x|3)3 for |x| < 1, 0 otherwise. + */ + private static double tricube(final double x) { + final double absX = FastMath.abs(x); + if (absX >= 1.0) { + return 0.0; + } + final double tmp = 1 - absX * absX * absX; + return tmp * tmp * tmp; + } + + /** + * Check that all elements of an array are finite real numbers. + * + * @param values Values array. + * @throws NotFiniteNumberException + * if one of the values is not a finite real number. + */ + private static void checkAllFiniteReal(final double[] values) { + for (int i = 0; i < values.length; i++) { + MathUtils.checkFinite(values[i]); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java new file mode 100644 index 000000000..83b02a411 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Interpolating function that implements the + * Microsphere Projection. + * + * @deprecated Code will be removed in 4.0. Use {@link InterpolatingMicrosphere} + * and {@link MicrosphereProjectionInterpolator} instead. + */ +@Deprecated +public class MicrosphereInterpolatingFunction + implements MultivariateFunction { + /** + * Space dimension. + */ + private final int dimension; + /** + * Internal accounting data for the interpolation algorithm. + * Each element of the list corresponds to one surface element of + * the microsphere. + */ + private final List microsphere; + /** + * Exponent used in the power law that computes the weights of the + * sample data. + */ + private final double brightnessExponent; + /** + * Sample data. + */ + private final Map 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 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 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 sample() { + return brightestSample; + } + } + + /** + * @param xval Arguments for the interpolation points. + * {@code xval[i][0]} is the first component of interpolation point + * {@code i}, {@code xval[i][1]} is the second component, and so on + * until {@code xval[i][d-1]}, the last component of that interpolation + * point (where {@code dimension} is thus the dimension of the sampled + * space). + * @param yval Values for the interpolation points. + * @param brightnessExponent Brightness dimming factor. + * @param microsphereElements Number of surface elements of the + * microsphere. + * @param rand Unit vector generator for creating the microsphere. + * @throws DimensionMismatchException if the lengths of {@code yval} and + * {@code xval} (equal to {@code n}, the number of interpolation points) + * do not match, or the the arrays {@code xval[0]} ... {@code xval[n]}, + * have lengths different from {@code dimension}. + * @throws NoDataException if there an array has zero-length. + * @throws NullArgumentException if an argument is {@code null}. + */ + public MicrosphereInterpolatingFunction(double[][] xval, + double[] yval, + int brightnessExponent, + int microsphereElements, + UnitSphereRandomVectorGenerator rand) + throws DimensionMismatchException, + NoDataException, + NullArgumentException { + if (xval == null || + yval == null) { + throw new NullArgumentException(); + } + if (xval.length == 0) { + throw new NoDataException(); + } + if (xval.length != yval.length) { + throw new DimensionMismatchException(xval.length, yval.length); + } + if (xval[0] == null) { + throw new NullArgumentException(); + } + + dimension = xval[0].length; + this.brightnessExponent = brightnessExponent; + + // Copy data samples. + samples = new HashMap(yval.length); + for (int i = 0; i < xval.length; ++i) { + final double[] xvalI = xval[i]; + if (xvalI == null) { + throw new NullArgumentException(); + } + if (xvalI.length != dimension) { + throw new DimensionMismatchException(xvalI.length, dimension); + } + + samples.put(new ArrayRealVector(xvalI), yval[i]); + } + + microsphere = new ArrayList(microsphereElements); + // Generate the microsphere, assuming that a fairly large number of + // randomly generated normals will represent a sphere. + for (int i = 0; i < microsphereElements; i++) { + microsphere.add(new MicrosphereSurfaceElement(rand.nextVector())); + } + } + + /** + * @param point Interpolation point. + * @return the interpolated value. + * @throws DimensionMismatchException if point dimension does not math sample + */ + public double value(double[] point) throws DimensionMismatchException { + final RealVector p = new ArrayRealVector(point); + + // Reset. + for (MicrosphereSurfaceElement md : microsphere) { + md.reset(); + } + + // Compute contribution of each sample points to the microsphere elements illumination + for (Map.Entry 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 sd = md.sample(); + if (sd != null) { + value += iV * sd.getValue(); + totalWeight += iV; + } + } + + return value / totalWeight; + } + + /** + * Compute the cosine of the angle between 2 vectors. + * + * @param v Vector. + * @param w Vector. + * @return the cosine of the angle between {@code v} and {@code w}. + */ + private double cosAngle(final RealVector v, final RealVector w) { + return v.dotProduct(w) / (v.getNorm() * w.getNorm()); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java new file mode 100644 index 000000000..e44af38a9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; + +/** + * Interpolator that implements the algorithm described in + * William Dudziak's + * MS thesis. + * + * @since 2.1 + * @deprecated Code will be removed in 4.0. Use {@link InterpolatingMicrosphere} + * and {@link MicrosphereProjectionInterpolator} instead. + */ +@Deprecated +public class MicrosphereInterpolator + implements MultivariateInterpolator { + /** + * Default number of surface elements that composes the microsphere. + */ + public static final int DEFAULT_MICROSPHERE_ELEMENTS = 2000; + /** + * Default exponent used the weights calculation. + */ + public static final int DEFAULT_BRIGHTNESS_EXPONENT = 2; + /** + * Number of surface elements of the microsphere. + */ + private final int microsphereElements; + /** + * Exponent used in the power law that computes the weights of the + * sample data. + */ + private final int brightnessExponent; + + /** + * Create a microsphere interpolator with default settings. + * Calling this constructor is equivalent to call {@link + * #MicrosphereInterpolator(int, int) + * MicrosphereInterpolator(MicrosphereInterpolator.DEFAULT_MICROSPHERE_ELEMENTS, + * MicrosphereInterpolator.DEFAULT_BRIGHTNESS_EXPONENT)}. + */ + public MicrosphereInterpolator() { + this(DEFAULT_MICROSPHERE_ELEMENTS, DEFAULT_BRIGHTNESS_EXPONENT); + } + + /** Create a microsphere interpolator. + * @param elements Number of surface elements of the microsphere. + * @param exponent Exponent used in the power law that computes the + * weights (distance dimming factor) of the sample data. + * @throws NotPositiveException if {@code exponent < 0}. + * @throws NotStrictlyPositiveException if {@code elements <= 0}. + */ + public MicrosphereInterpolator(final int elements, + final int exponent) + throws NotPositiveException, + NotStrictlyPositiveException { + if (exponent < 0) { + throw new NotPositiveException(exponent); + } + if (elements <= 0) { + throw new NotStrictlyPositiveException(elements); + } + + microsphereElements = elements; + brightnessExponent = exponent; + } + + /** + * {@inheritDoc} + */ + public MultivariateFunction interpolate(final double[][] xval, + final double[] yval) + throws DimensionMismatchException, + NoDataException, + NullArgumentException { + final UnitSphereRandomVectorGenerator rand + = new UnitSphereRandomVectorGenerator(xval[0].length); + return new MicrosphereInterpolatingFunction(xval, yval, + brightnessExponent, + microsphereElements, + rand); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java new file mode 100644 index 000000000..c48e7c552 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; + +/** + * Interpolator that implements the algorithm described in + * William Dudziak's + * MS thesis. + * + * @since 3.6 + */ +public class MicrosphereProjectionInterpolator + implements MultivariateInterpolator { + /** Brightness exponent. */ + private final double exponent; + /** Microsphere. */ + private final InterpolatingMicrosphere microsphere; + /** Whether to share the sphere. */ + private final boolean sharedSphere; + /** Tolerance value below which no interpolation is necessary. */ + private final double noInterpolationTolerance; + + /** + * Create a microsphere interpolator. + * + * @param dimension Space dimension. + * @param elements Number of surface elements of the microsphere. + * @param exponent Exponent used in the power law that computes the + * @param maxDarkFraction Maximum fraction of the facets that can be dark. + * If the fraction of "non-illuminated" facets is larger, no estimation + * of the value will be performed, and the {@code background} value will + * be returned instead. + * @param darkThreshold Value of the illumination below which a facet is + * considered dark. + * @param background Value returned when the {@code maxDarkFraction} + * threshold is exceeded. + * @param sharedSphere Whether the sphere can be shared among the + * interpolating function instances. If {@code true}, the instances + * will share the same data, and thus will not be thread-safe. + * @param noInterpolationTolerance When the distance between an + * interpolated point and one of the sample points is less than this + * value, no interpolation will be performed (the value of the sample + * will be returned). + * @throws NotStrictlyPositiveException + * if {@code dimension <= 0} or {@code elements <= 0}. + * @throws NotPositiveException if {@code exponent < 0}. + * @throws NotPositiveException if {@code darkThreshold < 0}. + * @throws OutOfRangeException if + * {@code maxDarkFraction} does not belong to the interval {@code [0, 1]}. + */ + public MicrosphereProjectionInterpolator(int dimension, + int elements, + double maxDarkFraction, + double darkThreshold, + double background, + double exponent, + boolean sharedSphere, + double noInterpolationTolerance) { + this(new InterpolatingMicrosphere(dimension, + elements, + maxDarkFraction, + darkThreshold, + background, + new UnitSphereRandomVectorGenerator(dimension)), + exponent, + sharedSphere, + noInterpolationTolerance); + } + + /** + * Create a microsphere interpolator. + * + * @param microsphere Microsphere. + * @param exponent Exponent used in the power law that computes the + * weights (distance dimming factor) of the sample data. + * @param sharedSphere Whether the sphere can be shared among the + * interpolating function instances. If {@code true}, the instances + * will share the same data, and thus will not be thread-safe. + * @param noInterpolationTolerance When the distance between an + * interpolated point and one of the sample points is less than this + * value, no interpolation will be performed (the value of the sample + * will be returned). + * @throws NotPositiveException if {@code exponent < 0}. + */ + public MicrosphereProjectionInterpolator(InterpolatingMicrosphere microsphere, + double exponent, + boolean sharedSphere, + double noInterpolationTolerance) + throws NotPositiveException { + if (exponent < 0) { + throw new NotPositiveException(exponent); + } + + this.microsphere = microsphere; + this.exponent = exponent; + this.sharedSphere = sharedSphere; + this.noInterpolationTolerance = noInterpolationTolerance; + } + + /** + * {@inheritDoc} + * + * @throws DimensionMismatchException if the space dimension of the + * given samples does not match the space dimension of the microsphere. + */ + public MultivariateFunction interpolate(final double[][] xval, + final double[] yval) + throws DimensionMismatchException, + NoDataException, + NullArgumentException { + if (xval == null || + yval == null) { + throw new NullArgumentException(); + } + if (xval.length == 0) { + throw new NoDataException(); + } + if (xval.length != yval.length) { + throw new DimensionMismatchException(xval.length, yval.length); + } + if (xval[0] == null) { + throw new NullArgumentException(); + } + final int dimension = microsphere.getDimension(); + if (dimension != xval[0].length) { + throw new DimensionMismatchException(xval[0].length, dimension); + } + + // Microsphere copy. + final InterpolatingMicrosphere m = sharedSphere ? microsphere : microsphere.copy(); + + return new MultivariateFunction() { + /** {inheritDoc} */ + public double value(double[] point) { + return m.value(point, + xval, + yval, + exponent, + noInterpolationTolerance); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java new file mode 100644 index 000000000..a353a0c6b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * Interface representing a univariate real interpolating function. + * + * @since 2.1 + */ +public interface MultivariateInterpolator { + + /** + * Computes an interpolating function for the data set. + * + * @param xval the arguments for the interpolation points. + * {@code xval[i][0]} is the first component of interpolation point + * {@code i}, {@code xval[i][1]} is the second component, and so on + * until {@code xval[i][d-1]}, the last component of that interpolation + * point (where {@code d} is thus the dimension of the space). + * @param yval the values for the interpolation points + * @return a function which interpolates the data set + * @throws MathIllegalArgumentException if the arguments violate assumptions + * made by the interpolation algorithm. + * @throws DimensionMismatchException when the array dimensions are not consistent. + * @throws NoDataException if an array has zero-length. + * @throws NullArgumentException if the arguments are {@code null}. + */ + MultivariateFunction interpolate(double[][] xval, double[] yval) + throws MathIllegalArgumentException, DimensionMismatchException, + NoDataException, NullArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java new file mode 100644 index 000000000..53dbf6aa9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm; + +/** + * Implements the + * Neville's Algorithm for interpolation of real univariate functions. For + * reference, see Introduction to Numerical Analysis, ISBN 038795452X, + * chapter 2. + *

+ * The actual code of Neville's algorithm is in PolynomialFunctionLagrangeForm, + * this class provides an easy-to-use interface to it.

+ * + * @since 1.2 + */ +public class NevilleInterpolator implements UnivariateInterpolator, + Serializable { + + /** serializable version identifier */ + static final long serialVersionUID = 3003707660147873733L; + + /** + * Computes an interpolating function for the data set. + * + * @param x Interpolating points. + * @param y Interpolating values. + * @return a function which interpolates the data set + * @throws DimensionMismatchException if the array lengths are different. + * @throws NumberIsTooSmallException if the number of points is less than 2. + * @throws NonMonotonicSequenceException if two abscissae have the same + * value. + */ + public PolynomialFunctionLagrangeForm interpolate(double x[], double y[]) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + return new PolynomialFunctionLagrangeForm(x, y); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.java new file mode 100644 index 000000000..5c15f87d1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.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 com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.BivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Function that implements the + * bicubic spline + * interpolation. + * This implementation currently uses {@link AkimaSplineInterpolator} as the + * underlying one-dimensional interpolator, which requires 5 sample points; + * insufficient data will raise an exception when the + * {@link #value(double,double) value} method is called. + * + * @since 3.4 + */ +public class PiecewiseBicubicSplineInterpolatingFunction + implements BivariateFunction { + /** The minimum number of points that are needed to compute the function. */ + private static final int MIN_NUM_POINTS = 5; + /** Samples x-coordinates */ + private final double[] xval; + /** Samples y-coordinates */ + private final double[] yval; + /** Set of cubic splines patching the whole data grid */ + private final double[][] fval; + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. the expected number + * of elements. + * @throws NonMonotonicSequenceException if {@code x} or {@code y} are not + * strictly increasing. + * @throws NullArgumentException if any of the arguments are null + * @throws NoDataException if any of the arrays has zero length. + * @throws DimensionMismatchException if the length of x and y don't match the row, column + * height of f + */ + public PiecewiseBicubicSplineInterpolatingFunction(double[] x, + double[] y, + double[][] f) + throws DimensionMismatchException, + NullArgumentException, + NoDataException, + NonMonotonicSequenceException { + if (x == null || + y == null || + f == null || + f[0] == null) { + throw new NullArgumentException(); + } + + final int xLen = x.length; + final int yLen = y.length; + + if (xLen == 0 || + yLen == 0 || + f.length == 0 || + f[0].length == 0) { + throw new NoDataException(); + } + + if (xLen < MIN_NUM_POINTS || + yLen < MIN_NUM_POINTS || + f.length < MIN_NUM_POINTS || + f[0].length < MIN_NUM_POINTS) { + throw new InsufficientDataException(); + } + + if (xLen != f.length) { + throw new DimensionMismatchException(xLen, f.length); + } + + if (yLen != f[0].length) { + throw new DimensionMismatchException(yLen, f[0].length); + } + + MathArrays.checkOrder(x); + MathArrays.checkOrder(y); + + xval = x.clone(); + yval = y.clone(); + fval = f.clone(); + } + + /** + * {@inheritDoc} + */ + public double value(double x, + double y) + throws OutOfRangeException { + final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator(); + final int offset = 2; + final int count = offset + 3; + final int i = searchIndex(x, xval, offset, count); + final int j = searchIndex(y, yval, offset, count); + + final double xArray[] = new double[count]; + final double yArray[] = new double[count]; + final double zArray[] = new double[count]; + final double interpArray[] = new double[count]; + + for (int index = 0; index < count; index++) { + xArray[index] = xval[i + index]; + yArray[index] = yval[j + index]; + } + + for (int zIndex = 0; zIndex < count; zIndex++) { + for (int index = 0; index < count; index++) { + zArray[index] = fval[i + index][j + zIndex]; + } + final PolynomialSplineFunction spline = interpolator.interpolate(xArray, zArray); + interpArray[zIndex] = spline.value(x); + } + + final PolynomialSplineFunction spline = interpolator.interpolate(yArray, interpArray); + + double returnValue = spline.value(y); + + return returnValue; + } + + /** + * Indicates whether a point is within the interpolation range. + * + * @param x First coordinate. + * @param y Second coordinate. + * @return {@code true} if (x, y) is a valid point. + * @since 3.3 + */ + public boolean isValidPoint(double x, + double y) { + if (x < xval[0] || + x > xval[xval.length - 1] || + y < yval[0] || + y > yval[yval.length - 1]) { + return false; + } else { + return true; + } + } + + /** + * @param c Coordinate. + * @param val Coordinate samples. + * @param offset how far back from found value to offset for querying + * @param count total number of elements forward from beginning that will be + * queried + * @return the index in {@code val} corresponding to the interval containing + * {@code c}. + * @throws OutOfRangeException if {@code c} is out of the range defined by + * the boundary values of {@code val}. + */ + private int searchIndex(double c, + double[] val, + int offset, + int count) { + int r = Arrays.binarySearch(val, c); + + if (r == -1 || r == -val.length - 1) { + throw new OutOfRangeException(c, val[0], val[val.length - 1]); + } + + if (r < 0) { + // "c" in within an interpolation sub-interval, which returns + // negative + // need to remove the negative sign for consistency + r = -r - offset - 1; + } else { + r -= offset; + } + + if (r < 0) { + r = 0; + } + + if ((r + count) >= val.length) { + // "c" is the last sample of the range: Return the index + // of the sample at the lower end of the last sub-interval. + r = val.length - count; + } + + return r; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java new file mode 100644 index 000000000..176ffbd01 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Generates a piecewise-bicubic interpolating function. + * + * @since 2.2 + */ +public class PiecewiseBicubicSplineInterpolator + implements BivariateGridInterpolator { + + /** + * {@inheritDoc} + */ + public PiecewiseBicubicSplineInterpolatingFunction interpolate( final double[] xval, + final double[] yval, + final double[][] fval) + throws DimensionMismatchException, + NullArgumentException, + NoDataException, + NonMonotonicSequenceException { + if ( xval == null || + yval == null || + fval == null || + fval[0] == null ) { + throw new NullArgumentException(); + } + + if ( xval.length == 0 || + yval.length == 0 || + fval.length == 0 ) { + throw new NoDataException(); + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + + return new PiecewiseBicubicSplineInterpolatingFunction( xval, yval, fval ); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java new file mode 100644 index 000000000..0f46290dd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian.GaussNewtonOptimizer; +import com.fr.third.org.apache.commons.math3.fitting.PolynomialFitter; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.optim.SimpleVectorValueChecker; + +/** + * Generates a bicubic interpolation function. + * Prior to generating the interpolating function, the input is smoothed using + * polynomial fitting. + * + * @since 2.2 + * @deprecated To be removed in 4.0 (see MATH-1166). + */ +@Deprecated +public class SmoothingPolynomialBicubicSplineInterpolator + extends BicubicSplineInterpolator { + /** Fitter for x. */ + private final PolynomialFitter xFitter; + /** Degree of the fitting polynomial. */ + private final int xDegree; + /** Fitter for y. */ + private final PolynomialFitter yFitter; + /** Degree of the fitting polynomial. */ + private final int yDegree; + + /** + * Default constructor. The degree of the fitting polynomials is set to 3. + */ + public SmoothingPolynomialBicubicSplineInterpolator() { + this(3); + } + + /** + * @param degree Degree of the polynomial fitting functions. + * @exception NotPositiveException if degree is not positive + */ + public SmoothingPolynomialBicubicSplineInterpolator(int degree) + throws NotPositiveException { + this(degree, degree); + } + + /** + * @param xDegree Degree of the polynomial fitting functions along the + * x-dimension. + * @param yDegree Degree of the polynomial fitting functions along the + * y-dimension. + * @exception NotPositiveException if degrees are not positive + */ + public SmoothingPolynomialBicubicSplineInterpolator(int xDegree, int yDegree) + throws NotPositiveException { + if (xDegree < 0) { + throw new NotPositiveException(xDegree); + } + if (yDegree < 0) { + throw new NotPositiveException(yDegree); + } + this.xDegree = xDegree; + this.yDegree = yDegree; + + final double safeFactor = 1e2; + final SimpleVectorValueChecker checker + = new SimpleVectorValueChecker(safeFactor * Precision.EPSILON, + safeFactor * Precision.SAFE_MIN); + xFitter = new PolynomialFitter(new GaussNewtonOptimizer(false, checker)); + yFitter = new PolynomialFitter(new GaussNewtonOptimizer(false, checker)); + } + + /** + * {@inheritDoc} + */ + @Override + public BicubicSplineInterpolatingFunction interpolate(final double[] xval, + final double[] yval, + final double[][] fval) + throws NoDataException, NullArgumentException, + DimensionMismatchException, NonMonotonicSequenceException { + if (xval.length == 0 || yval.length == 0 || fval.length == 0) { + throw new NoDataException(); + } + if (xval.length != fval.length) { + throw new DimensionMismatchException(xval.length, fval.length); + } + + final int xLen = xval.length; + final int yLen = yval.length; + + for (int i = 0; i < xLen; i++) { + if (fval[i].length != yLen) { + throw new DimensionMismatchException(fval[i].length, yLen); + } + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + + // For each line y[j] (0 <= j < yLen), construct a polynomial, with + // respect to variable x, fitting array fval[][j] + final PolynomialFunction[] yPolyX = new PolynomialFunction[yLen]; + for (int j = 0; j < yLen; j++) { + xFitter.clearObservations(); + for (int i = 0; i < xLen; i++) { + xFitter.addObservedPoint(1, xval[i], fval[i][j]); + } + + // Initial guess for the fit is zero for each coefficients (of which + // there are "xDegree" + 1). + yPolyX[j] = new PolynomialFunction(xFitter.fit(new double[xDegree + 1])); + } + + // For every knot (xval[i], yval[j]) of the grid, calculate corrected + // values fval_1 + final double[][] fval_1 = new double[xLen][yLen]; + for (int j = 0; j < yLen; j++) { + final PolynomialFunction f = yPolyX[j]; + for (int i = 0; i < xLen; i++) { + fval_1[i][j] = f.value(xval[i]); + } + } + + // For each line x[i] (0 <= i < xLen), construct a polynomial, with + // respect to variable y, fitting array fval_1[i][] + final PolynomialFunction[] xPolyY = new PolynomialFunction[xLen]; + for (int i = 0; i < xLen; i++) { + yFitter.clearObservations(); + for (int j = 0; j < yLen; j++) { + yFitter.addObservedPoint(1, yval[j], fval_1[i][j]); + } + + // Initial guess for the fit is zero for each coefficients (of which + // there are "yDegree" + 1). + xPolyY[i] = new PolynomialFunction(yFitter.fit(new double[yDegree + 1])); + } + + // For every knot (xval[i], yval[j]) of the grid, calculate corrected + // values fval_2 + final double[][] fval_2 = new double[xLen][yLen]; + for (int i = 0; i < xLen; i++) { + final PolynomialFunction f = xPolyY[i]; + for (int j = 0; j < yLen; j++) { + fval_2[i][j] = f.value(yval[j]); + } + } + + return super.interpolate(xval, yval, fval_2); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java new file mode 100644 index 000000000..2376a9528 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Computes a natural (also known as "free", "unclamped") cubic spline interpolation for the data set. + *

+ * The {@link #interpolate(double[], double[])} method returns a {@link PolynomialSplineFunction} + * consisting of n cubic polynomials, defined over the subintervals determined by the x values, + * {@code x[0] < x[i] ... < x[n].} The x values are referred to as "knot points." + *

+ * 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 x - x[i] where + * i is the index of the subinterval. See {@link PolynomialSplineFunction} for more details. + *

+ *

+ * The interpolating polynomials satisfy:

    + *
  1. The value of the PolynomialSplineFunction at each of the input x values equals the + * corresponding y value.
  2. + *
  3. 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).
  4. + *
+ *

+ * The cubic spline interpolation algorithm implemented is as described in R.L. Burden, J.D. Faires, + * Numerical Analysis, 4th Ed., 1989, PWS-Kent, ISBN 0-53491-585-X, pp 126-131. + *

+ * + */ +public class SplineInterpolator implements UnivariateInterpolator { + /** + * Computes an interpolating function for the data set. + * @param x the arguments for the interpolation points + * @param y the values for the interpolation points + * @return a function which interpolates the data set + * @throws DimensionMismatchException if {@code x} and {@code y} + * have different sizes. + * @throws NonMonotonicSequenceException if {@code x} is not sorted in + * strict increasing order. + * @throws NumberIsTooSmallException if the size of {@code x} is smaller + * than 3. + */ + public PolynomialSplineFunction interpolate(double x[], double y[]) + throws DimensionMismatchException, + NumberIsTooSmallException, + NonMonotonicSequenceException { + if (x.length != y.length) { + throw new DimensionMismatchException(x.length, y.length); + } + + if (x.length < 3) { + throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS, + x.length, 3, true); + } + + // Number of intervals. The number of data points is n + 1. + final int n = x.length - 1; + + MathArrays.checkOrder(x); + + // Differences between knot points + final double h[] = new double[n]; + for (int i = 0; i < n; i++) { + h[i] = x[i + 1] - x[i]; + } + + final double mu[] = new double[n]; + final double z[] = new double[n + 1]; + mu[0] = 0d; + z[0] = 0d; + double g = 0; + for (int i = 1; i < n; i++) { + g = 2d * (x[i+1] - x[i - 1]) - h[i - 1] * mu[i -1]; + mu[i] = h[i] / g; + z[i] = (3d * (y[i + 1] * h[i - 1] - y[i] * (x[i + 1] - x[i - 1])+ y[i - 1] * h[i]) / + (h[i - 1] * h[i]) - h[i - 1] * z[i - 1]) / g; + } + + // cubic spline coefficients -- b is linear, c quadratic, d is cubic (original y's are constants) + final double b[] = new double[n]; + final double c[] = new double[n + 1]; + final double d[] = new double[n]; + + z[n] = 0d; + c[n] = 0d; + + for (int j = n -1; j >=0; j--) { + c[j] = z[j] - mu[j] * c[j + 1]; + b[j] = (y[j + 1] - y[j]) / h[j] - h[j] * (c[j + 1] + 2d * c[j]) / 3d; + d[j] = (c[j + 1] - c[j]) / (3d * h[j]); + } + + final PolynomialFunction polynomials[] = new PolynomialFunction[n]; + final double coefficients[] = new double[4]; + for (int i = 0; i < n; i++) { + coefficients[0] = y[i]; + coefficients[1] = b[i]; + coefficients[2] = c[i]; + coefficients[3] = d[i]; + polynomials[i] = new PolynomialFunction(coefficients); + } + + return new PolynomialSplineFunction(x, polynomials); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java new file mode 100644 index 000000000..929f802ae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java @@ -0,0 +1,508 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.TrivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Function that implements the + * + * tricubic spline interpolation, as proposed in + *
+ * Tricubic interpolation in three dimensions, + * F. Lekien and J. Marsden, + * Int. J. Numer. Meth. Eng 2005; 63:455-471 + *
+ * + * @since 3.4. + */ +public class TricubicInterpolatingFunction + implements TrivariateFunction { + /** + * Matrix to compute the spline coefficients from the function values + * and function derivatives values + */ + private static final double[][] AINV = { + { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 9,-9,-9,9,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,6,-6,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,0,0,0,0,0,0,0,0,-2,-2,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,6,-6,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 4,-4,-4,4,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,-9,9,0,0,0,0,0,0,0,0,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,4,2,2,1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,-2,-2,-1,-1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,-2,-1,-2,-1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,-4,4,0,0,0,0,0,0,0,0,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,1,1,1,1,0,0,0,0 }, + {-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 9,-9,0,0,-9,9,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,0,0,0,0,0,0,0,0,4,2,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,0,0,6,-6,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,0,0,0,0,0,0,0,0,-2,-2,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,0,0,-9,9,0,0,0,0,0,0,0,0,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,4,2,0,0,2,1,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,-2,-2,0,0,-1,-1,0,0 }, + { 9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0 }, + { -27,27,27,-27,27,-27,-27,27,-18,-9,18,9,18,9,-18,-9,-18,18,-9,9,18,-18,9,-9,-18,18,18,-18,-9,9,9,-9,-12,-6,-6,-3,12,6,6,3,-12,-6,12,6,-6,-3,6,3,-12,12,-6,6,-6,6,-3,3,-8,-4,-4,-2,-4,-2,-2,-1 }, + { 18,-18,-18,18,-18,18,18,-18,9,9,-9,-9,-9,-9,9,9,12,-12,6,-6,-12,12,-6,6,12,-12,-12,12,6,-6,-6,6,6,6,3,3,-6,-6,-3,-3,6,6,-6,-6,3,3,-3,-3,8,-8,4,-4,4,-4,2,-2,4,4,2,2,2,2,1,1 }, + { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0 }, + { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,9,-9,9,-9,-9,9,-9,9,12,-12,-12,12,6,-6,-6,6,6,3,6,3,-6,-3,-6,-3,8,4,-8,-4,4,2,-4,-2,6,-6,6,-6,3,-3,3,-3,4,2,4,2,2,1,2,1 }, + { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-6,6,-6,6,6,-6,6,-6,-8,8,8,-8,-4,4,4,-4,-3,-3,-3,-3,3,3,3,3,-4,-4,4,4,-2,-2,2,2,-4,4,-4,4,-2,2,-2,2,-2,-2,-2,-2,-1,-1,-1,-1 }, + { 2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,0,0,6,-6,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 4,-4,0,0,-4,4,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,-2,-1,0,0,-2,-1,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,0,0,-4,4,0,0,0,0,0,0,0,0,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,1,1,0,0,1,1,0,0 }, + { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0 }, + { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,12,-12,6,-6,-12,12,-6,6,9,-9,-9,9,9,-9,-9,9,8,4,4,2,-8,-4,-4,-2,6,3,-6,-3,6,3,-6,-3,6,-6,3,-3,6,-6,3,-3,4,2,2,1,4,2,2,1 }, + { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-8,8,-4,4,8,-8,4,-4,-6,6,6,-6,-6,6,6,-6,-4,-4,-2,-2,4,4,2,2,-3,-3,3,3,-3,-3,3,3,-4,4,-2,2,-4,4,-2,2,-2,-2,-1,-1,-2,-2,-1,-1 }, + { 4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0 }, + { -12,12,12,-12,12,-12,-12,12,-8,-4,8,4,8,4,-8,-4,-6,6,-6,6,6,-6,6,-6,-6,6,6,-6,-6,6,6,-6,-4,-2,-4,-2,4,2,4,2,-4,-2,4,2,-4,-2,4,2,-3,3,-3,3,-3,3,-3,3,-2,-1,-2,-1,-2,-1,-2,-1 }, + { 8,-8,-8,8,-8,8,8,-8,4,4,-4,-4,-4,-4,4,4,4,-4,4,-4,-4,4,-4,4,4,-4,-4,4,4,-4,-4,4,2,2,2,2,-2,-2,-2,-2,2,2,-2,-2,2,2,-2,-2,2,-2,2,-2,2,-2,2,-2,1,1,1,1,1,1,1,1 } + }; + + /** Samples x-coordinates */ + private final double[] xval; + /** Samples y-coordinates */ + private final double[] yval; + /** Samples z-coordinates */ + private final double[] zval; + /** Set of cubic splines patching the whole data grid */ + private final TricubicFunction[][][] splines; + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param z Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. + * @param dFdX Values of the partial derivative of function with respect to x on every grid point. + * @param dFdY Values of the partial derivative of function with respect to y on every grid point. + * @param dFdZ Values of the partial derivative of function with respect to z on every grid point. + * @param d2FdXdY Values of the cross partial derivative of function on every grid point. + * @param d2FdXdZ Values of the cross partial derivative of function on every grid point. + * @param d2FdYdZ Values of the cross partial derivative of function on every grid point. + * @param d3FdXdYdZ Values of the cross partial derivative of function on every grid point. + * @throws NoDataException if any of the arrays has zero length. + * @throws DimensionMismatchException if the various arrays do not contain the expected number of elements. + * @throws NonMonotonicSequenceException if {@code x}, {@code y} or {@code z} are not strictly increasing. + */ + public TricubicInterpolatingFunction(double[] x, + double[] y, + double[] z, + double[][][] f, + double[][][] dFdX, + double[][][] dFdY, + double[][][] dFdZ, + double[][][] d2FdXdY, + double[][][] d2FdXdZ, + double[][][] d2FdYdZ, + double[][][] d3FdXdYdZ) + throws NoDataException, + DimensionMismatchException, + NonMonotonicSequenceException { + final int xLen = x.length; + final int yLen = y.length; + final int zLen = z.length; + + if (xLen == 0 || yLen == 0 || z.length == 0 || f.length == 0 || f[0].length == 0) { + throw new NoDataException(); + } + if (xLen != f.length) { + throw new DimensionMismatchException(xLen, f.length); + } + if (xLen != dFdX.length) { + throw new DimensionMismatchException(xLen, dFdX.length); + } + if (xLen != dFdY.length) { + throw new DimensionMismatchException(xLen, dFdY.length); + } + if (xLen != dFdZ.length) { + throw new DimensionMismatchException(xLen, dFdZ.length); + } + if (xLen != d2FdXdY.length) { + throw new DimensionMismatchException(xLen, d2FdXdY.length); + } + if (xLen != d2FdXdZ.length) { + throw new DimensionMismatchException(xLen, d2FdXdZ.length); + } + if (xLen != d2FdYdZ.length) { + throw new DimensionMismatchException(xLen, d2FdYdZ.length); + } + if (xLen != d3FdXdYdZ.length) { + throw new DimensionMismatchException(xLen, d3FdXdYdZ.length); + } + + MathArrays.checkOrder(x); + MathArrays.checkOrder(y); + MathArrays.checkOrder(z); + + xval = x.clone(); + yval = y.clone(); + zval = z.clone(); + + final int lastI = xLen - 1; + final int lastJ = yLen - 1; + final int lastK = zLen - 1; + splines = new TricubicFunction[lastI][lastJ][lastK]; + + for (int i = 0; i < lastI; i++) { + if (f[i].length != yLen) { + throw new DimensionMismatchException(f[i].length, yLen); + } + if (dFdX[i].length != yLen) { + throw new DimensionMismatchException(dFdX[i].length, yLen); + } + if (dFdY[i].length != yLen) { + throw new DimensionMismatchException(dFdY[i].length, yLen); + } + if (dFdZ[i].length != yLen) { + throw new DimensionMismatchException(dFdZ[i].length, yLen); + } + if (d2FdXdY[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdY[i].length, yLen); + } + if (d2FdXdZ[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdZ[i].length, yLen); + } + if (d2FdYdZ[i].length != yLen) { + throw new DimensionMismatchException(d2FdYdZ[i].length, yLen); + } + if (d3FdXdYdZ[i].length != yLen) { + throw new DimensionMismatchException(d3FdXdYdZ[i].length, yLen); + } + + final int ip1 = i + 1; + final double xR = xval[ip1] - xval[i]; + for (int j = 0; j < lastJ; j++) { + if (f[i][j].length != zLen) { + throw new DimensionMismatchException(f[i][j].length, zLen); + } + if (dFdX[i][j].length != zLen) { + throw new DimensionMismatchException(dFdX[i][j].length, zLen); + } + if (dFdY[i][j].length != zLen) { + throw new DimensionMismatchException(dFdY[i][j].length, zLen); + } + if (dFdZ[i][j].length != zLen) { + throw new DimensionMismatchException(dFdZ[i][j].length, zLen); + } + if (d2FdXdY[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdXdY[i][j].length, zLen); + } + if (d2FdXdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdXdZ[i][j].length, zLen); + } + if (d2FdYdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdYdZ[i][j].length, zLen); + } + if (d3FdXdYdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d3FdXdYdZ[i][j].length, zLen); + } + + final int jp1 = j + 1; + final double yR = yval[jp1] - yval[j]; + final double xRyR = xR * yR; + for (int k = 0; k < lastK; k++) { + final int kp1 = k + 1; + final double zR = zval[kp1] - zval[k]; + final double xRzR = xR * zR; + final double yRzR = yR * zR; + final double xRyRzR = xR * yRzR; + + final double[] beta = new double[] { + f[i][j][k], f[ip1][j][k], + f[i][jp1][k], f[ip1][jp1][k], + f[i][j][kp1], f[ip1][j][kp1], + f[i][jp1][kp1], f[ip1][jp1][kp1], + + dFdX[i][j][k] * xR, dFdX[ip1][j][k] * xR, + dFdX[i][jp1][k] * xR, dFdX[ip1][jp1][k] * xR, + dFdX[i][j][kp1] * xR, dFdX[ip1][j][kp1] * xR, + dFdX[i][jp1][kp1] * xR, dFdX[ip1][jp1][kp1] * xR, + + dFdY[i][j][k] * yR, dFdY[ip1][j][k] * yR, + dFdY[i][jp1][k] * yR, dFdY[ip1][jp1][k] * yR, + dFdY[i][j][kp1] * yR, dFdY[ip1][j][kp1] * yR, + dFdY[i][jp1][kp1] * yR, dFdY[ip1][jp1][kp1] * yR, + + dFdZ[i][j][k] * zR, dFdZ[ip1][j][k] * zR, + dFdZ[i][jp1][k] * zR, dFdZ[ip1][jp1][k] * zR, + dFdZ[i][j][kp1] * zR, dFdZ[ip1][j][kp1] * zR, + dFdZ[i][jp1][kp1] * zR, dFdZ[ip1][jp1][kp1] * zR, + + d2FdXdY[i][j][k] * xRyR, d2FdXdY[ip1][j][k] * xRyR, + d2FdXdY[i][jp1][k] * xRyR, d2FdXdY[ip1][jp1][k] * xRyR, + d2FdXdY[i][j][kp1] * xRyR, d2FdXdY[ip1][j][kp1] * xRyR, + d2FdXdY[i][jp1][kp1] * xRyR, d2FdXdY[ip1][jp1][kp1] * xRyR, + + d2FdXdZ[i][j][k] * xRzR, d2FdXdZ[ip1][j][k] * xRzR, + d2FdXdZ[i][jp1][k] * xRzR, d2FdXdZ[ip1][jp1][k] * xRzR, + d2FdXdZ[i][j][kp1] * xRzR, d2FdXdZ[ip1][j][kp1] * xRzR, + d2FdXdZ[i][jp1][kp1] * xRzR, d2FdXdZ[ip1][jp1][kp1] * xRzR, + + d2FdYdZ[i][j][k] * yRzR, d2FdYdZ[ip1][j][k] * yRzR, + d2FdYdZ[i][jp1][k] * yRzR, d2FdYdZ[ip1][jp1][k] * yRzR, + d2FdYdZ[i][j][kp1] * yRzR, d2FdYdZ[ip1][j][kp1] * yRzR, + d2FdYdZ[i][jp1][kp1] * yRzR, d2FdYdZ[ip1][jp1][kp1] * yRzR, + + d3FdXdYdZ[i][j][k] * xRyRzR, d3FdXdYdZ[ip1][j][k] * xRyRzR, + d3FdXdYdZ[i][jp1][k] * xRyRzR, d3FdXdYdZ[ip1][jp1][k] * xRyRzR, + d3FdXdYdZ[i][j][kp1] * xRyRzR, d3FdXdYdZ[ip1][j][kp1] * xRyRzR, + d3FdXdYdZ[i][jp1][kp1] * xRyRzR, d3FdXdYdZ[ip1][jp1][kp1] * xRyRzR, + }; + + splines[i][j][k] = new TricubicFunction(computeCoefficients(beta)); + } + } + } + } + + /** + * {@inheritDoc} + * + * @throws OutOfRangeException if any of the variables is outside its interpolation range. + */ + public double value(double x, double y, double z) + throws OutOfRangeException { + final int i = searchIndex(x, xval); + if (i == -1) { + throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]); + } + final int j = searchIndex(y, yval); + if (j == -1) { + throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]); + } + final int k = searchIndex(z, zval); + if (k == -1) { + throw new OutOfRangeException(z, zval[0], zval[zval.length - 1]); + } + + final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]); + final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]); + final double zN = (z - zval[k]) / (zval[k + 1] - zval[k]); + + return splines[i][j][k].value(xN, yN, zN); + } + + /** + * Indicates whether a point is within the interpolation range. + * + * @param x First coordinate. + * @param y Second coordinate. + * @param z Third coordinate. + * @return {@code true} if (x, y, z) is a valid point. + */ + public boolean isValidPoint(double x, double y, double z) { + if (x < xval[0] || + x > xval[xval.length - 1] || + y < yval[0] || + y > yval[yval.length - 1] || + z < zval[0] || + z > zval[zval.length - 1]) { + return false; + } else { + return true; + } + } + + /** + * @param c Coordinate. + * @param val Coordinate samples. + * @return the index in {@code val} corresponding to the interval containing {@code c}, or {@code -1} + * if {@code c} is out of the range defined by the end values of {@code val}. + */ + private int searchIndex(double c, double[] val) { + if (c < val[0]) { + return -1; + } + + final int max = val.length; + for (int i = 1; i < max; i++) { + if (c <= val[i]) { + return i - 1; + } + } + + return -1; + } + + /** + * Compute the spline coefficients from the list of function values and + * function partial derivatives values at the four corners of a grid + * element. They must be specified in the following order: + *
    + *
  • f(0,0,0)
  • + *
  • f(1,0,0)
  • + *
  • f(0,1,0)
  • + *
  • f(1,1,0)
  • + *
  • f(0,0,1)
  • + *
  • f(1,0,1)
  • + *
  • f(0,1,1)
  • + *
  • f(1,1,1)
  • + * + *
  • fx(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fx(1,1,1)
  • + * + *
  • fy(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fy(1,1,1)
  • + * + *
  • fz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fz(1,1,1)
  • + * + *
  • fxy(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxy(1,1,1)
  • + * + *
  • fxz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxz(1,1,1)
  • + * + *
  • fyz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fyz(1,1,1)
  • + * + *
  • fxyz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxyz(1,1,1)
  • + *
+ * where the subscripts indicate the partial derivative with respect to + * the corresponding variable(s). + * + * @param beta List of function values and function partial derivatives values. + * @return the spline coefficients. + */ + private double[] computeCoefficients(double[] beta) { + final int sz = 64; + final double[] a = new double[sz]; + + for (int i = 0; i < sz; i++) { + double result = 0; + final double[] row = AINV[i]; + for (int j = 0; j < sz; j++) { + result += row[j] * beta[j]; + } + a[i] = result; + } + + return a; + } +} + +/** + * 3D-spline function. + * + */ +class TricubicFunction + implements TrivariateFunction { + /** Number of points. */ + private static final short N = 4; + /** Coefficients */ + private final double[][][] a = new double[N][N][N]; + + /** + * @param aV List of spline coefficients. + */ + TricubicFunction(double[] aV) { + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + for (int k = 0; k < N; k++) { + a[i][j][k] = aV[i + N * (j + N * k)]; + } + } + } + } + + /** + * @param x x-coordinate of the interpolation point. + * @param y y-coordinate of the interpolation point. + * @param z z-coordinate of the interpolation point. + * @return the interpolated value. + * @throws OutOfRangeException if {@code x}, {@code y} or + * {@code z} are not in the interval {@code [0, 1]}. + */ + public double value(double x, double y, double z) + throws OutOfRangeException { + if (x < 0 || x > 1) { + throw new OutOfRangeException(x, 0, 1); + } + if (y < 0 || y > 1) { + throw new OutOfRangeException(y, 0, 1); + } + if (z < 0 || z > 1) { + throw new OutOfRangeException(z, 0, 1); + } + + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = { 1, x, x2, x3 }; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = { 1, y, y2, y3 }; + + final double z2 = z * z; + final double z3 = z2 * z; + final double[] pZ = { 1, z, z2, z3 }; + + double result = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + for (int k = 0; k < N; k++) { + result += a[i][j][k] * pX[i] * pY[j] * pZ[k]; + } + } + } + + return result; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java new file mode 100644 index 000000000..141c21275 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Generates a tricubic interpolating function. + * + * @since 3.4 + */ +public class TricubicInterpolator + implements TrivariateGridInterpolator { + /** + * {@inheritDoc} + */ + public TricubicInterpolatingFunction interpolate(final double[] xval, + final double[] yval, + final double[] zval, + final double[][][] fval) + throws NoDataException, NumberIsTooSmallException, + DimensionMismatchException, NonMonotonicSequenceException { + if (xval.length == 0 || yval.length == 0 || zval.length == 0 || fval.length == 0) { + throw new NoDataException(); + } + if (xval.length != fval.length) { + throw new DimensionMismatchException(xval.length, fval.length); + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + MathArrays.checkOrder(zval); + + final int xLen = xval.length; + final int yLen = yval.length; + final int zLen = zval.length; + + // Approximation to the partial derivatives using finite differences. + final double[][][] dFdX = new double[xLen][yLen][zLen]; + final double[][][] dFdY = new double[xLen][yLen][zLen]; + final double[][][] dFdZ = new double[xLen][yLen][zLen]; + final double[][][] d2FdXdY = new double[xLen][yLen][zLen]; + final double[][][] d2FdXdZ = new double[xLen][yLen][zLen]; + final double[][][] d2FdYdZ = new double[xLen][yLen][zLen]; + final double[][][] d3FdXdYdZ = new double[xLen][yLen][zLen]; + + for (int i = 1; i < xLen - 1; i++) { + if (yval.length != fval[i].length) { + throw new DimensionMismatchException(yval.length, fval[i].length); + } + + final int nI = i + 1; + final int pI = i - 1; + + final double nX = xval[nI]; + final double pX = xval[pI]; + + final double deltaX = nX - pX; + + for (int j = 1; j < yLen - 1; j++) { + if (zval.length != fval[i][j].length) { + throw new DimensionMismatchException(zval.length, fval[i][j].length); + } + + final int nJ = j + 1; + final int pJ = j - 1; + + final double nY = yval[nJ]; + final double pY = yval[pJ]; + + final double deltaY = nY - pY; + final double deltaXY = deltaX * deltaY; + + for (int k = 1; k < zLen - 1; k++) { + final int nK = k + 1; + final int pK = k - 1; + + final double nZ = zval[nK]; + final double pZ = zval[pK]; + + final double deltaZ = nZ - pZ; + + dFdX[i][j][k] = (fval[nI][j][k] - fval[pI][j][k]) / deltaX; + dFdY[i][j][k] = (fval[i][nJ][k] - fval[i][pJ][k]) / deltaY; + dFdZ[i][j][k] = (fval[i][j][nK] - fval[i][j][pK]) / deltaZ; + + final double deltaXZ = deltaX * deltaZ; + final double deltaYZ = deltaY * deltaZ; + + d2FdXdY[i][j][k] = (fval[nI][nJ][k] - fval[nI][pJ][k] - fval[pI][nJ][k] + fval[pI][pJ][k]) / deltaXY; + d2FdXdZ[i][j][k] = (fval[nI][j][nK] - fval[nI][j][pK] - fval[pI][j][nK] + fval[pI][j][pK]) / deltaXZ; + d2FdYdZ[i][j][k] = (fval[i][nJ][nK] - fval[i][nJ][pK] - fval[i][pJ][nK] + fval[i][pJ][pK]) / deltaYZ; + + final double deltaXYZ = deltaXY * deltaZ; + + d3FdXdYdZ[i][j][k] = (fval[nI][nJ][nK] - fval[nI][pJ][nK] - + fval[pI][nJ][nK] + fval[pI][pJ][nK] - + fval[nI][nJ][pK] + fval[nI][pJ][pK] + + fval[pI][nJ][pK] - fval[pI][pJ][pK]) / deltaXYZ; + } + } + } + + // Create the interpolating function. + return new TricubicInterpolatingFunction(xval, yval, zval, fval, + dFdX, dFdY, dFdZ, + d2FdXdY, d2FdXdZ, d2FdYdZ, + d3FdXdYdZ) { + /** {@inheritDoc} */ + @Override + public boolean isValidPoint(double x, double y, double z) { + if (x < xval[1] || + x > xval[xval.length - 2] || + y < yval[1] || + y > yval[yval.length - 2] || + z < zval[1] || + z > zval[zval.length - 2]) { + return false; + } else { + return true; + } + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java new file mode 100644 index 000000000..472c5ccae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.analysis.TrivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Function that implements the + * + * tricubic spline interpolation, as proposed in + *
+ * Tricubic interpolation in three dimensions, + * F. Lekien and J. Marsden, + * Int. J. Numer. Meth. Engng 2005; 63:455-471 + *
+ * + * @since 2.2 + * @deprecated To be removed in 4.0 (see MATH-1166). + */ +@Deprecated +public class TricubicSplineInterpolatingFunction + implements TrivariateFunction { + /** + * Matrix to compute the spline coefficients from the function values + * and function derivatives values + */ + private static final double[][] AINV = { + { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 9,-9,-9,9,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,6,-6,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,0,0,0,0,0,0,0,0,-2,-2,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,6,-6,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 4,-4,-4,4,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,-9,9,0,0,0,0,0,0,0,0,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,4,2,2,1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,-2,-2,-1,-1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,-2,-1,-2,-1,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,-4,4,0,0,0,0,0,0,0,0,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,1,1,1,1,0,0,0,0 }, + {-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 9,-9,0,0,-9,9,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,0,0,0,0,0,0,0,0,4,2,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,0,0,6,-6,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,0,0,0,0,0,0,0,0,-2,-2,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,0,0,-9,9,0,0,0,0,0,0,0,0,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,4,2,0,0,2,1,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,-2,-2,0,0,-1,-1,0,0 }, + { 9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0 }, + { -27,27,27,-27,27,-27,-27,27,-18,-9,18,9,18,9,-18,-9,-18,18,-9,9,18,-18,9,-9,-18,18,18,-18,-9,9,9,-9,-12,-6,-6,-3,12,6,6,3,-12,-6,12,6,-6,-3,6,3,-12,12,-6,6,-6,6,-3,3,-8,-4,-4,-2,-4,-2,-2,-1 }, + { 18,-18,-18,18,-18,18,18,-18,9,9,-9,-9,-9,-9,9,9,12,-12,6,-6,-12,12,-6,6,12,-12,-12,12,6,-6,-6,6,6,6,3,3,-6,-6,-3,-3,6,6,-6,-6,3,3,-3,-3,8,-8,4,-4,4,-4,2,-2,4,4,2,2,2,2,1,1 }, + { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0 }, + { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,9,-9,9,-9,-9,9,-9,9,12,-12,-12,12,6,-6,-6,6,6,3,6,3,-6,-3,-6,-3,8,4,-8,-4,4,2,-4,-2,6,-6,6,-6,3,-3,3,-3,4,2,4,2,2,1,2,1 }, + { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-6,6,-6,6,6,-6,6,-6,-8,8,8,-8,-4,4,4,-4,-3,-3,-3,-3,3,3,3,3,-4,-4,4,4,-2,-2,2,2,-4,4,-4,4,-2,2,-2,2,-2,-2,-2,-2,-1,-1,-1,-1 }, + { 2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { -6,6,0,0,6,-6,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 4,-4,0,0,-4,4,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,-2,-1,0,0,-2,-1,0,0 }, + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,0,0,-4,4,0,0,0,0,0,0,0,0,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,1,1,0,0,1,1,0,0 }, + { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0 }, + { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,12,-12,6,-6,-12,12,-6,6,9,-9,-9,9,9,-9,-9,9,8,4,4,2,-8,-4,-4,-2,6,3,-6,-3,6,3,-6,-3,6,-6,3,-3,6,-6,3,-3,4,2,2,1,4,2,2,1 }, + { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-8,8,-4,4,8,-8,4,-4,-6,6,6,-6,-6,6,6,-6,-4,-4,-2,-2,4,4,2,2,-3,-3,3,3,-3,-3,3,3,-4,4,-2,2,-4,4,-2,2,-2,-2,-1,-1,-2,-2,-1,-1 }, + { 4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0 }, + { 0,0,0,0,0,0,0,0,4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0 }, + { -12,12,12,-12,12,-12,-12,12,-8,-4,8,4,8,4,-8,-4,-6,6,-6,6,6,-6,6,-6,-6,6,6,-6,-6,6,6,-6,-4,-2,-4,-2,4,2,4,2,-4,-2,4,2,-4,-2,4,2,-3,3,-3,3,-3,3,-3,3,-2,-1,-2,-1,-2,-1,-2,-1 }, + { 8,-8,-8,8,-8,8,8,-8,4,4,-4,-4,-4,-4,4,4,4,-4,4,-4,-4,4,-4,4,4,-4,-4,4,4,-4,-4,4,2,2,2,2,-2,-2,-2,-2,2,2,-2,-2,2,2,-2,-2,2,-2,2,-2,2,-2,2,-2,1,1,1,1,1,1,1,1 } + }; + + /** Samples x-coordinates */ + private final double[] xval; + /** Samples y-coordinates */ + private final double[] yval; + /** Samples z-coordinates */ + private final double[] zval; + /** Set of cubic splines pacthing the whole data grid */ + private final TricubicSplineFunction[][][] splines; + + /** + * @param x Sample values of the x-coordinate, in increasing order. + * @param y Sample values of the y-coordinate, in increasing order. + * @param z Sample values of the y-coordinate, in increasing order. + * @param f Values of the function on every grid point. + * @param dFdX Values of the partial derivative of function with respect to x on every grid point. + * @param dFdY Values of the partial derivative of function with respect to y on every grid point. + * @param dFdZ Values of the partial derivative of function with respect to z on every grid point. + * @param d2FdXdY Values of the cross partial derivative of function on every grid point. + * @param d2FdXdZ Values of the cross partial derivative of function on every grid point. + * @param d2FdYdZ Values of the cross partial derivative of function on every grid point. + * @param d3FdXdYdZ Values of the cross partial derivative of function on every grid point. + * @throws NoDataException if any of the arrays has zero length. + * @throws DimensionMismatchException if the various arrays do not contain the expected number of elements. + * @throws NonMonotonicSequenceException if {@code x}, {@code y} or {@code z} are not strictly increasing. + */ + public TricubicSplineInterpolatingFunction(double[] x, + double[] y, + double[] z, + double[][][] f, + double[][][] dFdX, + double[][][] dFdY, + double[][][] dFdZ, + double[][][] d2FdXdY, + double[][][] d2FdXdZ, + double[][][] d2FdYdZ, + double[][][] d3FdXdYdZ) + throws NoDataException, + DimensionMismatchException, + NonMonotonicSequenceException { + final int xLen = x.length; + final int yLen = y.length; + final int zLen = z.length; + + if (xLen == 0 || yLen == 0 || z.length == 0 || f.length == 0 || f[0].length == 0) { + throw new NoDataException(); + } + if (xLen != f.length) { + throw new DimensionMismatchException(xLen, f.length); + } + if (xLen != dFdX.length) { + throw new DimensionMismatchException(xLen, dFdX.length); + } + if (xLen != dFdY.length) { + throw new DimensionMismatchException(xLen, dFdY.length); + } + if (xLen != dFdZ.length) { + throw new DimensionMismatchException(xLen, dFdZ.length); + } + if (xLen != d2FdXdY.length) { + throw new DimensionMismatchException(xLen, d2FdXdY.length); + } + if (xLen != d2FdXdZ.length) { + throw new DimensionMismatchException(xLen, d2FdXdZ.length); + } + if (xLen != d2FdYdZ.length) { + throw new DimensionMismatchException(xLen, d2FdYdZ.length); + } + if (xLen != d3FdXdYdZ.length) { + throw new DimensionMismatchException(xLen, d3FdXdYdZ.length); + } + + MathArrays.checkOrder(x); + MathArrays.checkOrder(y); + MathArrays.checkOrder(z); + + xval = x.clone(); + yval = y.clone(); + zval = z.clone(); + + final int lastI = xLen - 1; + final int lastJ = yLen - 1; + final int lastK = zLen - 1; + splines = new TricubicSplineFunction[lastI][lastJ][lastK]; + + for (int i = 0; i < lastI; i++) { + if (f[i].length != yLen) { + throw new DimensionMismatchException(f[i].length, yLen); + } + if (dFdX[i].length != yLen) { + throw new DimensionMismatchException(dFdX[i].length, yLen); + } + if (dFdY[i].length != yLen) { + throw new DimensionMismatchException(dFdY[i].length, yLen); + } + if (dFdZ[i].length != yLen) { + throw new DimensionMismatchException(dFdZ[i].length, yLen); + } + if (d2FdXdY[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdY[i].length, yLen); + } + if (d2FdXdZ[i].length != yLen) { + throw new DimensionMismatchException(d2FdXdZ[i].length, yLen); + } + if (d2FdYdZ[i].length != yLen) { + throw new DimensionMismatchException(d2FdYdZ[i].length, yLen); + } + if (d3FdXdYdZ[i].length != yLen) { + throw new DimensionMismatchException(d3FdXdYdZ[i].length, yLen); + } + + final int ip1 = i + 1; + for (int j = 0; j < lastJ; j++) { + if (f[i][j].length != zLen) { + throw new DimensionMismatchException(f[i][j].length, zLen); + } + if (dFdX[i][j].length != zLen) { + throw new DimensionMismatchException(dFdX[i][j].length, zLen); + } + if (dFdY[i][j].length != zLen) { + throw new DimensionMismatchException(dFdY[i][j].length, zLen); + } + if (dFdZ[i][j].length != zLen) { + throw new DimensionMismatchException(dFdZ[i][j].length, zLen); + } + if (d2FdXdY[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdXdY[i][j].length, zLen); + } + if (d2FdXdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdXdZ[i][j].length, zLen); + } + if (d2FdYdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d2FdYdZ[i][j].length, zLen); + } + if (d3FdXdYdZ[i][j].length != zLen) { + throw new DimensionMismatchException(d3FdXdYdZ[i][j].length, zLen); + } + + final int jp1 = j + 1; + for (int k = 0; k < lastK; k++) { + final int kp1 = k + 1; + + final double[] beta = new double[] { + f[i][j][k], f[ip1][j][k], + f[i][jp1][k], f[ip1][jp1][k], + f[i][j][kp1], f[ip1][j][kp1], + f[i][jp1][kp1], f[ip1][jp1][kp1], + + dFdX[i][j][k], dFdX[ip1][j][k], + dFdX[i][jp1][k], dFdX[ip1][jp1][k], + dFdX[i][j][kp1], dFdX[ip1][j][kp1], + dFdX[i][jp1][kp1], dFdX[ip1][jp1][kp1], + + dFdY[i][j][k], dFdY[ip1][j][k], + dFdY[i][jp1][k], dFdY[ip1][jp1][k], + dFdY[i][j][kp1], dFdY[ip1][j][kp1], + dFdY[i][jp1][kp1], dFdY[ip1][jp1][kp1], + + dFdZ[i][j][k], dFdZ[ip1][j][k], + dFdZ[i][jp1][k], dFdZ[ip1][jp1][k], + dFdZ[i][j][kp1], dFdZ[ip1][j][kp1], + dFdZ[i][jp1][kp1], dFdZ[ip1][jp1][kp1], + + d2FdXdY[i][j][k], d2FdXdY[ip1][j][k], + d2FdXdY[i][jp1][k], d2FdXdY[ip1][jp1][k], + d2FdXdY[i][j][kp1], d2FdXdY[ip1][j][kp1], + d2FdXdY[i][jp1][kp1], d2FdXdY[ip1][jp1][kp1], + + d2FdXdZ[i][j][k], d2FdXdZ[ip1][j][k], + d2FdXdZ[i][jp1][k], d2FdXdZ[ip1][jp1][k], + d2FdXdZ[i][j][kp1], d2FdXdZ[ip1][j][kp1], + d2FdXdZ[i][jp1][kp1], d2FdXdZ[ip1][jp1][kp1], + + d2FdYdZ[i][j][k], d2FdYdZ[ip1][j][k], + d2FdYdZ[i][jp1][k], d2FdYdZ[ip1][jp1][k], + d2FdYdZ[i][j][kp1], d2FdYdZ[ip1][j][kp1], + d2FdYdZ[i][jp1][kp1], d2FdYdZ[ip1][jp1][kp1], + + d3FdXdYdZ[i][j][k], d3FdXdYdZ[ip1][j][k], + d3FdXdYdZ[i][jp1][k], d3FdXdYdZ[ip1][jp1][k], + d3FdXdYdZ[i][j][kp1], d3FdXdYdZ[ip1][j][kp1], + d3FdXdYdZ[i][jp1][kp1], d3FdXdYdZ[ip1][jp1][kp1], + }; + + splines[i][j][k] = new TricubicSplineFunction(computeSplineCoefficients(beta)); + } + } + } + } + + /** + * {@inheritDoc} + * + * @throws OutOfRangeException if any of the variables is outside its interpolation range. + */ + public double value(double x, double y, double z) + throws OutOfRangeException { + final int i = searchIndex(x, xval); + if (i == -1) { + throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]); + } + final int j = searchIndex(y, yval); + if (j == -1) { + throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]); + } + final int k = searchIndex(z, zval); + if (k == -1) { + throw new OutOfRangeException(z, zval[0], zval[zval.length - 1]); + } + + final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]); + final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]); + final double zN = (z - zval[k]) / (zval[k + 1] - zval[k]); + + return splines[i][j][k].value(xN, yN, zN); + } + + /** + * @param c Coordinate. + * @param val Coordinate samples. + * @return the index in {@code val} corresponding to the interval containing {@code c}, or {@code -1} + * if {@code c} is out of the range defined by the end values of {@code val}. + */ + private int searchIndex(double c, double[] val) { + if (c < val[0]) { + return -1; + } + + final int max = val.length; + for (int i = 1; i < max; i++) { + if (c <= val[i]) { + return i - 1; + } + } + + return -1; + } + + /** + * Compute the spline coefficients from the list of function values and + * function partial derivatives values at the four corners of a grid + * element. They must be specified in the following order: + *
    + *
  • f(0,0,0)
  • + *
  • f(1,0,0)
  • + *
  • f(0,1,0)
  • + *
  • f(1,1,0)
  • + *
  • f(0,0,1)
  • + *
  • f(1,0,1)
  • + *
  • f(0,1,1)
  • + *
  • f(1,1,1)
  • + * + *
  • fx(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fx(1,1,1)
  • + * + *
  • fy(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fy(1,1,1)
  • + * + *
  • fz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fz(1,1,1)
  • + * + *
  • fxy(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxy(1,1,1)
  • + * + *
  • fxz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxz(1,1,1)
  • + * + *
  • fyz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fyz(1,1,1)
  • + * + *
  • fxyz(0,0,0)
  • + *
  • ... (same order as above)
  • + *
  • fxyz(1,1,1)
  • + *
+ * where the subscripts indicate the partial derivative with respect to + * the corresponding variable(s). + * + * @param beta List of function values and function partial derivatives values. + * @return the spline coefficients. + */ + private double[] computeSplineCoefficients(double[] beta) { + final int sz = 64; + final double[] a = new double[sz]; + + for (int i = 0; i < sz; i++) { + double result = 0; + final double[] row = AINV[i]; + for (int j = 0; j < sz; j++) { + result += row[j] * beta[j]; + } + a[i] = result; + } + + return a; + } +} + +/** + * 3D-spline function. + * + */ +class TricubicSplineFunction + implements TrivariateFunction { + /** Number of points. */ + private static final short N = 4; + /** Coefficients */ + private final double[][][] a = new double[N][N][N]; + + /** + * @param aV List of spline coefficients. + */ + TricubicSplineFunction(double[] aV) { + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + for (int k = 0; k < N; k++) { + a[i][j][k] = aV[i + N * (j + N * k)]; + } + } + } + } + + /** + * @param x x-coordinate of the interpolation point. + * @param y y-coordinate of the interpolation point. + * @param z z-coordinate of the interpolation point. + * @return the interpolated value. + * @throws OutOfRangeException if {@code x}, {@code y} or + * {@code z} are not in the interval {@code [0, 1]}. + */ + public double value(double x, double y, double z) + throws OutOfRangeException { + if (x < 0 || x > 1) { + throw new OutOfRangeException(x, 0, 1); + } + if (y < 0 || y > 1) { + throw new OutOfRangeException(y, 0, 1); + } + if (z < 0 || z > 1) { + throw new OutOfRangeException(z, 0, 1); + } + + final double x2 = x * x; + final double x3 = x2 * x; + final double[] pX = { 1, x, x2, x3 }; + + final double y2 = y * y; + final double y3 = y2 * y; + final double[] pY = { 1, y, y2, y3 }; + + final double z2 = z * z; + final double z3 = z2 * z; + final double[] pZ = { 1, z, z2, z3 }; + + double result = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + for (int k = 0; k < N; k++) { + result += a[i][j][k] * pX[i] * pY[j] * pZ[k]; + } + } + } + + return result; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java new file mode 100644 index 000000000..f91013ac3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Generates a tricubic interpolating function. + * + * @since 2.2 + * @deprecated To be removed in 4.0 (see MATH-1166). + */ +@Deprecated +public class TricubicSplineInterpolator + implements TrivariateGridInterpolator { + /** + * {@inheritDoc} + */ + public TricubicSplineInterpolatingFunction interpolate(final double[] xval, + final double[] yval, + final double[] zval, + final double[][][] fval) + throws NoDataException, NumberIsTooSmallException, + DimensionMismatchException, NonMonotonicSequenceException { + if (xval.length == 0 || yval.length == 0 || zval.length == 0 || fval.length == 0) { + throw new NoDataException(); + } + if (xval.length != fval.length) { + throw new DimensionMismatchException(xval.length, fval.length); + } + + MathArrays.checkOrder(xval); + MathArrays.checkOrder(yval); + MathArrays.checkOrder(zval); + + final int xLen = xval.length; + final int yLen = yval.length; + final int zLen = zval.length; + + // Samples, re-ordered as (z, x, y) and (y, z, x) tuplets + // fvalXY[k][i][j] = f(xval[i], yval[j], zval[k]) + // fvalZX[j][k][i] = f(xval[i], yval[j], zval[k]) + final double[][][] fvalXY = new double[zLen][xLen][yLen]; + final double[][][] fvalZX = new double[yLen][zLen][xLen]; + for (int i = 0; i < xLen; i++) { + if (fval[i].length != yLen) { + throw new DimensionMismatchException(fval[i].length, yLen); + } + + for (int j = 0; j < yLen; j++) { + if (fval[i][j].length != zLen) { + throw new DimensionMismatchException(fval[i][j].length, zLen); + } + + for (int k = 0; k < zLen; k++) { + final double v = fval[i][j][k]; + fvalXY[k][i][j] = v; + fvalZX[j][k][i] = v; + } + } + } + + final BicubicSplineInterpolator bsi = new BicubicSplineInterpolator(true); + + // For each line x[i] (0 <= i < xLen), construct a 2D spline in y and z + final BicubicSplineInterpolatingFunction[] xSplineYZ + = new BicubicSplineInterpolatingFunction[xLen]; + for (int i = 0; i < xLen; i++) { + xSplineYZ[i] = bsi.interpolate(yval, zval, fval[i]); + } + + // For each line y[j] (0 <= j < yLen), construct a 2D spline in z and x + final BicubicSplineInterpolatingFunction[] ySplineZX + = new BicubicSplineInterpolatingFunction[yLen]; + for (int j = 0; j < yLen; j++) { + ySplineZX[j] = bsi.interpolate(zval, xval, fvalZX[j]); + } + + // For each line z[k] (0 <= k < zLen), construct a 2D spline in x and y + final BicubicSplineInterpolatingFunction[] zSplineXY + = new BicubicSplineInterpolatingFunction[zLen]; + for (int k = 0; k < zLen; k++) { + zSplineXY[k] = bsi.interpolate(xval, yval, fvalXY[k]); + } + + // Partial derivatives wrt x and wrt y + final double[][][] dFdX = new double[xLen][yLen][zLen]; + final double[][][] dFdY = new double[xLen][yLen][zLen]; + final double[][][] d2FdXdY = new double[xLen][yLen][zLen]; + for (int k = 0; k < zLen; k++) { + final BicubicSplineInterpolatingFunction f = zSplineXY[k]; + for (int i = 0; i < xLen; i++) { + final double x = xval[i]; + for (int j = 0; j < yLen; j++) { + final double y = yval[j]; + dFdX[i][j][k] = f.partialDerivativeX(x, y); + dFdY[i][j][k] = f.partialDerivativeY(x, y); + d2FdXdY[i][j][k] = f.partialDerivativeXY(x, y); + } + } + } + + // Partial derivatives wrt y and wrt z + final double[][][] dFdZ = new double[xLen][yLen][zLen]; + final double[][][] d2FdYdZ = new double[xLen][yLen][zLen]; + for (int i = 0; i < xLen; i++) { + final BicubicSplineInterpolatingFunction f = xSplineYZ[i]; + for (int j = 0; j < yLen; j++) { + final double y = yval[j]; + for (int k = 0; k < zLen; k++) { + final double z = zval[k]; + dFdZ[i][j][k] = f.partialDerivativeY(y, z); + d2FdYdZ[i][j][k] = f.partialDerivativeXY(y, z); + } + } + } + + // Partial derivatives wrt x and wrt z + final double[][][] d2FdZdX = new double[xLen][yLen][zLen]; + for (int j = 0; j < yLen; j++) { + final BicubicSplineInterpolatingFunction f = ySplineZX[j]; + for (int k = 0; k < zLen; k++) { + final double z = zval[k]; + for (int i = 0; i < xLen; i++) { + final double x = xval[i]; + d2FdZdX[i][j][k] = f.partialDerivativeXY(z, x); + } + } + } + + // Third partial cross-derivatives + final double[][][] d3FdXdYdZ = new double[xLen][yLen][zLen]; + for (int i = 0; i < xLen ; i++) { + final int nI = nextIndex(i, xLen); + final int pI = previousIndex(i); + for (int j = 0; j < yLen; j++) { + final int nJ = nextIndex(j, yLen); + final int pJ = previousIndex(j); + for (int k = 0; k < zLen; k++) { + final int nK = nextIndex(k, zLen); + final int pK = previousIndex(k); + + // XXX Not sure about this formula + d3FdXdYdZ[i][j][k] = (fval[nI][nJ][nK] - fval[nI][pJ][nK] - + fval[pI][nJ][nK] + fval[pI][pJ][nK] - + fval[nI][nJ][pK] + fval[nI][pJ][pK] + + fval[pI][nJ][pK] - fval[pI][pJ][pK]) / + ((xval[nI] - xval[pI]) * (yval[nJ] - yval[pJ]) * (zval[nK] - zval[pK])) ; + } + } + } + + // Create the interpolating splines + return new TricubicSplineInterpolatingFunction(xval, yval, zval, fval, + dFdX, dFdY, dFdZ, + d2FdXdY, d2FdZdX, d2FdYdZ, + d3FdXdYdZ); + } + + /** + * Compute the next index of an array, clipping if necessary. + * It is assumed (but not checked) that {@code i} is larger than or equal to 0. + * + * @param i Index + * @param max Upper limit of the array + * @return the next index + */ + private int nextIndex(int i, int max) { + final int index = i + 1; + return index < max ? index : index - 1; + } + /** + * Compute the previous index of an array, clipping if necessary. + * It is assumed (but not checked) that {@code i} is smaller than the size of the array. + * + * @param i Index + * @return the previous index + */ + private int previousIndex(int i) { + final int index = i - 1; + return index >= 0 ? index : 0; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java new file mode 100644 index 000000000..284d3ea4d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.TrivariateFunction; + +/** + * Interface representing a trivariate real interpolating function where the + * sample points must be specified on a regular grid. + * + * @since 2.2 + */ +public interface TrivariateGridInterpolator { + /** + * Compute an interpolating function for the dataset. + * + * @param xval All the x-coordinates of the interpolation points, sorted + * in increasing order. + * @param yval All the y-coordinates of the interpolation points, sorted + * in increasing order. + * @param zval All the z-coordinates of the interpolation points, sorted + * in increasing order. + * @param fval the values of the interpolation points on all the grid knots: + * {@code fval[i][j][k] = f(xval[i], yval[j], zval[k])}. + * @return a function that interpolates the data set. + * @throws NoDataException if any of the arrays has zero length. + * @throws DimensionMismatchException if the array lengths are inconsistent. + * @throws NonMonotonicSequenceException if arrays are not sorted + * @throws NumberIsTooSmallException if the number of points is too small for + * the order of the interpolation + */ + TrivariateFunction interpolate(double[] xval, double[] yval, double[] zval, + double[][][] fval) + throws NoDataException, NumberIsTooSmallException, + DimensionMismatchException, NonMonotonicSequenceException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java new file mode 100644 index 000000000..7e5311367 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Interface representing a univariate real interpolating function. + * + */ +public interface UnivariateInterpolator { + /** + * Compute an interpolating function for the dataset. + * + * @param xval Arguments for the interpolation points. + * @param yval Values for the interpolation points. + * @return a function which interpolates the dataset. + * @throws MathIllegalArgumentException + * if the arguments violate assumptions made by the interpolation + * algorithm. + * @throws DimensionMismatchException if arrays lengthes do not match + */ + UnivariateFunction interpolate(double xval[], double yval[]) + throws MathIllegalArgumentException, DimensionMismatchException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java new file mode 100644 index 000000000..29b4daaa4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Adapter for classes implementing the {@link UnivariateInterpolator} + * interface. + * The data to be interpolated is assumed to be periodic. Thus values that are + * outside of the range can be passed to the interpolation function: They will + * be wrapped into the initial range before being passed to the class that + * actually computes the interpolation. + * + */ +public class UnivariatePeriodicInterpolator + implements UnivariateInterpolator { + /** Default number of extension points of the samples array. */ + public static final int DEFAULT_EXTEND = 5; + /** Interpolator. */ + private final UnivariateInterpolator interpolator; + /** Period. */ + private final double period; + /** Number of extension points. */ + private final int extend; + + /** + * Builds an interpolator. + * + * @param interpolator Interpolator. + * @param period Period. + * @param extend Number of points to be appended at the beginning and + * end of the sample arrays in order to avoid interpolation failure at + * the (periodic) boundaries of the orginal interval. The value is the + * number of sample points which the original {@code interpolator} needs + * on each side of the interpolated point. + */ + public UnivariatePeriodicInterpolator(UnivariateInterpolator interpolator, + double period, + int extend) { + this.interpolator = interpolator; + this.period = period; + this.extend = extend; + } + + /** + * Builds an interpolator. + * Uses {@link #DEFAULT_EXTEND} as the number of extension points on each side + * of the original abscissae range. + * + * @param interpolator Interpolator. + * @param period Period. + */ + public UnivariatePeriodicInterpolator(UnivariateInterpolator interpolator, + double period) { + this(interpolator, period, DEFAULT_EXTEND); + } + + /** + * {@inheritDoc} + * + * @throws NumberIsTooSmallException if the number of extension points + * is larger than the size of {@code xval}. + */ + public UnivariateFunction interpolate(double[] xval, + double[] yval) + throws NumberIsTooSmallException, NonMonotonicSequenceException { + if (xval.length < extend) { + throw new NumberIsTooSmallException(xval.length, extend, true); + } + + MathArrays.checkOrder(xval); + final double offset = xval[0]; + + final int len = xval.length + extend * 2; + final double[] x = new double[len]; + final double[] y = new double[len]; + for (int i = 0; i < xval.length; i++) { + final int index = i + extend; + x[index] = MathUtils.reduce(xval[i], period, offset); + y[index] = yval[i]; + } + + // Wrap to enable interpolation at the boundaries. + for (int i = 0; i < extend; i++) { + int index = xval.length - extend + i; + x[i] = MathUtils.reduce(xval[index], period, offset) - period; + y[i] = yval[index]; + + index = len - extend + i; + x[index] = MathUtils.reduce(xval[i], period, offset) + period; + y[index] = yval[i]; + } + + MathArrays.sortInPlace(x, y); + + final UnivariateFunction f = interpolator.interpolate(x, y); + return new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(final double x) throws MathIllegalArgumentException { + return f.value(MathUtils.reduce(x, period, offset)); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/package-info.java new file mode 100644 index 000000000..8936e480d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/interpolation/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Univariate real functions interpolation algorithms. + * + */ +package com.fr.third.org.apache.commons.math3.analysis.interpolation; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/package-info.java new file mode 100644 index 000000000..2bcce60f0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/package-info.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. + */ +/** + * + *

+ * Parent package for common numerical analysis procedures, including root finding, + * function interpolation and integration. Note that optimization (i.e. minimization + * and maximization) is a separate top-level package. + *

+ *

+ * Function interfaces are intended to be implemented by user code to represent + * domain problems. The algorithms provided by the library operate on these + * functions to find their roots, or integrate them, or ... Functions can be multivariate + * or univariate, real vectorial or matrix-valued, and they can be differentiable or not. + *

+ * + */ +package com.fr.third.org.apache.commons.math3.analysis; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java new file mode 100644 index 000000000..ea2683f15 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java @@ -0,0 +1,412 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Immutable representation of a real polynomial function with real coefficients. + *

+ * Horner's Method + * is used to evaluate the function.

+ * + */ +public class PolynomialFunction implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction, Serializable { + /** + * Serialization identifier + */ + private static final long serialVersionUID = -7726511984200295583L; + /** + * The coefficients of the polynomial, ordered by degree -- i.e., + * coefficients[0] is the constant term and coefficients[n] is the + * coefficient of x^n where n is the degree of the polynomial. + */ + private final double coefficients[]; + + /** + * Construct a polynomial with the given coefficients. The first element + * of the coefficients array is the constant term. Higher degree + * coefficients follow in sequence. The degree of the resulting polynomial + * is the index of the last non-null element of the array, or 0 if all elements + * are null. + *

+ * The constructor makes a copy of the input array and assigns the copy to + * the coefficients property.

+ * + * @param c Polynomial coefficients. + * @throws NullArgumentException if {@code c} is {@code null}. + * @throws NoDataException if {@code c} is empty. + */ + public PolynomialFunction(double c[]) + throws NullArgumentException, NoDataException { + super(); + MathUtils.checkNotNull(c); + int n = c.length; + if (n == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY); + } + while ((n > 1) && (c[n - 1] == 0)) { + --n; + } + this.coefficients = new double[n]; + System.arraycopy(c, 0, this.coefficients, 0, n); + } + + /** + * Compute the value of the function for the given argument. + *

+ * The value returned is

+ * {@code coefficients[n] * x^n + ... + coefficients[1] * x + coefficients[0]} + *

+ * + * @param x Argument for which the function value should be computed. + * @return the value of the polynomial at the given point. + * @see UnivariateFunction#value(double) + */ + public double value(double x) { + return evaluate(coefficients, x); + } + + /** + * Returns the degree of the polynomial. + * + * @return the degree of the polynomial. + */ + public int degree() { + return coefficients.length - 1; + } + + /** + * Returns a copy of the coefficients array. + *

+ * Changes made to the returned copy will not affect the coefficients of + * the polynomial.

+ * + * @return a fresh copy of the coefficients array. + */ + public double[] getCoefficients() { + return coefficients.clone(); + } + + /** + * Uses Horner's Method to evaluate the polynomial with the given coefficients at + * the argument. + * + * @param coefficients Coefficients of the polynomial to evaluate. + * @param argument Input value. + * @return the value of the polynomial. + * @throws NoDataException if {@code coefficients} is empty. + * @throws NullArgumentException if {@code coefficients} is {@code null}. + */ + protected static double evaluate(double[] coefficients, double argument) + throws NullArgumentException, NoDataException { + MathUtils.checkNotNull(coefficients); + int n = coefficients.length; + if (n == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY); + } + double result = coefficients[n - 1]; + for (int j = n - 2; j >= 0; j--) { + result = argument * result + coefficients[j]; + } + return result; + } + + + /** {@inheritDoc} + * @since 3.1 + * @throws NoDataException if {@code coefficients} is empty. + * @throws NullArgumentException if {@code coefficients} is {@code null}. + */ + public DerivativeStructure value(final DerivativeStructure t) + throws NullArgumentException, NoDataException { + MathUtils.checkNotNull(coefficients); + int n = coefficients.length; + if (n == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY); + } + DerivativeStructure result = + new DerivativeStructure(t.getFreeParameters(), t.getOrder(), coefficients[n - 1]); + for (int j = n - 2; j >= 0; j--) { + result = result.multiply(t).add(coefficients[j]); + } + return result; + } + + /** + * Add a polynomial to the instance. + * + * @param p Polynomial to add. + * @return a new polynomial which is the sum of the instance and {@code p}. + */ + public PolynomialFunction add(final PolynomialFunction p) { + // identify the lowest degree polynomial + final int lowLength = FastMath.min(coefficients.length, p.coefficients.length); + final int highLength = FastMath.max(coefficients.length, p.coefficients.length); + + // build the coefficients array + double[] newCoefficients = new double[highLength]; + for (int i = 0; i < lowLength; ++i) { + newCoefficients[i] = coefficients[i] + p.coefficients[i]; + } + System.arraycopy((coefficients.length < p.coefficients.length) ? + p.coefficients : coefficients, + lowLength, + newCoefficients, lowLength, + highLength - lowLength); + + return new PolynomialFunction(newCoefficients); + } + + /** + * Subtract a polynomial from the instance. + * + * @param p Polynomial to subtract. + * @return a new polynomial which is the instance minus {@code p}. + */ + public PolynomialFunction subtract(final PolynomialFunction p) { + // identify the lowest degree polynomial + int lowLength = FastMath.min(coefficients.length, p.coefficients.length); + int highLength = FastMath.max(coefficients.length, p.coefficients.length); + + // build the coefficients array + double[] newCoefficients = new double[highLength]; + for (int i = 0; i < lowLength; ++i) { + newCoefficients[i] = coefficients[i] - p.coefficients[i]; + } + if (coefficients.length < p.coefficients.length) { + for (int i = lowLength; i < highLength; ++i) { + newCoefficients[i] = -p.coefficients[i]; + } + } else { + System.arraycopy(coefficients, lowLength, newCoefficients, lowLength, + highLength - lowLength); + } + + return new PolynomialFunction(newCoefficients); + } + + /** + * Negate the instance. + * + * @return a new polynomial with all coefficients negated + */ + public PolynomialFunction negate() { + double[] newCoefficients = new double[coefficients.length]; + for (int i = 0; i < coefficients.length; ++i) { + newCoefficients[i] = -coefficients[i]; + } + return new PolynomialFunction(newCoefficients); + } + + /** + * Multiply the instance by a polynomial. + * + * @param p Polynomial to multiply by. + * @return a new polynomial equal to this times {@code p} + */ + public PolynomialFunction multiply(final PolynomialFunction p) { + double[] newCoefficients = new double[coefficients.length + p.coefficients.length - 1]; + + for (int i = 0; i < newCoefficients.length; ++i) { + newCoefficients[i] = 0.0; + for (int j = FastMath.max(0, i + 1 - p.coefficients.length); + j < FastMath.min(coefficients.length, i + 1); + ++j) { + newCoefficients[i] += coefficients[j] * p.coefficients[i-j]; + } + } + + return new PolynomialFunction(newCoefficients); + } + + /** + * Returns the coefficients of the derivative of the polynomial with the given coefficients. + * + * @param coefficients Coefficients of the polynomial to differentiate. + * @return the coefficients of the derivative or {@code null} if coefficients has length 1. + * @throws NoDataException if {@code coefficients} is empty. + * @throws NullArgumentException if {@code coefficients} is {@code null}. + */ + protected static double[] differentiate(double[] coefficients) + throws NullArgumentException, NoDataException { + MathUtils.checkNotNull(coefficients); + int n = coefficients.length; + if (n == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY); + } + if (n == 1) { + return new double[]{0}; + } + double[] result = new double[n - 1]; + for (int i = n - 1; i > 0; i--) { + result[i - 1] = i * coefficients[i]; + } + return result; + } + + /** + * Returns the derivative as a {@link PolynomialFunction}. + * + * @return the derivative polynomial. + */ + public PolynomialFunction polynomialDerivative() { + return new PolynomialFunction(differentiate(coefficients)); + } + + /** + * Returns the derivative as a {@link UnivariateFunction}. + * + * @return the derivative function. + */ + public UnivariateFunction derivative() { + return polynomialDerivative(); + } + + /** + * Returns a string representation of the polynomial. + * + *

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 -3 for a constant negative polynomial, + * but 1 - 3 x + x^2 if the negative coefficient is not + * the first one displayed).

+ * + * @return a string representation of the polynomial. + */ + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + if (coefficients[0] == 0.0) { + if (coefficients.length == 1) { + return "0"; + } + } else { + s.append(toString(coefficients[0])); + } + + for (int i = 1; i < coefficients.length; ++i) { + if (coefficients[i] != 0) { + if (s.length() > 0) { + if (coefficients[i] < 0) { + s.append(" - "); + } else { + s.append(" + "); + } + } else { + if (coefficients[i] < 0) { + s.append("-"); + } + } + + double absAi = FastMath.abs(coefficients[i]); + if ((absAi - 1) != 0) { + s.append(toString(absAi)); + s.append(' '); + } + + s.append("x"); + if (i > 1) { + s.append('^'); + s.append(Integer.toString(i)); + } + } + } + + return s.toString(); + } + + /** + * Creates a string representing a coefficient, removing ".0" endings. + * + * @param coeff Coefficient. + * @return a string representation of {@code coeff}. + */ + private static String toString(double coeff) { + final String c = Double.toString(coeff); + if (c.endsWith(".0")) { + return c.substring(0, c.length() - 2); + } else { + return c; + } + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(coefficients); + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PolynomialFunction)) { + return false; + } + PolynomialFunction other = (PolynomialFunction) obj; + if (!Arrays.equals(coefficients, other.coefficients)) { + return false; + } + return true; + } + + /** + * Dedicated parametric polynomial class. + * + * @since 3.0 + */ + public static class Parametric implements ParametricUnivariateFunction { + /** {@inheritDoc} */ + public double[] gradient(double x, double ... parameters) { + final double[] gradient = new double[parameters.length]; + double xn = 1.0; + for (int i = 0; i < parameters.length; ++i) { + gradient[i] = xn; + xn *= x; + } + return gradient; + } + + /** {@inheritDoc} */ + public double value(final double x, final double ... parameters) + throws NoDataException { + return PolynomialFunction.evaluate(parameters, x); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java new file mode 100644 index 000000000..e350e72bb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implements the representation of a real polynomial function in + * + * Lagrange Form. For reference, see Introduction to Numerical + * Analysis, ISBN 038795452X, chapter 2. + *

+ * The approximated function should be smooth enough for Lagrange polynomial + * to work well. Otherwise, consider using splines instead.

+ * + * @since 1.2 + */ +public class PolynomialFunctionLagrangeForm implements UnivariateFunction { + /** + * The coefficients of the polynomial, ordered by degree -- i.e. + * coefficients[0] is the constant term and coefficients[n] is the + * coefficient of x^n where n is the degree of the polynomial. + */ + private double coefficients[]; + /** + * Interpolating points (abscissas). + */ + private final double x[]; + /** + * Function values at interpolating points. + */ + private final double y[]; + /** + * Whether the polynomial coefficients are available. + */ + private boolean coefficientsComputed; + + /** + * Construct a Lagrange polynomial with the given abscissas and function + * values. The order of interpolating points are not important. + *

+ * The constructor makes copy of the input arrays and assigns them.

+ * + * @param x interpolating points + * @param y function values at interpolating points + * @throws DimensionMismatchException if the array lengths are different. + * @throws NumberIsTooSmallException if the number of points is less than 2. + * @throws NonMonotonicSequenceException + * if two abscissae have the same value. + */ + public PolynomialFunctionLagrangeForm(double x[], double y[]) + throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException { + this.x = new double[x.length]; + this.y = new double[y.length]; + System.arraycopy(x, 0, this.x, 0, x.length); + System.arraycopy(y, 0, this.y, 0, y.length); + coefficientsComputed = false; + + if (!verifyInterpolationArray(x, y, false)) { + MathArrays.sortInPlace(this.x, this.y); + // Second check in case some abscissa is duplicated. + verifyInterpolationArray(this.x, this.y, true); + } + } + + /** + * Calculate the function value at the given point. + * + * @param z Point at which the function value is to be computed. + * @return the function value. + * @throws DimensionMismatchException if {@code x} and {@code y} have + * different lengths. + * @throws NonMonotonicSequenceException + * if {@code x} is not sorted in strictly increasing order. + * @throws NumberIsTooSmallException if the size of {@code x} is less + * than 2. + */ + public double value(double z) { + return evaluateInternal(x, y, z); + } + + /** + * Returns the degree of the polynomial. + * + * @return the degree of the polynomial + */ + public int degree() { + return x.length - 1; + } + + /** + * Returns a copy of the interpolating points array. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ * + * @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. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ * + * @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. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ *

+ * Note that coefficients computation can be ill-conditioned. Use with caution + * and only when it is necessary.

+ * + * @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 + * + * Neville's Algorithm. It takes O(n^2) time. + * + * @param x Interpolating points array. + * @param y Interpolating values array. + * @param z Point at which the function value is to be computed. + * @return the function value. + * @throws DimensionMismatchException if {@code x} and {@code y} have + * different lengths. + * @throws NonMonotonicSequenceException + * if {@code x} is not sorted in strictly increasing order. + * @throws NumberIsTooSmallException if the size of {@code x} is less + * than 2. + */ + public static double evaluate(double x[], double y[], double z) + throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException { + if (verifyInterpolationArray(x, y, false)) { + return evaluateInternal(x, y, z); + } + + // Array is not sorted. + final double[] xNew = new double[x.length]; + final double[] yNew = new double[y.length]; + System.arraycopy(x, 0, xNew, 0, x.length); + System.arraycopy(y, 0, yNew, 0, y.length); + + MathArrays.sortInPlace(xNew, yNew); + // Second check in case some abscissa is duplicated. + verifyInterpolationArray(xNew, yNew, true); + return evaluateInternal(xNew, yNew, z); + } + + /** + * Evaluate the Lagrange polynomial using + * + * Neville's Algorithm. It takes O(n^2) time. + * + * @param x Interpolating points array. + * @param y Interpolating values array. + * @param z Point at which the function value is to be computed. + * @return the function value. + * @throws DimensionMismatchException if {@code x} and {@code y} have + * different lengths. + * @throws NonMonotonicSequenceException + * if {@code x} is not sorted in strictly increasing order. + * @throws NumberIsTooSmallException if the size of {@code x} is less + * than 2. + */ + private static double evaluateInternal(double x[], double y[], double z) { + int nearest = 0; + final int n = x.length; + final double[] c = new double[n]; + final double[] d = new double[n]; + double min_dist = Double.POSITIVE_INFINITY; + for (int i = 0; i < n; i++) { + // initialize the difference arrays + c[i] = y[i]; + d[i] = y[i]; + // find out the abscissa closest to z + final double dist = FastMath.abs(z - x[i]); + if (dist < min_dist) { + nearest = i; + min_dist = dist; + } + } + + // initial approximation to the function value at z + double value = y[nearest]; + + for (int i = 1; i < n; i++) { + for (int j = 0; j < n-i; j++) { + final double tc = x[j] - z; + final double td = x[i+j] - z; + final double divider = x[j] - x[i+j]; + // update the difference arrays + final double w = (c[j+1] - d[j]) / divider; + c[j] = tc * w; + d[j] = td * w; + } + // sum up the difference terms to get the final value + if (nearest < 0.5*(n-i+1)) { + value += c[nearest]; // fork down + } else { + nearest--; + value += d[nearest]; // fork up + } + } + + return value; + } + + /** + * Calculate the coefficients of Lagrange polynomial from the + * interpolation data. It takes O(n^2) time. + * Note that this computation can be ill-conditioned: Use with caution + * and only when it is necessary. + */ + protected void computeCoefficients() { + final int n = degree() + 1; + coefficients = new double[n]; + for (int i = 0; i < n; i++) { + coefficients[i] = 0.0; + } + + // c[] are the coefficients of P(x) = (x-x[0])(x-x[1])...(x-x[n-1]) + final double[] c = new double[n+1]; + c[0] = 1.0; + for (int i = 0; i < n; i++) { + for (int j = i; j > 0; j--) { + c[j] = c[j-1] - c[j] * x[i]; + } + c[0] *= -x[i]; + c[i+1] = 1; + } + + final double[] tc = new double[n]; + for (int i = 0; i < n; i++) { + // d = (x[i]-x[0])...(x[i]-x[i-1])(x[i]-x[i+1])...(x[i]-x[n-1]) + double d = 1; + for (int j = 0; j < n; j++) { + if (i != j) { + d *= x[i] - x[j]; + } + } + final double t = y[i] / d; + // Lagrange polynomial is the sum of n terms, each of which is a + // polynomial of degree n-1. tc[] are the coefficients of the i-th + // numerator Pi(x) = (x-x[0])...(x-x[i-1])(x-x[i+1])...(x-x[n-1]). + tc[n-1] = c[n]; // actually c[n] = 1 + coefficients[n-1] += t * tc[n-1]; + for (int j = n-2; j >= 0; j--) { + tc[j] = c[j+1] + tc[j+1] * x[i]; + coefficients[j] += t * tc[j]; + } + } + + coefficientsComputed = true; + } + + /** + * Check that the interpolation arrays are valid. + * The arrays features checked by this method are that both arrays have the + * same length and this length is at least 2. + * + * @param x Interpolating points array. + * @param y Interpolating values array. + * @param abort Whether to throw an exception if {@code x} is not sorted. + * @throws DimensionMismatchException if the array lengths are different. + * @throws NumberIsTooSmallException if the number of points is less than 2. + * @throws NonMonotonicSequenceException + * if {@code x} is not sorted in strictly increasing order and {@code abort} + * is {@code true}. + * @return {@code false} if the {@code x} is not sorted in increasing order, + * {@code true} otherwise. + * @see #evaluate(double[], double[], double) + * @see #computeCoefficients() + */ + public static boolean verifyInterpolationArray(double x[], double y[], boolean abort) + throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException { + if (x.length != y.length) { + throw new DimensionMismatchException(x.length, y.length); + } + if (x.length < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS, 2, x.length, true); + } + + return MathArrays.checkOrder(x, MathArrays.OrderDirection.INCREASING, true, abort); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java new file mode 100644 index 000000000..8509160a9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.analysis.interpolation.DividedDifferenceInterpolator; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implements the representation of a real polynomial function in + * Newton Form. For reference, see Elementary Numerical Analysis, + * ISBN 0070124477, chapter 2. + *

+ * 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[]

+ * + * @since 1.2 + */ +public class PolynomialFunctionNewtonForm implements UnivariateDifferentiableFunction { + + /** + * The coefficients of the polynomial, ordered by degree -- i.e. + * coefficients[0] is the constant term and coefficients[n] is the + * coefficient of x^n where n is the degree of the polynomial. + */ + private double coefficients[]; + + /** + * Centers of the Newton polynomial. + */ + private final double c[]; + + /** + * When all c[i] = 0, a[] becomes normal polynomial coefficients, + * i.e. a[i] = coefficients[i]. + */ + private final double a[]; + + /** + * Whether the polynomial coefficients are available. + */ + private boolean coefficientsComputed; + + /** + * Construct a Newton polynomial with the given a[] and c[]. The order of + * centers are important in that if c[] shuffle, then values of a[] would + * completely change, not just a permutation of old a[]. + *

+ * The constructor makes copy of the input arrays and assigns them.

+ * + * @param a Coefficients in Newton form formula. + * @param c Centers. + * @throws NullArgumentException if any argument is {@code null}. + * @throws NoDataException if any array has zero length. + * @throws DimensionMismatchException if the size difference between + * {@code a} and {@code c} is not equal to 1. + */ + public PolynomialFunctionNewtonForm(double a[], double c[]) + throws NullArgumentException, NoDataException, DimensionMismatchException { + + verifyInputArray(a, c); + this.a = new double[a.length]; + this.c = new double[c.length]; + System.arraycopy(a, 0, this.a, 0, a.length); + System.arraycopy(c, 0, this.c, 0, c.length); + coefficientsComputed = false; + } + + /** + * Calculate the function value at the given point. + * + * @param z Point at which the function value is to be computed. + * @return the function value. + */ + public double value(double z) { + return evaluate(a, c, z); + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + verifyInputArray(a, c); + + final int n = c.length; + DerivativeStructure value = new DerivativeStructure(t.getFreeParameters(), t.getOrder(), a[n]); + for (int i = n - 1; i >= 0; i--) { + value = t.subtract(c[i]).multiply(value).add(a[i]); + } + + return value; + + } + + /** + * Returns the degree of the polynomial. + * + * @return the degree of the polynomial + */ + public int degree() { + return c.length; + } + + /** + * Returns a copy of coefficients in Newton form formula. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ * + * @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. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ * + * @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. + *

+ * Changes made to the returned copy will not affect the polynomial.

+ * + * @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 + * Horner's Rule and takes O(N) time. + * + * @param a Coefficients in Newton form formula. + * @param c Centers. + * @param z Point at which the function value is to be computed. + * @return the function value. + * @throws NullArgumentException if any argument is {@code null}. + * @throws NoDataException if any array has zero length. + * @throws DimensionMismatchException if the size difference between + * {@code a} and {@code c} is not equal to 1. + */ + public static double evaluate(double a[], double c[], double z) + throws NullArgumentException, DimensionMismatchException, NoDataException { + verifyInputArray(a, c); + + final int n = c.length; + double value = a[n]; + for (int i = n - 1; i >= 0; i--) { + value = a[i] + (z - c[i]) * value; + } + + return value; + } + + /** + * Calculate the normal polynomial coefficients given the Newton form. + * It also uses nested multiplication but takes O(N^2) time. + */ + protected void computeCoefficients() { + final int n = degree(); + + coefficients = new double[n+1]; + for (int i = 0; i <= n; i++) { + coefficients[i] = 0.0; + } + + coefficients[0] = a[n]; + for (int i = n-1; i >= 0; i--) { + for (int j = n-i; j > 0; j--) { + coefficients[j] = coefficients[j-1] - c[i] * coefficients[j]; + } + coefficients[0] = a[i] - c[i] * coefficients[0]; + } + + coefficientsComputed = true; + } + + /** + * Verifies that the input arrays are valid. + *

+ * The centers must be distinct for interpolation purposes, but not + * for general use. Thus it is not verified here.

+ * + * @param a the coefficients in Newton form formula + * @param c the centers + * @throws NullArgumentException if any argument is {@code null}. + * @throws NoDataException if any array has zero length. + * @throws DimensionMismatchException if the size difference between + * {@code a} and {@code c} is not equal to 1. + * @see DividedDifferenceInterpolator#computeDividedDifference(double[], + * double[]) + */ + protected static void verifyInputArray(double a[], double c[]) + throws NullArgumentException, NoDataException, DimensionMismatchException { + MathUtils.checkNotNull(a); + MathUtils.checkNotNull(c); + if (a.length == 0 || c.length == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY); + } + if (a.length != c.length + 1) { + throw new DimensionMismatchException(LocalizedFormats.ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1, + a.length, c.length); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java new file mode 100644 index 000000000..a3b5f0f5a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Represents a polynomial spline function. + *

+ * A polynomial spline function consists of a set of + * interpolating polynomials and an ascending array of domain + * knot points, 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 + * PolynomialSplineFunction itself, but are assumed to hold among + * the polynomials and knot points passed to the constructor.

+ *

+ * N.B.: The polynomials in the polynomials property must be + * centered on the knot points to compute the spline function values. + * See below.

+ *

+ * The domain of the polynomial spline function is + * [smallest knot, largest knot]. Attempts to evaluate the + * function at values outside of this range generate IllegalArgumentExceptions. + *

+ *

+ * The value of the polynomial spline function for an argument x + * is computed as follows: + *

    + *
  1. The knot array is searched to find the segment to which x + * belongs. If x is less than the smallest knot point or greater + * than the largest one, an IllegalArgumentException + * is thrown.
  2. + *
  3. Let j be the index of the largest knot point that is less + * than or equal to x. The value returned is + * {@code polynomials[j](x - knot[j])}
+ * + */ +public class PolynomialSplineFunction implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction { + /** + * Spline segment interval delimiters (knots). + * Size is n + 1 for n segments. + */ + private final double knots[]; + /** + * The polynomial functions that make up the spline. The first element + * determines the value of the spline over the first subinterval, the + * second over the second, etc. Spline function values are determined by + * evaluating these functions at {@code (x - knot[i])} where i is the + * knot segment to which x belongs. + */ + private final PolynomialFunction polynomials[]; + /** + * Number of spline segments. It is equal to the number of polynomials and + * to the number of partition points - 1. + */ + private final int n; + + + /** + * Construct a polynomial spline function with the given segment delimiters + * and interpolating polynomials. + * The constructor copies both arrays and assigns the copies to the knots + * and polynomials properties, respectively. + * + * @param knots Spline segment interval delimiters. + * @param polynomials Polynomial functions that make up the spline. + * @throws NullArgumentException if either of the input arrays is {@code null}. + * @throws NumberIsTooSmallException if knots has length less than 2. + * @throws DimensionMismatchException if {@code polynomials.length != knots.length - 1}. + * @throws NonMonotonicSequenceException if the {@code knots} array is not strictly increasing. + * + */ + public PolynomialSplineFunction(double knots[], PolynomialFunction polynomials[]) + throws NullArgumentException, NumberIsTooSmallException, + DimensionMismatchException, NonMonotonicSequenceException{ + if (knots == null || + polynomials == null) { + throw new NullArgumentException(); + } + if (knots.length < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION, + 2, knots.length, false); + } + if (knots.length - 1 != polynomials.length) { + throw new DimensionMismatchException(polynomials.length, knots.length); + } + MathArrays.checkOrder(knots); + + this.n = knots.length -1; + this.knots = new double[n + 1]; + System.arraycopy(knots, 0, this.knots, 0, n + 1); + this.polynomials = new PolynomialFunction[n]; + System.arraycopy(polynomials, 0, this.polynomials, 0, n); + } + + /** + * Compute the value for the function. + * See {@link PolynomialSplineFunction} for details on the algorithm for + * computing the value of the function. + * + * @param v Point for which the function value should be computed. + * @return the value. + * @throws OutOfRangeException if {@code v} is outside of the domain of the + * spline function (smaller than the smallest knot point or larger than the + * largest knot point). + */ + public double value(double v) { + if (v < knots[0] || v > knots[n]) { + throw new OutOfRangeException(v, knots[0], knots[n]); + } + int i = Arrays.binarySearch(knots, v); + if (i < 0) { + i = -i - 2; + } + // This will handle the case where v is the last knot value + // There are only n-1 polynomials, so if v is the last knot + // then we will use the last polynomial to calculate the value. + if ( i >= polynomials.length ) { + i--; + } + return polynomials[i].value(v - knots[i]); + } + + /** + * Get the derivative of the polynomial spline function. + * + * @return the derivative function. + */ + public UnivariateFunction derivative() { + return polynomialSplineDerivative(); + } + + /** + * Get the derivative of the polynomial spline function. + * + * @return the derivative function. + */ + public PolynomialSplineFunction polynomialSplineDerivative() { + PolynomialFunction derivativePolynomials[] = new PolynomialFunction[n]; + for (int i = 0; i < n; i++) { + derivativePolynomials[i] = polynomials[i].polynomialDerivative(); + } + return new PolynomialSplineFunction(knots, derivativePolynomials); + } + + + /** {@inheritDoc} + * @since 3.1 + */ + public DerivativeStructure value(final DerivativeStructure t) { + final double t0 = t.getValue(); + if (t0 < knots[0] || t0 > knots[n]) { + throw new OutOfRangeException(t0, knots[0], knots[n]); + } + int i = Arrays.binarySearch(knots, t0); + if (i < 0) { + i = -i - 2; + } + // This will handle the case where t is the last knot value + // There are only n-1 polynomials, so if t is the last knot + // then we will use the last polynomial to calculate the value. + if ( i >= polynomials.length ) { + i--; + } + return polynomials[i].value(t.subtract(knots[i])); + } + + /** + * Get the number of spline segments. + * It is also the number of polynomials and the number of knot points - 1. + * + * @return the number of spline segments. + */ + public int getN() { + return n; + } + + /** + * Get a copy of the interpolating polynomials array. + * It returns a fresh copy of the array. Changes made to the copy will + * not affect the polynomials property. + * + * @return the interpolating polynomials. + */ + public PolynomialFunction[] getPolynomials() { + PolynomialFunction p[] = new PolynomialFunction[n]; + System.arraycopy(polynomials, 0, p, 0, n); + return p; + } + + /** + * Get an array copy of the knot points. + * It returns a fresh copy of the array. Changes made to the copy + * will not affect the knots property. + * + * @return the knot points. + */ + public double[] getKnots() { + double out[] = new double[n + 1]; + System.arraycopy(knots, 0, out, 0, n + 1); + return out; + } + + /** + * Indicates whether a point is within the interpolation range. + * + * @param x Point. + * @return {@code true} if {@code x} is a valid point. + */ + public boolean isValidPoint(double x) { + if (x < knots[0] || + x > knots[n]) { + return false; + } else { + return true; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java new file mode 100644 index 000000000..1d526cc74 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java @@ -0,0 +1,444 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * A collection of static methods that operate on or return polynomials. + * + * @since 2.0 + */ +public class PolynomialsUtils { + + /** Coefficients for Chebyshev polynomials. */ + private static final List CHEBYSHEV_COEFFICIENTS; + + /** Coefficients for Hermite polynomials. */ + private static final List HERMITE_COEFFICIENTS; + + /** Coefficients for Laguerre polynomials. */ + private static final List LAGUERRE_COEFFICIENTS; + + /** Coefficients for Legendre polynomials. */ + private static final List LEGENDRE_COEFFICIENTS; + + /** Coefficients for Jacobi polynomials. */ + private static final Map> JACOBI_COEFFICIENTS; + + static { + + // initialize recurrence for Chebyshev polynomials + // T0(X) = 1, T1(X) = 0 + 1 * X + CHEBYSHEV_COEFFICIENTS = new ArrayList(); + 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(); + 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(); + 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(); + LEGENDRE_COEFFICIENTS.add(BigFraction.ONE); + LEGENDRE_COEFFICIENTS.add(BigFraction.ZERO); + LEGENDRE_COEFFICIENTS.add(BigFraction.ONE); + + // initialize map for Jacobi polynomials + JACOBI_COEFFICIENTS = new HashMap>(); + + } + + /** + * Private constructor, to prevent instantiation. + */ + private PolynomialsUtils() { + } + + /** + * Create a Chebyshev polynomial of the first kind. + *

Chebyshev + * polynomials of the first kind are orthogonal polynomials. + * They can be defined by the following recurrence relations:

+ * \( + * T_0(x) = 1 \\ + * T_1(x) = x \\ + * T_{k+1}(x) = 2x T_k(x) - T_{k-1}(x) + * \) + *

+ * @param degree degree of the polynomial + * @return Chebyshev polynomial of specified degree + */ + public static PolynomialFunction createChebyshevPolynomial(final int degree) { + return buildPolynomial(degree, CHEBYSHEV_COEFFICIENTS, + new RecurrenceCoefficientsGenerator() { + /** Fixed recurrence coefficients. */ + private final BigFraction[] coeffs = { BigFraction.ZERO, BigFraction.TWO, BigFraction.ONE }; + /** {@inheritDoc} */ + public BigFraction[] generate(int k) { + return coeffs; + } + }); + } + + /** + * Create a Hermite polynomial. + *

Hermite + * polynomials are orthogonal polynomials. + * They can be defined by the following recurrence relations:

+ * \( + * H_0(x) = 1 \\ + * H_1(x) = 2x \\ + * H_{k+1}(x) = 2x H_k(X) - 2k H_{k-1}(x) + * \) + *

+ + * @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. + *

Laguerre + * polynomials are orthogonal polynomials. + * They can be defined by the following recurrence relations:

+ * \( + * L_0(x) = 1 \\ + * L_1(x) = 1 - x \\ + * (k+1) L_{k+1}(x) = (2k + 1 - x) L_k(x) - k L_{k-1}(x) + * \) + *

+ * @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. + *

Legendre + * polynomials are orthogonal polynomials. + * They can be defined by the following recurrence relations:

+ * \( + * P_0(x) = 1 \\ + * P_1(x) = x \\ + * (k+1) P_{k+1}(x) = (2k+1) x P_k(x) - k P_{k-1}(x) + * \) + *

+ * @param degree degree of the polynomial + * @return Legendre polynomial of specified degree + */ + public static PolynomialFunction createLegendrePolynomial(final int degree) { + return buildPolynomial(degree, LEGENDRE_COEFFICIENTS, + new RecurrenceCoefficientsGenerator() { + /** {@inheritDoc} */ + public BigFraction[] generate(int k) { + final int kP1 = k + 1; + return new BigFraction[] { + BigFraction.ZERO, + new BigFraction(k + kP1, kP1), + new BigFraction(k, kP1)}; + } + }); + } + + /** + * Create a Jacobi polynomial. + *

Jacobi + * polynomials are orthogonal polynomials. + * They can be defined by the following recurrence relations:

+ * \( + * P_0^{vw}(x) = 1 \\ + * P_{-1}^{vw}(x) = 0 \\ + * 2k(k + v + w)(2k + v + w - 2) P_k^{vw}(x) = \\ + * (2k + v + w - 1)[(2k + v + w)(2k + v + w - 2) x + v^2 - w^2] P_{k-1}^{vw}(x) \\ + * - 2(k + v - 1)(k + w - 1)(2k + v + w) P_{k-2}^{vw}(x) + * \) + *

+ * @param degree degree of the polynomial + * @param v first exponent + * @param w second exponent + * @return Jacobi polynomial of specified degree + */ + public static PolynomialFunction createJacobiPolynomial(final int degree, final int v, final int w) { + + // select the appropriate list + final JacobiKey key = new JacobiKey(v, w); + + if (!JACOBI_COEFFICIENTS.containsKey(key)) { + + // allocate a new list for v, w + final List list = new ArrayList(); + JACOBI_COEFFICIENTS.put(key, list); + + // Pv,w,0(x) = 1; + list.add(BigFraction.ONE); + + // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2 + list.add(new BigFraction(v - w, 2)); + list.add(new BigFraction(2 + v + w, 2)); + + } + + return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key), + new RecurrenceCoefficientsGenerator() { + /** {@inheritDoc} */ + public BigFraction[] generate(int k) { + k++; + final int kvw = k + v + w; + final int twoKvw = kvw + k; + final int twoKvwM1 = twoKvw - 1; + final int twoKvwM2 = twoKvw - 2; + final int den = 2 * k * kvw * twoKvwM2; + + return new BigFraction[] { + new BigFraction(twoKvwM1 * (v * v - w * w), den), + new BigFraction(twoKvwM1 * twoKvw * twoKvwM2, den), + new BigFraction(2 * (k + v - 1) * (k + w - 1) * twoKvw, den) + }; + } + }); + + } + + /** Inner class for Jacobi polynomials keys. */ + private static class JacobiKey { + + /** First exponent. */ + private final int v; + + /** Second exponent. */ + private final int w; + + /** Simple constructor. + * @param v first exponent + * @param w second exponent + */ + JacobiKey(final int v, final int w) { + this.v = v; + this.w = w; + } + + /** Get hash code. + * @return hash code + */ + @Override + public int hashCode() { + return (v << 16) ^ w; + } + + /** Check if the instance represent the same key as another instance. + * @param key other key + * @return true if the instance and the other key refer to the same polynomial + */ + @Override + public boolean equals(final Object key) { + + if ((key == null) || !(key instanceof JacobiKey)) { + return false; + } + + final JacobiKey otherK = (JacobiKey) key; + return (v == otherK.v) && (w == otherK.w); + + } + } + + /** + * Compute the coefficients of the polynomial \(P_s(x)\) + * whose values at point {@code x} will be the same as the those from the + * original polynomial \(P(x)\) when computed at {@code x + shift}. + *

+ * More precisely, let \(\Delta = \) {@code shift} and let + * \(P_s(x) = P(x + \Delta)\). The returned array + * consists of the coefficients of \(P_s\). So if \(a_0, ..., a_{n-1}\) + * are the coefficients of \(P\), then the returned array + * \(b_0, ..., b_{n-1}\) satisfies the identity + * \(\sum_{i=0}^{n-1} b_i x^i = \sum_{i=0}^{n-1} a_i (x + \Delta)^i\) for all \(x\). + * + * @param coefficients Coefficients of the original polynomial. + * @param shift Shift value. + * @return the coefficients \(b_i\) of the shifted + * polynomial. + */ + public static double[] shift(final double[] coefficients, + final double shift) { + final int dp1 = coefficients.length; + final double[] newCoefficients = new double[dp1]; + + // Pascal triangle. + final int[][] coeff = new int[dp1][dp1]; + for (int i = 0; i < dp1; i++){ + for(int j = 0; j <= i; j++){ + coeff[i][j] = (int) CombinatoricsUtils.binomialCoefficient(i, j); + } + } + + // First polynomial coefficient. + for (int i = 0; i < dp1; i++){ + newCoefficients[0] += coefficients[i] * FastMath.pow(shift, i); + } + + // Superior order. + final int d = dp1 - 1; + for (int i = 0; i < d; i++) { + for (int j = i; j < d; j++){ + newCoefficients[i + 1] += coeff[j + 1][j - i] * + coefficients[j + 1] * FastMath.pow(shift, j - i); + } + } + + return newCoefficients; + } + + + /** Get the coefficients array for a given degree. + * @param degree degree of the polynomial + * @param coefficients list where the computed coefficients are stored + * @param generator recurrence coefficients generator + * @return coefficients array + */ + private static PolynomialFunction buildPolynomial(final int degree, + final List coefficients, + final RecurrenceCoefficientsGenerator generator) { + synchronized (coefficients) { + final int maxDegree = (int) FastMath.floor(FastMath.sqrt(2 * coefficients.size())) - 1; + if (degree > maxDegree) { + computeUpToDegree(degree, maxDegree, generator, coefficients); + } + } + + // coefficient for polynomial 0 is l [0] + // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1) + // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2) + // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3) + // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4) + // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5) + // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6) + // ... + final int start = degree * (degree + 1) / 2; + + final double[] a = new double[degree + 1]; + for (int i = 0; i <= degree; ++i) { + a[i] = coefficients.get(start + i).doubleValue(); + } + + // build the polynomial + return new PolynomialFunction(a); + + } + + /** Compute polynomial coefficients up to a given degree. + * @param degree maximal degree + * @param maxDegree current maximal degree + * @param generator recurrence coefficients generator + * @param coefficients list where the computed coefficients should be appended + */ + private static void computeUpToDegree(final int degree, final int maxDegree, + final RecurrenceCoefficientsGenerator generator, + final List coefficients) { + + int startK = (maxDegree - 1) * maxDegree / 2; + for (int k = maxDegree; k < degree; ++k) { + + // start indices of two previous polynomials Pk(X) and Pk-1(X) + int startKm1 = startK; + startK += k; + + // Pk+1(X) = (a[0] + a[1] X) Pk(X) - a[2] Pk-1(X) + BigFraction[] ai = generator.generate(k); + + BigFraction ck = coefficients.get(startK); + BigFraction ckm1 = coefficients.get(startKm1); + + // degree 0 coefficient + coefficients.add(ck.multiply(ai[0]).subtract(ckm1.multiply(ai[2]))); + + // degree 1 to degree k-1 coefficients + for (int i = 1; i < k; ++i) { + final BigFraction ckPrev = ck; + ck = coefficients.get(startK + i); + ckm1 = coefficients.get(startKm1 + i); + coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])).subtract(ckm1.multiply(ai[2]))); + } + + // degree k coefficient + final BigFraction ckPrev = ck; + ck = coefficients.get(startK + k); + coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1]))); + + // degree k+1 coefficient + coefficients.add(ck.multiply(ai[1])); + + } + + } + + /** Interface for recurrence coefficients generation. */ + private interface RecurrenceCoefficientsGenerator { + /** + * Generate recurrence coefficients. + * @param k highest degree of the polynomials used in the recurrence + * @return an array of three coefficients such that + * \( P_{k+1}(x) = (a[0] + a[1] x) P_k(x) - a[2] P_{k-1}(x) \) + */ + BigFraction[] generate(int k); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/package-info.java new file mode 100644 index 000000000..a4a2b14f1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/polynomials/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Univariate real polynomials implementations, seen as differentiable + * univariate real functions. + * + */ +package com.fr.third.org.apache.commons.math3.analysis.polynomials; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java new file mode 100644 index 000000000..e5bcfd0a9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Provide a default implementation for several functions useful to generic + * solvers. + * + * @since 3.0 + * @deprecated as of 3.1, replaced by {@link AbstractUnivariateDifferentiableSolver} + */ +@Deprecated +public abstract class AbstractDifferentiableUnivariateSolver + extends BaseAbstractUnivariateSolver + implements DifferentiableUnivariateSolver { + /** Derivative of the function to solve. */ + private UnivariateFunction functionDerivative; + + /** + * Construct a solver with given absolute accuracy. + * + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractDifferentiableUnivariateSolver(final double absoluteAccuracy) { + super(absoluteAccuracy); + } + + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + */ + protected AbstractDifferentiableUnivariateSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at specified point. + * @throws TooManyEvaluationsException if the maximal number of evaluations is exceeded. + */ + protected double computeDerivativeObjectiveValue(double point) + throws TooManyEvaluationsException { + incrementEvaluationCount(); + return functionDerivative.value(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setup(int maxEval, DifferentiableUnivariateFunction f, + double min, double max, double startValue) { + super.setup(maxEval, f, min, max, startValue); + functionDerivative = f.derivative(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java new file mode 100644 index 000000000..bc163365a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; + +/** + * Base class for solvers. + * + * @since 3.0 + */ +public abstract class AbstractPolynomialSolver + extends BaseAbstractUnivariateSolver + implements PolynomialSolver { + /** Function. */ + private PolynomialFunction polynomialFunction; + + /** + * Construct a solver with given absolute accuracy. + * + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractPolynomialSolver(final double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractPolynomialSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + */ + protected AbstractPolynomialSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setup(int maxEval, PolynomialFunction f, + double min, double max, double startValue) { + super.setup(maxEval, f, min, max, startValue); + polynomialFunction = f; + } + + /** + * @return the coefficients of the polynomial function. + */ + protected double[] getCoefficients() { + return polynomialFunction.getCoefficients(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java new file mode 100644 index 000000000..14a2a366f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; + +/** + * Provide a default implementation for several functions useful to generic + * solvers. + * + * @since 3.1 + */ +public abstract class AbstractUnivariateDifferentiableSolver + extends BaseAbstractUnivariateSolver + implements UnivariateDifferentiableSolver { + + /** Function to solve. */ + private UnivariateDifferentiableFunction function; + + /** + * Construct a solver with given absolute accuracy. + * + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractUnivariateDifferentiableSolver(final double absoluteAccuracy) { + super(absoluteAccuracy); + } + + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + */ + protected AbstractUnivariateDifferentiableSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value and derivative at specified point. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + */ + protected DerivativeStructure computeObjectiveValueAndDerivative(double point) + throws TooManyEvaluationsException { + incrementEvaluationCount(); + return function.value(new DerivativeStructure(1, 1, 0, point)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setup(int maxEval, UnivariateDifferentiableFunction f, + double min, double max, double startValue) { + super.setup(maxEval, f, min, max, startValue); + function = f; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java new file mode 100644 index 000000000..41abe94eb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Base class for solvers. + * + * @since 3.0 + */ +public abstract class AbstractUnivariateSolver + extends BaseAbstractUnivariateSolver + implements UnivariateSolver { + /** + * Construct a solver with given absolute accuracy. + * + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractUnivariateSolver(final double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + */ + protected AbstractUnivariateSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + */ + protected AbstractUnivariateSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AllowedSolution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AllowedSolution.java new file mode 100644 index 000000000..e7b968eae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/AllowedSolution.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 com.fr.third.org.apache.commons.math3.analysis.solvers; + + +import com.fr.third.org.apache.commons.math3.ode.ODEIntegrator; +import com.fr.third.org.apache.commons.math3.ode.events.EventHandler; + +/** The kinds of solutions that a {@link BracketedUnivariateSolver + * (bracketed univariate real) root-finding algorithm} may accept as solutions. + * This basically controls whether or not under-approximations and + * over-approximations are allowed. + * + *

If all solutions are accepted ({@link #ANY_SIDE}), then the solution + * that the root-finding algorithm returns for a given root may be equal to the + * actual root, but it may also be an approximation that is slightly smaller + * or slightly larger than the actual root. Root-finding algorithms generally + * only guarantee that the returned solution is within the requested + * tolerances. In certain cases however, in particular for + * {@link EventHandler state events} of + * {@link ODEIntegrator ODE solvers}, it + * may be necessary to guarantee that a solution is returned that lies on a + * specific side the solution.

+ * + * @see BracketedUnivariateSolver + * @since 3.0 + */ +public enum AllowedSolution { + /** There are no additional side restriction on the solutions for + * root-finding. That is, both under-approximations and over-approximations + * are allowed. So, if a function f(x) has a root at x = x0, then the + * root-finding result s may be smaller than x0, equal to x0, or greater + * than x0. + */ + ANY_SIDE, + + /** Only solutions that are less than or equal to the actual root are + * acceptable as solutions for root-finding. In other words, + * over-approximations are not allowed. So, if a function f(x) has a root + * at x = x0, then the root-finding result s must satisfy s <= x0. + */ + LEFT_SIDE, + + /** Only solutions that are greater than or equal to the actual root are + * acceptable as solutions for root-finding. In other words, + * under-approximations are not allowed. So, if a function f(x) has a root + * at x = x0, then the root-finding result s must satisfy s >= x0. + */ + RIGHT_SIDE, + + /** Only solutions for which values are less than or equal to zero are + * acceptable as solutions for root-finding. So, if a function f(x) has + * a root at x = x0, then the root-finding result s must satisfy f(s) <= 0. + */ + BELOW_SIDE, + + /** Only solutions for which values are greater than or equal to zero are + * acceptable as solutions for root-finding. So, if a function f(x) has + * a root at x = x0, then the root-finding result s must satisfy f(s) >= 0. + */ + ABOVE_SIDE; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java new file mode 100644 index 000000000..c508c702a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Provide a default implementation for several functions useful to generic + * solvers. + * The default values for relative and function tolerances are 1e-14 + * and 1e-15, respectively. It is however highly recommended to not + * rely on the default, but rather carefully consider values that match + * user's expectations, as well as the specifics of each implementation. + * + * @param Type of function to solve. + * + * @since 2.0 + */ +public abstract class BaseAbstractUnivariateSolver + implements BaseUnivariateSolver { + /** Default relative accuracy. */ + private static final double DEFAULT_RELATIVE_ACCURACY = 1e-14; + /** Default function value accuracy. */ + private static final double DEFAULT_FUNCTION_VALUE_ACCURACY = 1e-15; + /** Function value accuracy. */ + private final double functionValueAccuracy; + /** Absolute accuracy. */ + private final double absoluteAccuracy; + /** Relative accuracy. */ + private final double relativeAccuracy; + /** Evaluations counter. */ + private IntegerSequence.Incrementor evaluations; + /** Lower end of search interval. */ + private double searchMin; + /** Higher end of search interval. */ + private double searchMax; + /** Initial guess. */ + private double searchStart; + /** Function to solve. */ + private FUNC function; + + /** + * Construct a solver with given absolute accuracy. + * + * @param absoluteAccuracy Maximum absolute error. + */ + protected BaseAbstractUnivariateSolver(final double absoluteAccuracy) { + this(DEFAULT_RELATIVE_ACCURACY, + absoluteAccuracy, + DEFAULT_FUNCTION_VALUE_ACCURACY); + } + + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + */ + protected BaseAbstractUnivariateSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + this(relativeAccuracy, + absoluteAccuracy, + DEFAULT_FUNCTION_VALUE_ACCURACY); + } + + /** + * Construct a solver with given accuracies. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + */ + protected BaseAbstractUnivariateSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + this.absoluteAccuracy = absoluteAccuracy; + this.relativeAccuracy = relativeAccuracy; + this.functionValueAccuracy = functionValueAccuracy; + this.evaluations = IntegerSequence.Incrementor.create(); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + /** + * @return the lower end of the search interval. + */ + public double getMin() { + return searchMin; + } + /** + * @return the higher end of the search interval. + */ + public double getMax() { + return searchMax; + } + /** + * @return the initial guess. + */ + public double getStartValue() { + return searchStart; + } + /** + * {@inheritDoc} + */ + public double getAbsoluteAccuracy() { + return absoluteAccuracy; + } + /** + * {@inheritDoc} + */ + public double getRelativeAccuracy() { + return relativeAccuracy; + } + /** + * {@inheritDoc} + */ + public double getFunctionValueAccuracy() { + return functionValueAccuracy; + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at specified point. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + */ + protected double computeObjectiveValue(double point) + throws TooManyEvaluationsException { + incrementEvaluationCount(); + return function.value(point); + } + + /** + * Prepare for computation. + * Subclasses must call this method if they override any of the + * {@code solve} methods. + * + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param maxEval Maximum number of evaluations. + * @exception NullArgumentException if f is null + */ + protected void setup(int maxEval, + FUNC f, + double min, double max, + double startValue) + throws NullArgumentException { + // Checks. + MathUtils.checkNotNull(f); + + // Reset. + searchMin = min; + searchMax = max; + searchStart = startValue; + function = f; + evaluations = evaluations.withMaximalCount(maxEval).withStart(0); + } + + /** {@inheritDoc} */ + public double solve(int maxEval, FUNC f, double min, double max, double startValue) + throws TooManyEvaluationsException, + NoBracketingException { + // Initialization. + setup(maxEval, f, min, max, startValue); + + // Perform computation. + return doSolve(); + } + + /** {@inheritDoc} */ + public double solve(int maxEval, FUNC f, double min, double max) { + return solve(maxEval, f, min, max, min + 0.5 * (max - min)); + } + + /** {@inheritDoc} */ + public double solve(int maxEval, FUNC f, double startValue) + throws TooManyEvaluationsException, + NoBracketingException { + return solve(maxEval, f, Double.NaN, Double.NaN, startValue); + } + + /** + * Method for implementing actual optimization algorithms in derived + * classes. + * + * @return the root. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + * @throws NoBracketingException if the initial search interval does not bracket + * a root and the solver requires it. + */ + protected abstract double doSolve() + throws TooManyEvaluationsException, NoBracketingException; + + /** + * Check whether the function takes opposite signs at the endpoints. + * + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @return {@code true} if the function values have opposite signs at the + * given points. + */ + protected boolean isBracketing(final double lower, + final double upper) { + return UnivariateSolverUtils.isBracketing(function, lower, upper); + } + + /** + * Check whether the arguments form a (strictly) increasing sequence. + * + * @param start First number. + * @param mid Second number. + * @param end Third number. + * @return {@code true} if the arguments form an increasing sequence. + */ + protected boolean isSequence(final double start, + final double mid, + final double end) { + return UnivariateSolverUtils.isSequence(start, mid, end); + } + + /** + * Check that the endpoints specify an interval. + * + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + protected void verifyInterval(final double lower, + final double upper) + throws NumberIsTooLargeException { + UnivariateSolverUtils.verifyInterval(lower, upper); + } + + /** + * Check that {@code lower < initial < upper}. + * + * @param lower Lower endpoint. + * @param initial Initial value. + * @param upper Upper endpoint. + * @throws NumberIsTooLargeException if {@code lower >= initial} or + * {@code initial >= upper}. + */ + protected void verifySequence(final double lower, + final double initial, + final double upper) + throws NumberIsTooLargeException { + UnivariateSolverUtils.verifySequence(lower, initial, upper); + } + + /** + * Check that the endpoints specify an interval and the function takes + * opposite signs at the endpoints. + * + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @throws NullArgumentException if the function has not been set. + * @throws NoBracketingException if the function has the same sign at + * the endpoints. + */ + protected void verifyBracketing(final double lower, + final double upper) + throws NullArgumentException, + NoBracketingException { + UnivariateSolverUtils.verifyBracketing(function, lower, upper); + } + + /** + * Increment the evaluation count by one. + * Method {@link #computeObjectiveValue(double)} calls this method internally. + * It is provided for subclasses that do not exclusively use + * {@code computeObjectiveValue} to solve the function. + * See e.g. {@link AbstractUnivariateDifferentiableSolver}. + * + * @throws TooManyEvaluationsException when the allowed number of function + * evaluations has been exhausted. + */ + protected void incrementEvaluationCount() + throws TooManyEvaluationsException { + try { + evaluations.increment(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java new file mode 100644 index 000000000..7a5190cb1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Base class for all bracketing Secant-based methods for root-finding + * (approximating a zero of a univariate real function). + * + *

Implementation of the {@link RegulaFalsiSolver Regula Falsi} and + * {@link IllinoisSolver Illinois} methods is based on the + * following article: M. Dowell and P. Jarratt, + * A modified regula falsi method for computing the root of an + * equation, BIT Numerical Mathematics, volume 11, number 2, + * pages 168-174, Springer, 1971.

+ * + *

Implementation of the {@link PegasusSolver Pegasus} method is + * based on the following article: M. Dowell and P. Jarratt, + * The "Pegasus" method for computing the root of an equation, + * BIT Numerical Mathematics, volume 12, number 4, pages 503-508, Springer, + * 1972.

+ * + *

The {@link SecantSolver Secant} method is not a + * bracketing method, so it is not implemented here. It has a separate + * implementation.

+ * + * @since 3.0 + */ +public abstract class BaseSecantSolver + extends AbstractUnivariateSolver + implements BracketedUnivariateSolver { + + /** Default absolute accuracy. */ + protected static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** The kinds of solutions that the algorithm may accept. */ + private AllowedSolution allowed; + + /** The Secant-based root-finding method to use. */ + private final Method method; + + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + * @param method Secant-based root-finding method to use. + */ + protected BaseSecantSolver(final double absoluteAccuracy, final Method method) { + super(absoluteAccuracy); + this.allowed = AllowedSolution.ANY_SIDE; + this.method = method; + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param method Secant-based root-finding method to use. + */ + protected BaseSecantSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final Method method) { + super(relativeAccuracy, absoluteAccuracy); + this.allowed = AllowedSolution.ANY_SIDE; + this.method = method; + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Maximum relative error. + * @param absoluteAccuracy Maximum absolute error. + * @param functionValueAccuracy Maximum function value error. + * @param method Secant-based root-finding method to use + */ + protected BaseSecantSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy, + final Method method) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + this.allowed = AllowedSolution.ANY_SIDE; + this.method = method; + } + + /** {@inheritDoc} */ + public double solve(final int maxEval, final UnivariateFunction f, + final double min, final double max, + final AllowedSolution allowedSolution) { + return solve(maxEval, f, min, max, min + 0.5 * (max - min), allowedSolution); + } + + /** {@inheritDoc} */ + public double solve(final int maxEval, final UnivariateFunction f, + final double min, final double max, final double startValue, + final AllowedSolution allowedSolution) { + this.allowed = allowedSolution; + return super.solve(maxEval, f, min, max, startValue); + } + + /** {@inheritDoc} */ + @Override + public double solve(final int maxEval, final UnivariateFunction f, + final double min, final double max, final double startValue) { + return solve(maxEval, f, min, max, startValue, AllowedSolution.ANY_SIDE); + } + + /** + * {@inheritDoc} + * + * @throws ConvergenceException if the algorithm failed due to finite + * precision. + */ + @Override + protected final double doSolve() + throws ConvergenceException { + // Get initial solution + double x0 = getMin(); + double x1 = getMax(); + double f0 = computeObjectiveValue(x0); + double f1 = computeObjectiveValue(x1); + + // If one of the bounds is the exact root, return it. Since these are + // not under-approximations or over-approximations, we can return them + // regardless of the allowed solutions. + if (f0 == 0.0) { + return x0; + } + if (f1 == 0.0) { + return x1; + } + + // Verify bracketing of initial solution. + verifyBracketing(x0, x1); + + // Get accuracies. + final double ftol = getFunctionValueAccuracy(); + final double atol = getAbsoluteAccuracy(); + final double rtol = getRelativeAccuracy(); + + // Keep track of inverted intervals, meaning that the left bound is + // larger than the right bound. + boolean inverted = false; + + // Keep finding better approximations. + while (true) { + // Calculate the next approximation. + final double x = x1 - ((f1 * (x1 - x0)) / (f1 - f0)); + final double fx = computeObjectiveValue(x); + + // If the new approximation is the exact root, return it. Since + // this is not an under-approximation or an over-approximation, + // we can return it regardless of the allowed solutions. + if (fx == 0.0) { + return x; + } + + // Update the bounds with the new approximation. + if (f1 * fx < 0) { + // The value of x1 has switched to the other bound, thus inverting + // the interval. + x0 = x1; + f0 = f1; + inverted = !inverted; + } else { + switch (method) { + case ILLINOIS: + f0 *= 0.5; + break; + case PEGASUS: + f0 *= f1 / (f1 + fx); + break; + case REGULA_FALSI: + // Detect early that algorithm is stuck, instead of waiting + // for the maximum number of iterations to be exceeded. + if (x == x1) { + throw new ConvergenceException(); + } + break; + default: + // Should never happen. + throw new MathInternalError(); + } + } + // Update from [x0, x1] to [x0, x]. + x1 = x; + f1 = fx; + + // If the function value of the last approximation is too small, + // given the function value accuracy, then we can't get closer to + // the root than we already are. + if (FastMath.abs(f1) <= ftol) { + switch (allowed) { + case ANY_SIDE: + return x1; + case LEFT_SIDE: + if (inverted) { + return x1; + } + break; + case RIGHT_SIDE: + if (!inverted) { + return x1; + } + break; + case BELOW_SIDE: + if (f1 <= 0) { + return x1; + } + break; + case ABOVE_SIDE: + if (f1 >= 0) { + return x1; + } + break; + default: + throw new MathInternalError(); + } + } + + // If the current interval is within the given accuracies, we + // are satisfied with the current approximation. + if (FastMath.abs(x1 - x0) < FastMath.max(rtol * FastMath.abs(x1), + atol)) { + switch (allowed) { + case ANY_SIDE: + return x1; + case LEFT_SIDE: + return inverted ? x1 : x0; + case RIGHT_SIDE: + return inverted ? x0 : x1; + case BELOW_SIDE: + return (f1 <= 0) ? x1 : x0; + case ABOVE_SIDE: + return (f1 >= 0) ? x1 : x0; + default: + throw new MathInternalError(); + } + } + } + } + + /** Secant-based root-finding methods. */ + protected enum Method { + + /** + * The {@link RegulaFalsiSolver Regula Falsi} or + * False Position method. + */ + REGULA_FALSI, + + /** The {@link IllinoisSolver Illinois} method. */ + ILLINOIS, + + /** The {@link PegasusSolver Pegasus} method. */ + PEGASUS; + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java new file mode 100644 index 000000000..543503b07 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + + +/** + * Interface for (univariate real) rootfinding algorithms. + * Implementations will search for only one zero in the given interval. + * + * This class is not intended for use outside of the Apache Commons Math + * library, regular user should rely on more specific interfaces like + * {@link UnivariateSolver}, {@link PolynomialSolver} or {@link + * DifferentiableUnivariateSolver}. + * @param Type of function to solve. + * + * @since 3.0 + * @see UnivariateSolver + * @see PolynomialSolver + * @see DifferentiableUnivariateSolver + */ +public interface BaseUnivariateSolver { + /** + * Get the maximum number of function evaluations. + * + * @return the maximum number of function evaluations. + */ + int getMaxEvaluations(); + + /** + * Get the number of evaluations of the objective function. + * The number of evaluations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + int getEvaluations(); + + /** + * Get the absolute accuracy of the solver. Solutions returned by the + * solver should be accurate to this tolerance, i.e., if ε is the + * absolute accuracy of the solver and {@code v} is a value returned by + * one of the {@code solve} methods, then a root of the function should + * exist somewhere in the interval ({@code v} - ε, {@code v} + ε). + * + * @return the absolute accuracy. + */ + double getAbsoluteAccuracy(); + + /** + * Get the relative accuracy of the solver. The contract for relative + * accuracy is the same as {@link #getAbsoluteAccuracy()}, but using + * relative, rather than absolute error. If ρ is the relative accuracy + * configured for a solver and {@code v} is a value returned, then a root + * of the function should exist somewhere in the interval + * ({@code v} - ρ {@code v}, {@code v} + ρ {@code v}). + * + * @return the relative accuracy. + */ + double getRelativeAccuracy(); + + /** + * Get the function value accuracy of the solver. If {@code v} is + * a value returned by the solver for a function {@code f}, + * then by contract, {@code |f(v)|} should be less than or equal to + * the function value accuracy configured for the solver. + * + * @return the function value accuracy. + */ + double getFunctionValueAccuracy(); + + /** + * Solve for a zero root in the given interval. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @return a value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + double solve(int maxEval, FUNC f, double min, double max) + throws MathIllegalArgumentException, TooManyEvaluationsException; + + /** + * Solve for a zero in the given interval, start at {@code startValue}. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @return a value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + double solve(int maxEval, FUNC f, double min, double max, double startValue) + throws MathIllegalArgumentException, TooManyEvaluationsException; + + /** + * Solve for a zero in the vicinity of {@code startValue}. + * + * @param f Function to solve. + * @param startValue Start value to use. + * @return a value where the function is zero. + * @param maxEval Maximum number of evaluations. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + double solve(int maxEval, FUNC f, double startValue); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BisectionSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BisectionSolver.java new file mode 100644 index 000000000..1b7bd0111 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BisectionSolver.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * bisection algorithm for finding zeros of univariate real functions. + *

+ * The function should be continuous but not necessarily smooth.

+ * + */ +public class BisectionSolver extends AbstractUnivariateSolver { + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver with default accuracy (1e-6). + */ + public BisectionSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public BisectionSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public BisectionSolver(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException { + double min = getMin(); + double max = getMax(); + verifyInterval(min, max); + final double absoluteAccuracy = getAbsoluteAccuracy(); + double m; + double fm; + double fmin; + + while (true) { + m = UnivariateSolverUtils.midpoint(min, max); + fmin = computeObjectiveValue(min); + fm = computeObjectiveValue(m); + + if (fm * fmin > 0) { + // max and m bracket the root. + min = m; + } else { + // min and m bracket the root. + max = m; + } + + if (FastMath.abs(max - min) <= absoluteAccuracy) { + m = UnivariateSolverUtils.midpoint(min, max); + return m; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.java new file mode 100644 index 000000000..5aa29a625 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.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 com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.analysis.RealFieldUnivariateFunction; + +/** Interface for {@link UnivariateSolver (univariate real) root-finding + * algorithms} that maintain a bracketed solution. There are several advantages + * to having such root-finding algorithms: + *
    + *
  • The bracketed solution guarantees that the root is kept within the + * interval. As such, these algorithms generally also guarantee + * convergence.
  • + *
  • The bracketed solution means that we have the opportunity to only + * return roots that are greater than or equal to the actual root, or + * are less than or equal to the actual root. That is, we can control + * whether under-approximations and over-approximations are + * {@link AllowedSolution allowed solutions}. Other root-finding + * algorithms can usually only guarantee that the solution (the root that + * was found) is around the actual root.
  • + *
+ * + *

For backwards compatibility, all root-finding algorithms must have + * {@link AllowedSolution#ANY_SIDE ANY_SIDE} as default for the allowed + * solutions.

+ * + * @see AllowedSolution + * @param the type of the field elements + * @since 3.6 + */ +public interface BracketedRealFieldUnivariateSolver> { + + /** + * Get the maximum number of function evaluations. + * + * @return the maximum number of function evaluations. + */ + int getMaxEvaluations(); + + /** + * Get the number of evaluations of the objective function. + * The number of evaluations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + int getEvaluations(); + + /** + * Get the absolute accuracy of the solver. Solutions returned by the + * solver should be accurate to this tolerance, i.e., if ε is the + * absolute accuracy of the solver and {@code v} is a value returned by + * one of the {@code solve} methods, then a root of the function should + * exist somewhere in the interval ({@code v} - ε, {@code v} + ε). + * + * @return the absolute accuracy. + */ + T getAbsoluteAccuracy(); + + /** + * Get the relative accuracy of the solver. The contract for relative + * accuracy is the same as {@link #getAbsoluteAccuracy()}, but using + * relative, rather than absolute error. If ρ is the relative accuracy + * configured for a solver and {@code v} is a value returned, then a root + * of the function should exist somewhere in the interval + * ({@code v} - ρ {@code v}, {@code v} + ρ {@code v}). + * + * @return the relative accuracy. + */ + T getRelativeAccuracy(); + + /** + * Get the function value accuracy of the solver. If {@code v} is + * a value returned by the solver for a function {@code f}, + * then by contract, {@code |f(v)|} should be less than or equal to + * the function value accuracy configured for the solver. + * + * @return the function value accuracy. + */ + T getFunctionValueAccuracy(); + + /** + * Solve for a zero in the given interval. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return A value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + T solve(int maxEval, RealFieldUnivariateFunction f, T min, T max, + AllowedSolution allowedSolution); + + /** + * Solve for a zero in the given interval, start at {@code startValue}. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return A value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + T solve(int maxEval, RealFieldUnivariateFunction f, T min, T max, T startValue, + AllowedSolution allowedSolution); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.java new file mode 100644 index 000000000..d1c57660b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.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 com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** Interface for {@link UnivariateSolver (univariate real) root-finding + * algorithms} that maintain a bracketed solution. There are several advantages + * to having such root-finding algorithms: + *
    + *
  • The bracketed solution guarantees that the root is kept within the + * interval. As such, these algorithms generally also guarantee + * convergence.
  • + *
  • The bracketed solution means that we have the opportunity to only + * return roots that are greater than or equal to the actual root, or + * are less than or equal to the actual root. That is, we can control + * whether under-approximations and over-approximations are + * {@link AllowedSolution allowed solutions}. Other root-finding + * algorithms can usually only guarantee that the solution (the root that + * was found) is around the actual root.
  • + *
+ * + *

For backwards compatibility, all root-finding algorithms must have + * {@link AllowedSolution#ANY_SIDE ANY_SIDE} as default for the allowed + * solutions.

+ * @param Type of function to solve. + * + * @see AllowedSolution + * @since 3.0 + */ +public interface BracketedUnivariateSolver + extends BaseUnivariateSolver { + + /** + * Solve for a zero in the given interval. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return A value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + double solve(int maxEval, FUNC f, double min, double max, + AllowedSolution allowedSolution); + + /** + * Solve for a zero in the given interval, start at {@code startValue}. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return A value where the function is zero. + * @throws MathIllegalArgumentException + * if the arguments do not satisfy the requirements specified by the solver. + * @throws TooManyEvaluationsException if + * the allowed number of evaluations is exceeded. + */ + double solve(int maxEval, FUNC f, double min, double max, double startValue, + AllowedSolution allowedSolution); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java new file mode 100644 index 000000000..5717ec3bf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java @@ -0,0 +1,411 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class implements a modification of the Brent algorithm. + *

+ * The changes with respect to the original Brent algorithm are: + *

    + *
  • the returned value is chosen in the current interval according + * to user specified {@link AllowedSolution},
  • + *
  • the maximal order for the invert polynomial root search is + * user-specified instead of being invert quadratic only
  • + *

+ * The given interval must bracket the root.

+ * + */ +public class BracketingNthOrderBrentSolver + extends AbstractUnivariateSolver + implements BracketedUnivariateSolver { + + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** Default maximal order. */ + private static final int DEFAULT_MAXIMAL_ORDER = 5; + + /** Maximal aging triggering an attempt to balance the bracketing interval. */ + private static final int MAXIMAL_AGING = 2; + + /** Reduction factor for attempts to balance the bracketing interval. */ + private static final double REDUCTION_FACTOR = 1.0 / 16.0; + + /** Maximal order. */ + private final int maximalOrder; + + /** The kinds of solutions that the algorithm may accept. */ + private AllowedSolution allowed; + + /** + * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively) + */ + public BracketingNthOrderBrentSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER); + } + + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + * @param maximalOrder maximal order. + * @exception NumberIsTooSmallException if maximal order is lower than 2 + */ + public BracketingNthOrderBrentSolver(final double absoluteAccuracy, + final int maximalOrder) + throws NumberIsTooSmallException { + super(absoluteAccuracy); + if (maximalOrder < 2) { + throw new NumberIsTooSmallException(maximalOrder, 2, true); + } + this.maximalOrder = maximalOrder; + this.allowed = AllowedSolution.ANY_SIDE; + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param maximalOrder maximal order. + * @exception NumberIsTooSmallException if maximal order is lower than 2 + */ + public BracketingNthOrderBrentSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final int maximalOrder) + throws NumberIsTooSmallException { + super(relativeAccuracy, absoluteAccuracy); + if (maximalOrder < 2) { + throw new NumberIsTooSmallException(maximalOrder, 2, true); + } + this.maximalOrder = maximalOrder; + this.allowed = AllowedSolution.ANY_SIDE; + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Function value accuracy. + * @param maximalOrder maximal order. + * @exception NumberIsTooSmallException if maximal order is lower than 2 + */ + public BracketingNthOrderBrentSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy, + final int maximalOrder) + throws NumberIsTooSmallException { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + if (maximalOrder < 2) { + throw new NumberIsTooSmallException(maximalOrder, 2, true); + } + this.maximalOrder = maximalOrder; + this.allowed = AllowedSolution.ANY_SIDE; + } + + /** Get the maximal order. + * @return maximal order + */ + public int getMaximalOrder() { + return maximalOrder; + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + // prepare arrays with the first points + final double[] x = new double[maximalOrder + 1]; + final double[] y = new double[maximalOrder + 1]; + x[0] = getMin(); + x[1] = getStartValue(); + x[2] = getMax(); + verifySequence(x[0], x[1], x[2]); + + // evaluate initial guess + y[1] = computeObjectiveValue(x[1]); + if (Precision.equals(y[1], 0.0, 1)) { + // return the initial guess if it is a perfect root. + return x[1]; + } + + // evaluate first endpoint + y[0] = computeObjectiveValue(x[0]); + if (Precision.equals(y[0], 0.0, 1)) { + // return the first endpoint if it is a perfect root. + return x[0]; + } + + int nbPoints; + int signChangeIndex; + if (y[0] * y[1] < 0) { + + // reduce interval if it brackets the root + nbPoints = 2; + signChangeIndex = 1; + + } else { + + // evaluate second endpoint + y[2] = computeObjectiveValue(x[2]); + if (Precision.equals(y[2], 0.0, 1)) { + // return the second endpoint if it is a perfect root. + return x[2]; + } + + if (y[1] * y[2] < 0) { + // use all computed point as a start sampling array for solving + nbPoints = 3; + signChangeIndex = 2; + } else { + throw new NoBracketingException(x[0], x[2], y[0], y[2]); + } + + } + + // prepare a work array for inverse polynomial interpolation + final double[] tmpX = new double[x.length]; + + // current tightest bracketing of the root + double xA = x[signChangeIndex - 1]; + double yA = y[signChangeIndex - 1]; + double absYA = FastMath.abs(yA); + int agingA = 0; + double xB = x[signChangeIndex]; + double yB = y[signChangeIndex]; + double absYB = FastMath.abs(yB); + int agingB = 0; + + // search loop + while (true) { + + // check convergence of bracketing interval + final double xTol = getAbsoluteAccuracy() + + getRelativeAccuracy() * FastMath.max(FastMath.abs(xA), FastMath.abs(xB)); + if (((xB - xA) <= xTol) || (FastMath.max(absYA, absYB) < getFunctionValueAccuracy())) { + switch (allowed) { + case ANY_SIDE : + return absYA < absYB ? xA : xB; + case LEFT_SIDE : + return xA; + case RIGHT_SIDE : + return xB; + case BELOW_SIDE : + return (yA <= 0) ? xA : xB; + case ABOVE_SIDE : + return (yA < 0) ? xB : xA; + default : + // this should never happen + throw new MathInternalError(); + } + } + + // target for the next evaluation point + double targetY; + if (agingA >= MAXIMAL_AGING) { + // we keep updating the high bracket, try to compensate this + final int p = agingA - MAXIMAL_AGING; + final double weightA = (1 << p) - 1; + final double weightB = p + 1; + targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB); + } else if (agingB >= MAXIMAL_AGING) { + // we keep updating the low bracket, try to compensate this + final int p = agingB - MAXIMAL_AGING; + final double weightA = p + 1; + final double weightB = (1 << p) - 1; + targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB); + } else { + // bracketing is balanced, try to find the root itself + targetY = 0; + } + + // make a few attempts to guess a root, + double nextX; + int start = 0; + int end = nbPoints; + do { + + // guess a value for current target, using inverse polynomial interpolation + System.arraycopy(x, start, tmpX, start, end - start); + nextX = guessX(targetY, tmpX, y, start, end); + + if (!((nextX > xA) && (nextX < xB))) { + // the guessed root is not strictly inside of the tightest bracketing interval + + // the guessed root is either not strictly inside the interval or it + // is a NaN (which occurs when some sampling points share the same y) + // we try again with a lower interpolation order + if (signChangeIndex - start >= end - signChangeIndex) { + // we have more points before the sign change, drop the lowest point + ++start; + } else { + // we have more points after sign change, drop the highest point + --end; + } + + // we need to do one more attempt + nextX = Double.NaN; + + } + + } while (Double.isNaN(nextX) && (end - start > 1)); + + if (Double.isNaN(nextX)) { + // fall back to bisection + nextX = xA + 0.5 * (xB - xA); + start = signChangeIndex - 1; + end = signChangeIndex; + } + + // evaluate the function at the guessed root + final double nextY = computeObjectiveValue(nextX); + if (Precision.equals(nextY, 0.0, 1)) { + // we have found an exact root, since it is not an approximation + // we don't need to bother about the allowed solutions setting + return nextX; + } + + if ((nbPoints > 2) && (end - start != nbPoints)) { + + // we have been forced to ignore some points to keep bracketing, + // they are probably too far from the root, drop them from now on + nbPoints = end - start; + System.arraycopy(x, start, x, 0, nbPoints); + System.arraycopy(y, start, y, 0, nbPoints); + signChangeIndex -= start; + + } else if (nbPoints == x.length) { + + // we have to drop one point in order to insert the new one + nbPoints--; + + // keep the tightest bracketing interval as centered as possible + if (signChangeIndex >= (x.length + 1) / 2) { + // we drop the lowest point, we have to shift the arrays and the index + System.arraycopy(x, 1, x, 0, nbPoints); + System.arraycopy(y, 1, y, 0, nbPoints); + --signChangeIndex; + } + + } + + // insert the last computed point + //(by construction, we know it lies inside the tightest bracketing interval) + System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex); + x[signChangeIndex] = nextX; + System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex); + y[signChangeIndex] = nextY; + ++nbPoints; + + // update the bracketing interval + if (nextY * yA <= 0) { + // the sign change occurs before the inserted point + xB = nextX; + yB = nextY; + absYB = FastMath.abs(yB); + ++agingA; + agingB = 0; + } else { + // the sign change occurs after the inserted point + xA = nextX; + yA = nextY; + absYA = FastMath.abs(yA); + agingA = 0; + ++agingB; + + // update the sign change index + signChangeIndex++; + + } + + } + + } + + /** Guess an x value by nth order inverse polynomial interpolation. + *

+ * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q + * is built such that for all considered points (xi, yi), + * Q(yi) = xi. + *

+ * @param targetY target value for y + * @param x reference points abscissas for interpolation, + * note that this array is modified during computation + * @param y reference points ordinates for interpolation + * @param start start index of the points to consider (inclusive) + * @param end end index of the points to consider (exclusive) + * @return guessed root (will be a NaN if two points share the same y) + */ + private double guessX(final double targetY, final double[] x, final double[] y, + final int start, final int end) { + + // compute Q Newton coefficients by divided differences + for (int i = start; i < end - 1; ++i) { + final int delta = i + 1 - start; + for (int j = end - 1; j > i; --j) { + x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]); + } + } + + // evaluate Q(targetY) + double x0 = 0; + for (int j = end - 1; j >= start; --j) { + x0 = x[j] + x0 * (targetY - y[j]); + } + + return x0; + + } + + /** {@inheritDoc} */ + public double solve(int maxEval, UnivariateFunction f, double min, + double max, AllowedSolution allowedSolution) + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + this.allowed = allowedSolution; + return super.solve(maxEval, f, min, max); + } + + /** {@inheritDoc} */ + public double solve(int maxEval, UnivariateFunction f, double min, + double max, double startValue, + AllowedSolution allowedSolution) + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + this.allowed = allowedSolution; + return super.solve(maxEval, f, min, max, startValue); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BrentSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BrentSolver.java new file mode 100644 index 000000000..2bde2f5cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/BrentSolver.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class implements the + * Brent algorithm for finding zeros of real univariate functions. + * The function should be continuous but not necessarily smooth. + * The {@code solve} method returns a zero {@code x} of the function {@code f} + * in the given interval {@code [a, b]} to within a tolerance + * {@code 2 eps abs(x) + t} where {@code eps} is the relative accuracy and + * {@code t} is the absolute accuracy. + *

The given interval must bracket the root.

+ *

+ * The reference implementation is given in chapter 4 of + *

+ * Algorithms for Minimization Without Derivatives, + * Richard P. Brent, + * Dover, 2002 + *
+ * + * @see BaseAbstractUnivariateSolver + */ +public class BrentSolver extends AbstractUnivariateSolver { + + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver with default absolute accuracy (1e-6). + */ + public BrentSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public BrentSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public BrentSolver(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Function value accuracy. + * + * @see BaseAbstractUnivariateSolver#BaseAbstractUnivariateSolver(double,double,double) + */ + public BrentSolver(double relativeAccuracy, + double absoluteAccuracy, + double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws NoBracketingException, + TooManyEvaluationsException, + NumberIsTooLargeException { + double min = getMin(); + double max = getMax(); + final double initial = getStartValue(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + + verifySequence(min, initial, max); + + // Return the initial guess if it is good enough. + double yInitial = computeObjectiveValue(initial); + if (FastMath.abs(yInitial) <= functionValueAccuracy) { + return initial; + } + + // Return the first endpoint if it is good enough. + double yMin = computeObjectiveValue(min); + if (FastMath.abs(yMin) <= functionValueAccuracy) { + return min; + } + + // Reduce interval if min and initial bracket the root. + if (yInitial * yMin < 0) { + return brent(min, initial, yMin, yInitial); + } + + // Return the second endpoint if it is good enough. + double yMax = computeObjectiveValue(max); + if (FastMath.abs(yMax) <= functionValueAccuracy) { + return max; + } + + // Reduce interval if initial and max bracket the root. + if (yInitial * yMax < 0) { + return brent(initial, max, yInitial, yMax); + } + + throw new NoBracketingException(min, max, yMin, yMax); + } + + /** + * Search for a zero inside the provided interval. + * This implementation is based on the algorithm described at page 58 of + * the book + *
+ * Algorithms for Minimization Without Derivatives, + * Richard P. Brent, + * Dover 0-486-41998-3 + *
+ * + * @param lo Lower bound of the search interval. + * @param hi Higher bound of the search interval. + * @param fLo Function value at the lower bound of the search interval. + * @param fHi Function value at the higher bound of the search interval. + * @return the value where the function is zero. + */ + private double brent(double lo, double hi, + double fLo, double fHi) { + double a = lo; + double fa = fLo; + double b = hi; + double fb = fHi; + double c = a; + double fc = fa; + double d = b - a; + double e = d; + + final double t = getAbsoluteAccuracy(); + final double eps = getRelativeAccuracy(); + + while (true) { + if (FastMath.abs(fc) < FastMath.abs(fb)) { + a = b; + b = c; + c = a; + fa = fb; + fb = fc; + fc = fa; + } + + final double tol = 2 * eps * FastMath.abs(b) + t; + final double m = 0.5 * (c - b); + + if (FastMath.abs(m) <= tol || + Precision.equals(fb, 0)) { + return b; + } + if (FastMath.abs(e) < tol || + FastMath.abs(fa) <= FastMath.abs(fb)) { + // Force bisection. + d = m; + e = d; + } else { + double s = fb / fa; + double p; + double q; + // The equality test (a == c) is intentional, + // it is part of the original Brent's method and + // it should NOT be replaced by proximity test. + if (a == c) { + // Linear interpolation. + p = 2 * m * s; + q = 1 - s; + } else { + // Inverse quadratic interpolation. + q = fa / fc; + final double r = fb / fc; + p = s * (2 * m * q * (q - r) - (b - a) * (r - 1)); + q = (q - 1) * (r - 1) * (s - 1); + } + if (p > 0) { + q = -q; + } else { + p = -p; + } + s = e; + e = d; + if (p >= 1.5 * m * q - FastMath.abs(tol * q) || + p >= FastMath.abs(0.5 * s * q)) { + // Inverse quadratic interpolation gives a value + // in the wrong direction, or progress is slow. + // Fall back to bisection. + d = m; + e = d; + } else { + d = p / q; + } + } + a = b; + fa = fb; + + if (FastMath.abs(d) > tol) { + b += d; + } else if (m > 0) { + b += tol; + } else { + b -= tol; + } + fb = computeObjectiveValue(b); + if ((fb > 0 && fc > 0) || + (fb <= 0 && fc <= 0)) { + c = a; + fc = fa; + d = b - a; + e = d; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java new file mode 100644 index 000000000..bd93a1d2b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; + + +/** + * Interface for (univariate real) rootfinding algorithms. + * Implementations will search for only one zero in the given interval. + * + * @deprecated as of 3.1, replaced by {@link UnivariateDifferentiableSolver} + */ +@Deprecated +public interface DifferentiableUnivariateSolver + extends BaseUnivariateSolver {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java new file mode 100644 index 000000000..1eb55869d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java @@ -0,0 +1,446 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.analysis.RealFieldUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class implements a modification of the Brent algorithm. + *

+ * The changes with respect to the original Brent algorithm are: + *

    + *
  • the returned value is chosen in the current interval according + * to user specified {@link AllowedSolution}
  • + *
  • the maximal order for the invert polynomial root search is + * user-specified instead of being invert quadratic only
  • + *

+ * The given interval must bracket the root.

+ * + * @param the type of the field elements + * @since 3.6 + */ +public class FieldBracketingNthOrderBrentSolver> + implements BracketedRealFieldUnivariateSolver { + + /** Maximal aging triggering an attempt to balance the bracketing interval. */ + private static final int MAXIMAL_AGING = 2; + + /** Field to which the elements belong. */ + private final Field field; + + /** Maximal order. */ + private final int maximalOrder; + + /** Function value accuracy. */ + private final T functionValueAccuracy; + + /** Absolute accuracy. */ + private final T absoluteAccuracy; + + /** Relative accuracy. */ + private final T relativeAccuracy; + + /** Evaluations counter. */ + private IntegerSequence.Incrementor evaluations; + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Function value accuracy. + * @param maximalOrder maximal order. + * @exception NumberIsTooSmallException if maximal order is lower than 2 + */ + public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy, + final T absoluteAccuracy, + final T functionValueAccuracy, + final int maximalOrder) + throws NumberIsTooSmallException { + if (maximalOrder < 2) { + throw new NumberIsTooSmallException(maximalOrder, 2, true); + } + this.field = relativeAccuracy.getField(); + this.maximalOrder = maximalOrder; + this.absoluteAccuracy = absoluteAccuracy; + this.relativeAccuracy = relativeAccuracy; + this.functionValueAccuracy = functionValueAccuracy; + this.evaluations = IntegerSequence.Incrementor.create(); + } + + /** Get the maximal order. + * @return maximal order + */ + public int getMaximalOrder() { + return maximalOrder; + } + + /** + * Get the maximal number of function evaluations. + * + * @return the maximal number of function evaluations. + */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** + * Get the number of evaluations of the objective function. + * The number of evaluations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** + * Get the absolute accuracy. + * @return absolute accuracy + */ + public T getAbsoluteAccuracy() { + return absoluteAccuracy; + } + + /** + * Get the relative accuracy. + * @return relative accuracy + */ + public T getRelativeAccuracy() { + return relativeAccuracy; + } + + /** + * Get the function accuracy. + * @return function accuracy + */ + public T getFunctionValueAccuracy() { + return functionValueAccuracy; + } + + /** + * Solve for a zero in the given interval. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return a value where the function is zero. + * @exception NullArgumentException if f is null. + * @exception NoBracketingException if root cannot be bracketed + */ + public T solve(final int maxEval, final RealFieldUnivariateFunction f, + final T min, final T max, final AllowedSolution allowedSolution) + throws NullArgumentException, NoBracketingException { + return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution); + } + + /** + * Solve for a zero in the given interval, start at {@code startValue}. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return a value where the function is zero. + * @exception NullArgumentException if f is null. + * @exception NoBracketingException if root cannot be bracketed + */ + public T solve(final int maxEval, final RealFieldUnivariateFunction f, + final T min, final T max, final T startValue, + final AllowedSolution allowedSolution) + throws NullArgumentException, NoBracketingException { + + // Checks. + MathUtils.checkNotNull(f); + + // Reset. + evaluations = evaluations.withMaximalCount(maxEval).withStart(0); + T zero = field.getZero(); + T nan = zero.add(Double.NaN); + + // prepare arrays with the first points + final T[] x = MathArrays.buildArray(field, maximalOrder + 1); + final T[] y = MathArrays.buildArray(field, maximalOrder + 1); + x[0] = min; + x[1] = startValue; + x[2] = max; + + // evaluate initial guess + evaluations.increment(); + y[1] = f.value(x[1]); + if (Precision.equals(y[1].getReal(), 0.0, 1)) { + // return the initial guess if it is a perfect root. + return x[1]; + } + + // evaluate first endpoint + evaluations.increment(); + y[0] = f.value(x[0]); + if (Precision.equals(y[0].getReal(), 0.0, 1)) { + // return the first endpoint if it is a perfect root. + return x[0]; + } + + int nbPoints; + int signChangeIndex; + if (y[0].multiply(y[1]).getReal() < 0) { + + // reduce interval if it brackets the root + nbPoints = 2; + signChangeIndex = 1; + + } else { + + // evaluate second endpoint + evaluations.increment(); + y[2] = f.value(x[2]); + if (Precision.equals(y[2].getReal(), 0.0, 1)) { + // return the second endpoint if it is a perfect root. + return x[2]; + } + + if (y[1].multiply(y[2]).getReal() < 0) { + // use all computed point as a start sampling array for solving + nbPoints = 3; + signChangeIndex = 2; + } else { + throw new NoBracketingException(x[0].getReal(), x[2].getReal(), + y[0].getReal(), y[2].getReal()); + } + + } + + // prepare a work array for inverse polynomial interpolation + final T[] tmpX = MathArrays.buildArray(field, x.length); + + // current tightest bracketing of the root + T xA = x[signChangeIndex - 1]; + T yA = y[signChangeIndex - 1]; + T absXA = xA.abs(); + T absYA = yA.abs(); + int agingA = 0; + T xB = x[signChangeIndex]; + T yB = y[signChangeIndex]; + T absXB = xB.abs(); + T absYB = yB.abs(); + int agingB = 0; + + // search loop + while (true) { + + // check convergence of bracketing interval + T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA; + T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA; + final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX)); + if (xB.subtract(xA).subtract(xTol).getReal() <= 0 || + maxY.subtract(functionValueAccuracy).getReal() < 0) { + switch (allowedSolution) { + case ANY_SIDE : + return absYA.subtract(absYB).getReal() < 0 ? xA : xB; + case LEFT_SIDE : + return xA; + case RIGHT_SIDE : + return xB; + case BELOW_SIDE : + return yA.getReal() <= 0 ? xA : xB; + case ABOVE_SIDE : + return yA.getReal() < 0 ? xB : xA; + default : + // this should never happen + throw new MathInternalError(null); + } + } + + // target for the next evaluation point + T targetY; + if (agingA >= MAXIMAL_AGING) { + // we keep updating the high bracket, try to compensate this + targetY = yB.divide(16).negate(); + } else if (agingB >= MAXIMAL_AGING) { + // we keep updating the low bracket, try to compensate this + targetY = yA.divide(16).negate(); + } else { + // bracketing is balanced, try to find the root itself + targetY = zero; + } + + // make a few attempts to guess a root, + T nextX; + int start = 0; + int end = nbPoints; + do { + + // guess a value for current target, using inverse polynomial interpolation + System.arraycopy(x, start, tmpX, start, end - start); + nextX = guessX(targetY, tmpX, y, start, end); + + if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) { + // the guessed root is not strictly inside of the tightest bracketing interval + + // the guessed root is either not strictly inside the interval or it + // is a NaN (which occurs when some sampling points share the same y) + // we try again with a lower interpolation order + if (signChangeIndex - start >= end - signChangeIndex) { + // we have more points before the sign change, drop the lowest point + ++start; + } else { + // we have more points after sign change, drop the highest point + --end; + } + + // we need to do one more attempt + nextX = nan; + + } + + } while (Double.isNaN(nextX.getReal()) && (end - start > 1)); + + if (Double.isNaN(nextX.getReal())) { + // fall back to bisection + nextX = xA.add(xB.subtract(xA).divide(2)); + start = signChangeIndex - 1; + end = signChangeIndex; + } + + // evaluate the function at the guessed root + evaluations.increment(); + final T nextY = f.value(nextX); + if (Precision.equals(nextY.getReal(), 0.0, 1)) { + // we have found an exact root, since it is not an approximation + // we don't need to bother about the allowed solutions setting + return nextX; + } + + if ((nbPoints > 2) && (end - start != nbPoints)) { + + // we have been forced to ignore some points to keep bracketing, + // they are probably too far from the root, drop them from now on + nbPoints = end - start; + System.arraycopy(x, start, x, 0, nbPoints); + System.arraycopy(y, start, y, 0, nbPoints); + signChangeIndex -= start; + + } else if (nbPoints == x.length) { + + // we have to drop one point in order to insert the new one + nbPoints--; + + // keep the tightest bracketing interval as centered as possible + if (signChangeIndex >= (x.length + 1) / 2) { + // we drop the lowest point, we have to shift the arrays and the index + System.arraycopy(x, 1, x, 0, nbPoints); + System.arraycopy(y, 1, y, 0, nbPoints); + --signChangeIndex; + } + + } + + // insert the last computed point + //(by construction, we know it lies inside the tightest bracketing interval) + System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex); + x[signChangeIndex] = nextX; + System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex); + y[signChangeIndex] = nextY; + ++nbPoints; + + // update the bracketing interval + if (nextY.multiply(yA).getReal() <= 0) { + // the sign change occurs before the inserted point + xB = nextX; + yB = nextY; + absYB = yB.abs(); + ++agingA; + agingB = 0; + } else { + // the sign change occurs after the inserted point + xA = nextX; + yA = nextY; + absYA = yA.abs(); + agingA = 0; + ++agingB; + + // update the sign change index + signChangeIndex++; + + } + + } + + } + + /** Guess an x value by nth order inverse polynomial interpolation. + *

+ * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q + * is built such that for all considered points (xi, yi), + * Q(yi) = xi. + *

+ * @param targetY target value for y + * @param x reference points abscissas for interpolation, + * note that this array is modified during computation + * @param y reference points ordinates for interpolation + * @param start start index of the points to consider (inclusive) + * @param end end index of the points to consider (exclusive) + * @return guessed root (will be a NaN if two points share the same y) + */ + private T guessX(final T targetY, final T[] x, final T[] y, + final int start, final int end) { + + // compute Q Newton coefficients by divided differences + for (int i = start; i < end - 1; ++i) { + final int delta = i + 1 - start; + for (int j = end - 1; j > i; --j) { + x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta])); + } + } + + // evaluate Q(targetY) + T x0 = field.getZero(); + for (int j = end - 1; j >= start; --j) { + x0 = x[j].add(x0.multiply(targetY.subtract(y[j]))); + } + + return x0; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java new file mode 100644 index 000000000..5b7a4327b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + + +/** + * Implements the Illinois method for root-finding (approximating + * a zero of a univariate real function). It is a modified + * {@link RegulaFalsiSolver Regula Falsi} method. + * + *

Like the Regula Falsi method, convergence is guaranteed by + * maintaining a bracketed solution. The Illinois method however, + * should converge much faster than the original Regula Falsi + * method. Furthermore, this implementation of the Illinois method + * should not suffer from the same implementation issues as the Regula + * Falsi method, which may fail to convergence in certain cases.

+ * + *

The Illinois method assumes that the function is continuous, + * but not necessarily smooth.

+ * + *

Implementation based on the following article: M. Dowell and P. Jarratt, + * A modified regula falsi method for computing the root of an + * equation, BIT Numerical Mathematics, volume 11, number 2, + * pages 168-174, Springer, 1971.

+ * + * @since 3.0 + */ +public class IllinoisSolver extends BaseSecantSolver { + + /** Construct a solver with default accuracy (1e-6). */ + public IllinoisSolver() { + super(DEFAULT_ABSOLUTE_ACCURACY, Method.ILLINOIS); + } + + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public IllinoisSolver(final double absoluteAccuracy) { + super(absoluteAccuracy, Method.ILLINOIS); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public IllinoisSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy, Method.ILLINOIS); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Maximum function value error. + */ + public IllinoisSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.PEGASUS); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java new file mode 100644 index 000000000..120c6491e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.complex.ComplexUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Laguerre's Method for root finding of real coefficient polynomials. + * For reference, see + *
+ * A First Course in Numerical Analysis, + * ISBN 048641454X, chapter 8. + *
+ * Laguerre's method is global in the sense that it can start with any initial + * approximation and be able to solve all roots from that point. + * The algorithm requires a bracketing condition. + * + * @since 1.2 + */ +public class LaguerreSolver extends AbstractPolynomialSolver { + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + /** Complex solver. */ + private final ComplexSolver complexSolver = new ComplexSolver(); + + /** + * Construct a solver with default accuracy (1e-6). + */ + public LaguerreSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public LaguerreSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public LaguerreSolver(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Function value accuracy. + */ + public LaguerreSolver(double relativeAccuracy, + double absoluteAccuracy, + double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + public double doSolve() + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + final double min = getMin(); + final double max = getMax(); + final double initial = getStartValue(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + + verifySequence(min, initial, max); + + // Return the initial guess if it is good enough. + final double yInitial = computeObjectiveValue(initial); + if (FastMath.abs(yInitial) <= functionValueAccuracy) { + return initial; + } + + // Return the first endpoint if it is good enough. + final double yMin = computeObjectiveValue(min); + if (FastMath.abs(yMin) <= functionValueAccuracy) { + return min; + } + + // Reduce interval if min and initial bracket the root. + if (yInitial * yMin < 0) { + return laguerre(min, initial, yMin, yInitial); + } + + // Return the second endpoint if it is good enough. + final double yMax = computeObjectiveValue(max); + if (FastMath.abs(yMax) <= functionValueAccuracy) { + return max; + } + + // Reduce interval if initial and max bracket the root. + if (yInitial * yMax < 0) { + return laguerre(initial, max, yInitial, yMax); + } + + throw new NoBracketingException(min, max, yMin, yMax); + } + + /** + * Find a real root in the given interval. + * + * Despite the bracketing condition, the root returned by + * {@link LaguerreSolver.ComplexSolver#solve(Complex[],Complex)} may + * not be a real zero inside {@code [min, max]}. + * For example, p(x) = x3 + 1, + * with {@code min = -2}, {@code max = 2}, {@code initial = 0}. + * When it occurs, this code calls + * {@link LaguerreSolver.ComplexSolver#solveAll(Complex[],Complex)} + * in order to obtain all roots and picks up one real root. + * + * @param lo Lower bound of the search interval. + * @param hi Higher bound of the search interval. + * @param fLo Function value at the lower bound of the search interval. + * @param fHi Function value at the higher bound of the search interval. + * @return the point at which the function value is zero. + * @deprecated This method should not be part of the public API: It will + * be made private in version 4.0. + */ + @Deprecated + public double laguerre(double lo, double hi, + double fLo, double fHi) { + final Complex c[] = ComplexUtils.convertToComplex(getCoefficients()); + + final Complex initial = new Complex(0.5 * (lo + hi), 0); + final Complex z = complexSolver.solve(c, initial); + if (complexSolver.isRoot(lo, hi, z)) { + return z.getReal(); + } else { + double r = Double.NaN; + // Solve all roots and select the one we are seeking. + Complex[] root = complexSolver.solveAll(c, initial); + for (int i = 0; i < root.length; i++) { + if (complexSolver.isRoot(lo, hi, root[i])) { + r = root[i].getReal(); + break; + } + } + return r; + } + } + + /** + * Find all complex roots for the polynomial with the given + * coefficients, starting from the given initial value. + *

+ * Note: This method is not part of the API of {@link BaseUnivariateSolver}.

+ * + * @param coefficients Polynomial coefficients. + * @param initial Start value. + * @return the full set of complex roots of the polynomial + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded when solving for one of the roots + * @throws NullArgumentException if the {@code coefficients} is + * {@code null}. + * @throws NoDataException if the {@code coefficients} array is empty. + * @since 3.1 + */ + public Complex[] solveAllComplex(double[] coefficients, + double initial) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + return solveAllComplex(coefficients, initial, Integer.MAX_VALUE); + } + + /** + * Find all complex roots for the polynomial with the given + * coefficients, starting from the given initial value. + *

+ * Note: This method is not part of the API of {@link BaseUnivariateSolver}.

+ * + * @param coefficients polynomial coefficients + * @param initial start value + * @param maxEval maximum number of evaluations + * @return the full set of complex roots of the polynomial + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded when solving for one of the roots + * @throws NullArgumentException if the {@code coefficients} is + * {@code null} + * @throws NoDataException if the {@code coefficients} array is empty + * @since 3.5 + */ + public Complex[] solveAllComplex(double[] coefficients, + double initial, int maxEval) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + setup(maxEval, + new PolynomialFunction(coefficients), + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + initial); + return complexSolver.solveAll(ComplexUtils.convertToComplex(coefficients), + new Complex(initial, 0d)); + } + + /** + * Find a complex root for the polynomial with the given coefficients, + * starting from the given initial value. + *

+ * Note: This method is not part of the API of {@link BaseUnivariateSolver}.

+ * + * @param coefficients Polynomial coefficients. + * @param initial Start value. + * @return a complex root of the polynomial + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded. + * @throws NullArgumentException if the {@code coefficients} is + * {@code null}. + * @throws NoDataException if the {@code coefficients} array is empty. + * @since 3.1 + */ + public Complex solveComplex(double[] coefficients, + double initial) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + return solveComplex(coefficients, initial, Integer.MAX_VALUE); + } + + /** + * Find a complex root for the polynomial with the given coefficients, + * starting from the given initial value. + *

+ * Note: This method is not part of the API of {@link BaseUnivariateSolver}.

+ * + * @param coefficients polynomial coefficients + * @param initial start value + * @param maxEval maximum number of evaluations + * @return a complex root of the polynomial + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded + * @throws NullArgumentException if the {@code coefficients} is + * {@code null} + * @throws NoDataException if the {@code coefficients} array is empty + * @since 3.1 + */ + public Complex solveComplex(double[] coefficients, + double initial, int maxEval) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + setup(maxEval, + new PolynomialFunction(coefficients), + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + initial); + return complexSolver.solve(ComplexUtils.convertToComplex(coefficients), + new Complex(initial, 0d)); + } + + /** + * Class for searching all (complex) roots. + */ + private class ComplexSolver { + /** + * Check whether the given complex root is actually a real zero + * in the given interval, within the solver tolerance level. + * + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param z Complex root. + * @return {@code true} if z is a real zero. + */ + public boolean isRoot(double min, double max, Complex z) { + if (isSequence(min, z.getReal(), max)) { + double tolerance = FastMath.max(getRelativeAccuracy() * z.abs(), getAbsoluteAccuracy()); + return (FastMath.abs(z.getImaginary()) <= tolerance) || + (z.abs() <= getFunctionValueAccuracy()); + } + return false; + } + + /** + * Find all complex roots for the polynomial with the given + * coefficients, starting from the given initial value. + * + * @param coefficients Polynomial coefficients. + * @param initial Start value. + * @return the point at which the function value is zero. + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded. + * @throws NullArgumentException if the {@code coefficients} is + * {@code null}. + * @throws NoDataException if the {@code coefficients} array is empty. + */ + public Complex[] solveAll(Complex coefficients[], Complex initial) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + if (coefficients == null) { + throw new NullArgumentException(); + } + final int n = coefficients.length - 1; + if (n == 0) { + throw new NoDataException(LocalizedFormats.POLYNOMIAL); + } + // Coefficients for deflated polynomial. + final Complex c[] = new Complex[n + 1]; + for (int i = 0; i <= n; i++) { + c[i] = coefficients[i]; + } + + // Solve individual roots successively. + final Complex root[] = new Complex[n]; + for (int i = 0; i < n; i++) { + final Complex subarray[] = new Complex[n - i + 1]; + System.arraycopy(c, 0, subarray, 0, subarray.length); + root[i] = solve(subarray, initial); + // Polynomial deflation using synthetic division. + Complex newc = c[n - i]; + Complex oldc = null; + for (int j = n - i - 1; j >= 0; j--) { + oldc = c[j]; + c[j] = newc; + newc = oldc.add(newc.multiply(root[i])); + } + } + + return root; + } + + /** + * Find a complex root for the polynomial with the given coefficients, + * starting from the given initial value. + * + * @param coefficients Polynomial coefficients. + * @param initial Start value. + * @return the point at which the function value is zero. + * @throws TooManyEvaluationsException + * if the maximum number of evaluations is exceeded. + * @throws NullArgumentException if the {@code coefficients} is + * {@code null}. + * @throws NoDataException if the {@code coefficients} array is empty. + */ + public Complex solve(Complex coefficients[], Complex initial) + throws NullArgumentException, + NoDataException, + TooManyEvaluationsException { + if (coefficients == null) { + throw new NullArgumentException(); + } + + final int n = coefficients.length - 1; + if (n == 0) { + throw new NoDataException(LocalizedFormats.POLYNOMIAL); + } + + final double absoluteAccuracy = getAbsoluteAccuracy(); + final double relativeAccuracy = getRelativeAccuracy(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + + final Complex nC = new Complex(n, 0); + final Complex n1C = new Complex(n - 1, 0); + + Complex z = initial; + Complex oldz = new Complex(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY); + while (true) { + // Compute pv (polynomial value), dv (derivative value), and + // d2v (second derivative value) simultaneously. + Complex pv = coefficients[n]; + Complex dv = Complex.ZERO; + Complex d2v = Complex.ZERO; + for (int j = n-1; j >= 0; j--) { + d2v = dv.add(z.multiply(d2v)); + dv = pv.add(z.multiply(dv)); + pv = coefficients[j].add(z.multiply(pv)); + } + d2v = d2v.multiply(new Complex(2.0, 0.0)); + + // Check for convergence. + final double tolerance = FastMath.max(relativeAccuracy * z.abs(), + absoluteAccuracy); + if ((z.subtract(oldz)).abs() <= tolerance) { + return z; + } + if (pv.abs() <= functionValueAccuracy) { + return z; + } + + // Now pv != 0, calculate the new approximation. + final Complex G = dv.divide(pv); + final Complex G2 = G.multiply(G); + final Complex H = G2.subtract(d2v.divide(pv)); + final Complex delta = n1C.multiply((nC.multiply(H)).subtract(G2)); + // Choose a denominator larger in magnitude. + final Complex deltaSqrt = delta.sqrt(); + final Complex dplus = G.add(deltaSqrt); + final Complex dminus = G.subtract(deltaSqrt); + final Complex denominator = dplus.abs() > dminus.abs() ? dplus : dminus; + // Perturb z if denominator is zero, for instance, + // p(x) = x^3 + 1, z = 0. + if (denominator.equals(new Complex(0.0, 0.0))) { + z = z.add(new Complex(absoluteAccuracy, absoluteAccuracy)); + oldz = new Complex(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY); + } else { + oldz = z; + z = z.subtract(nC.divide(denominator)); + } + incrementEvaluationCount(); + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver.java new file mode 100644 index 000000000..bd893f2ba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the + * Muller's Method for root finding of real univariate functions. For + * reference, see Elementary Numerical Analysis, ISBN 0070124477, + * chapter 3. + *

+ * Muller's method applies to both real and complex functions, but here we + * restrict ourselves to real functions. + * This class differs from {@link MullerSolver} in the way it avoids complex + * operations.

+ * Muller's original method would have function evaluation at complex point. + * Since our f(x) is real, we have to find ways to avoid that. Bracketing + * condition is one way to go: by requiring bracketing in every iteration, + * the newly computed approximation is guaranteed to be real.

+ *

+ * 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.

+ *

+ * The formulas here use divided differences directly.

+ * + * @since 1.2 + * @see MullerSolver2 + */ +public class MullerSolver extends AbstractUnivariateSolver { + + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver with default accuracy (1e-6). + */ + public MullerSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public MullerSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public MullerSolver(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + final double min = getMin(); + final double max = getMax(); + final double initial = getStartValue(); + + final double functionValueAccuracy = getFunctionValueAccuracy(); + + verifySequence(min, initial, max); + + // check for zeros before verifying bracketing + final double fMin = computeObjectiveValue(min); + if (FastMath.abs(fMin) < functionValueAccuracy) { + return min; + } + final double fMax = computeObjectiveValue(max); + if (FastMath.abs(fMax) < functionValueAccuracy) { + return max; + } + final double fInitial = computeObjectiveValue(initial); + if (FastMath.abs(fInitial) < functionValueAccuracy) { + return initial; + } + + verifyBracketing(min, max); + + if (isBracketing(min, initial)) { + return solve(min, initial, fMin, fInitial); + } else { + return solve(initial, max, fInitial, fMax); + } + } + + /** + * Find a real root in the given interval. + * + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param fMin function value at the lower bound. + * @param fMax function value at the upper bound. + * @return the point at which the function value is zero. + * @throws TooManyEvaluationsException if the allowed number of calls to + * the function to be solved has been exhausted. + */ + private double solve(double min, double max, + double fMin, double fMax) + throws TooManyEvaluationsException { + final double relativeAccuracy = getRelativeAccuracy(); + final double absoluteAccuracy = getAbsoluteAccuracy(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + + // [x0, x2] is the bracketing interval in each iteration + // x1 is the last approximation and an interpolation point in (x0, x2) + // x is the new root approximation and new x1 for next round + // d01, d12, d012 are divided differences + + double x0 = min; + double y0 = fMin; + double x2 = max; + double y2 = fMax; + double x1 = 0.5 * (x0 + x2); + double y1 = computeObjectiveValue(x1); + + double oldx = Double.POSITIVE_INFINITY; + while (true) { + // Muller's method employs quadratic interpolation through + // x0, x1, x2 and x is the zero of the interpolating parabola. + // Due to bracketing condition, this parabola must have two + // real roots and we choose one in [x0, x2] to be x. + final double d01 = (y1 - y0) / (x1 - x0); + final double d12 = (y2 - y1) / (x2 - x1); + final double d012 = (d12 - d01) / (x2 - x0); + final double c1 = d01 + (x1 - x0) * d012; + final double delta = c1 * c1 - 4 * y1 * d012; + final double xplus = x1 + (-2.0 * y1) / (c1 + FastMath.sqrt(delta)); + final double xminus = x1 + (-2.0 * y1) / (c1 - FastMath.sqrt(delta)); + // xplus and xminus are two roots of parabola and at least + // one of them should lie in (x0, x2) + final double x = isSequence(x0, xplus, x2) ? xplus : xminus; + final double y = computeObjectiveValue(x); + + // check for convergence + final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy); + if (FastMath.abs(x - oldx) <= tolerance || + FastMath.abs(y) <= functionValueAccuracy) { + return x; + } + + // Bisect if convergence is too slow. Bisection would waste + // our calculation of x, hopefully it won't happen often. + // the real number equality test x == x1 is intentional and + // completes the proximity tests above it + boolean bisect = (x < x1 && (x1 - x0) > 0.95 * (x2 - x0)) || + (x > x1 && (x2 - x1) > 0.95 * (x2 - x0)) || + (x == x1); + // prepare the new bracketing interval for next iteration + if (!bisect) { + x0 = x < x1 ? x0 : x1; + y0 = x < x1 ? y0 : y1; + x2 = x > x1 ? x2 : x1; + y2 = x > x1 ? y2 : y1; + x1 = x; y1 = y; + oldx = x; + } else { + double xm = 0.5 * (x0 + x2); + double ym = computeObjectiveValue(xm); + if (FastMath.signum(y0) + FastMath.signum(ym) == 0.0) { + x2 = xm; y2 = ym; + } else { + x0 = xm; y0 = ym; + } + x1 = 0.5 * (x0 + x2); + y1 = computeObjectiveValue(x1); + oldx = Double.POSITIVE_INFINITY; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver2.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver2.java new file mode 100644 index 000000000..c2771f6f5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/MullerSolver2.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the + * Muller's Method for root finding of real univariate functions. For + * reference, see Elementary Numerical Analysis, ISBN 0070124477, + * chapter 3. + *

+ * Muller's method applies to both real and complex functions, but here we + * restrict ourselves to real functions. + * This class differs from {@link MullerSolver} in the way it avoids complex + * operations.

+ * Except for the initial [min, max], it does not require bracketing + * condition, e.g. f(x0), f(x1), f(x2) can have the same sign. If a complex + * number arises in the computation, we simply use its modulus as a real + * approximation.

+ *

+ * Because the interval may not be bracketing, the bisection alternative is + * not applicable here. However in practice our treatment usually works + * well, especially near real zeroes where the imaginary part of the complex + * approximation is often negligible.

+ *

+ * The formulas here do not use divided differences directly.

+ * + * @since 1.2 + * @see MullerSolver + */ +public class MullerSolver2 extends AbstractUnivariateSolver { + + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver with default accuracy (1e-6). + */ + public MullerSolver2() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public MullerSolver2(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public MullerSolver2(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException, + NumberIsTooLargeException, + NoBracketingException { + final double min = getMin(); + final double max = getMax(); + + verifyInterval(min, max); + + final double relativeAccuracy = getRelativeAccuracy(); + final double absoluteAccuracy = getAbsoluteAccuracy(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + + // x2 is the last root approximation + // x is the new approximation and new x2 for next round + // x0 < x1 < x2 does not hold here + + double x0 = min; + double y0 = computeObjectiveValue(x0); + if (FastMath.abs(y0) < functionValueAccuracy) { + return x0; + } + double x1 = max; + double y1 = computeObjectiveValue(x1); + if (FastMath.abs(y1) < functionValueAccuracy) { + return x1; + } + + if(y0 * y1 > 0) { + throw new NoBracketingException(x0, x1, y0, y1); + } + + double x2 = 0.5 * (x0 + x1); + double y2 = computeObjectiveValue(x2); + + double oldx = Double.POSITIVE_INFINITY; + while (true) { + // quadratic interpolation through x0, x1, x2 + final double q = (x2 - x1) / (x1 - x0); + final double a = q * (y2 - (1 + q) * y1 + q * y0); + final double b = (2 * q + 1) * y2 - (1 + q) * (1 + q) * y1 + q * q * y0; + final double c = (1 + q) * y2; + final double delta = b * b - 4 * a * c; + double x; + final double denominator; + if (delta >= 0.0) { + // choose a denominator larger in magnitude + double dplus = b + FastMath.sqrt(delta); + double dminus = b - FastMath.sqrt(delta); + denominator = FastMath.abs(dplus) > FastMath.abs(dminus) ? dplus : dminus; + } else { + // take the modulus of (B +/- FastMath.sqrt(delta)) + denominator = FastMath.sqrt(b * b - delta); + } + if (denominator != 0) { + x = x2 - 2.0 * c * (x2 - x1) / denominator; + // perturb x if it exactly coincides with x1 or x2 + // the equality tests here are intentional + while (x == x1 || x == x2) { + x += absoluteAccuracy; + } + } else { + // extremely rare case, get a random number to skip it + x = min + FastMath.random() * (max - min); + oldx = Double.POSITIVE_INFINITY; + } + final double y = computeObjectiveValue(x); + + // check for convergence + final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy); + if (FastMath.abs(x - oldx) <= tolerance || + FastMath.abs(y) <= functionValueAccuracy) { + return x; + } + + // prepare the next iteration + x0 = x1; + y0 = y1; + x1 = x2; + y1 = y2; + x2 = x; + y2 = y; + oldx = x; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.java new file mode 100644 index 000000000..a0bf7144c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.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 com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements + * Newton's Method for finding zeros of real univariate differentiable + * functions. + * + * @since 3.1 + */ +public class NewtonRaphsonSolver extends AbstractUnivariateDifferentiableSolver { + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver. + */ + public NewtonRaphsonSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public NewtonRaphsonSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + + /** + * Find a zero near the midpoint of {@code min} and {@code max}. + * + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param maxEval Maximum number of evaluations. + * @return the value where the function is zero. + * @throws TooManyEvaluationsException + * if the maximum evaluation count is exceeded. + * @throws NumberIsTooLargeException + * if {@code min >= max}. + */ + @Override + public double solve(int maxEval, final UnivariateDifferentiableFunction f, + final double min, final double max) + throws TooManyEvaluationsException { + return super.solve(maxEval, f, UnivariateSolverUtils.midpoint(min, max)); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException { + final double startValue = getStartValue(); + final double absoluteAccuracy = getAbsoluteAccuracy(); + + double x0 = startValue; + double x1; + while (true) { + final DerivativeStructure y0 = computeObjectiveValueAndDerivative(x0); + x1 = x0 - (y0.getValue() / y0.getPartialDerivative(1)); + if (FastMath.abs(x1 - x0) <= absoluteAccuracy) { + return x1; + } + + x0 = x1; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonSolver.java new file mode 100644 index 000000000..62267faa6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/NewtonSolver.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 com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableUnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements + * Newton's Method for finding zeros of real univariate functions. + *

+ * The function should be continuous but not necessarily smooth.

+ * + * @deprecated as of 3.1, replaced by {@link NewtonRaphsonSolver} + */ +@Deprecated +public class NewtonSolver extends AbstractDifferentiableUnivariateSolver { + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver. + */ + public NewtonSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public NewtonSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + + /** + * Find a zero near the midpoint of {@code min} and {@code max}. + * + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param maxEval Maximum number of evaluations. + * @return the value where the function is zero. + * @throws TooManyEvaluationsException + * if the maximum evaluation count is exceeded. + * @throws NumberIsTooLargeException + * if {@code min >= max}. + */ + @Override + public double solve(int maxEval, final DifferentiableUnivariateFunction f, + final double min, final double max) + throws TooManyEvaluationsException { + return super.solve(maxEval, f, UnivariateSolverUtils.midpoint(min, max)); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException { + final double startValue = getStartValue(); + final double absoluteAccuracy = getAbsoluteAccuracy(); + + double x0 = startValue; + double x1; + while (true) { + x1 = x0 - (computeObjectiveValue(x0) / computeDerivativeObjectiveValue(x0)); + if (FastMath.abs(x1 - x0) <= absoluteAccuracy) { + return x1; + } + + x0 = x1; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PegasusSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PegasusSolver.java new file mode 100644 index 000000000..3b7dd1668 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PegasusSolver.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +/** + * Implements the Pegasus method for root-finding (approximating + * a zero of a univariate real function). It is a modified + * {@link RegulaFalsiSolver Regula Falsi} method. + * + *

Like the Regula Falsi method, convergence is guaranteed by + * maintaining a bracketed solution. The Pegasus method however, + * should converge much faster than the original Regula Falsi + * method. Furthermore, this implementation of the Pegasus method + * should not suffer from the same implementation issues as the Regula + * Falsi method, which may fail to convergence in certain cases. Also, + * the Pegasus method should converge faster than the + * {@link IllinoisSolver Illinois} method, another Regula + * Falsi-based method.

+ * + *

The Pegasus method assumes that the function is continuous, + * but not necessarily smooth.

+ * + *

Implementation based on the following article: M. Dowell and P. Jarratt, + * The "Pegasus" method for computing the root of an equation, + * BIT Numerical Mathematics, volume 12, number 4, pages 503-508, Springer, + * 1972.

+ * + * @since 3.0 + */ +public class PegasusSolver extends BaseSecantSolver { + + /** Construct a solver with default accuracy (1e-6). */ + public PegasusSolver() { + super(DEFAULT_ABSOLUTE_ACCURACY, Method.PEGASUS); + } + + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public PegasusSolver(final double absoluteAccuracy) { + super(absoluteAccuracy, Method.PEGASUS); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public PegasusSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy, Method.PEGASUS); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Maximum function value error. + */ + public PegasusSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.PEGASUS); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java new file mode 100644 index 000000000..34a786b8e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; + +/** + * Interface for (polynomial) root-finding algorithms. + * Implementations will search for only one zero in the given interval. + * + * @since 3.0 + */ +public interface PolynomialSolver + extends BaseUnivariateSolver {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java new file mode 100644 index 000000000..250436bf3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +/** + * Implements the Regula Falsi or False position method for + * root-finding (approximating a zero of a univariate real function). It is a + * modified {@link SecantSolver Secant} method. + * + *

The Regula Falsi method is included for completeness, for + * testing purposes, for educational purposes, for comparison to other + * algorithms, etc. It is however not intended to be used + * for actual problems, as one of the bounds often remains fixed, resulting + * in very slow convergence. Instead, one of the well-known modified + * Regula Falsi algorithms can be used ({@link IllinoisSolver + * Illinois} or {@link PegasusSolver Pegasus}). These two + * algorithms solve the fundamental issues of the original Regula + * Falsi algorithm, and greatly out-performs it for most, if not all, + * (practical) functions. + * + *

Unlike the Secant method, the Regula Falsi guarantees + * convergence, by maintaining a bracketed solution. Note however, that due to + * the finite/limited precision of Java's {@link Double double} type, which is + * used in this implementation, the algorithm may get stuck in a situation + * where it no longer makes any progress. Such cases are detected and result + * in a {@code ConvergenceException} exception being thrown. In other words, + * the algorithm theoretically guarantees convergence, but the implementation + * does not.

+ * + *

The Regula Falsi method assumes that the function is continuous, + * but not necessarily smooth.

+ * + *

Implementation based on the following article: M. Dowell and P. Jarratt, + * A modified regula falsi method for computing the root of an + * equation, BIT Numerical Mathematics, volume 11, number 2, + * pages 168-174, Springer, 1971.

+ * + * @since 3.0 + */ +public class RegulaFalsiSolver extends BaseSecantSolver { + + /** Construct a solver with default accuracy (1e-6). */ + public RegulaFalsiSolver() { + super(DEFAULT_ABSOLUTE_ACCURACY, Method.REGULA_FALSI); + } + + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public RegulaFalsiSolver(final double absoluteAccuracy) { + super(absoluteAccuracy, Method.REGULA_FALSI); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public RegulaFalsiSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy, Method.REGULA_FALSI); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Maximum function value error. + */ + public RegulaFalsiSolver(final double relativeAccuracy, + final double absoluteAccuracy, + final double functionValueAccuracy) { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.REGULA_FALSI); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RiddersSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RiddersSolver.java new file mode 100644 index 000000000..e7427523c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/RiddersSolver.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the + * Ridders' Method for root finding of real univariate functions. For + * reference, see C. Ridders, A new algorithm for computing a single root + * of a real continuous function , IEEE Transactions on Circuits and + * Systems, 26 (1979), 979 - 980. + *

+ * The function should be continuous but not necessarily smooth.

+ * + * @since 1.2 + */ +public class RiddersSolver extends AbstractUnivariateSolver { + /** Default absolute accuracy. */ + private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** + * Construct a solver with default accuracy (1e-6). + */ + public RiddersSolver() { + this(DEFAULT_ABSOLUTE_ACCURACY); + } + /** + * Construct a solver. + * + * @param absoluteAccuracy Absolute accuracy. + */ + public RiddersSolver(double absoluteAccuracy) { + super(absoluteAccuracy); + } + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + */ + public RiddersSolver(double relativeAccuracy, + double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + + /** + * {@inheritDoc} + */ + @Override + protected double doSolve() + throws TooManyEvaluationsException, + NoBracketingException { + double min = getMin(); + double max = getMax(); + // [x1, x2] is the bracketing interval in each iteration + // x3 is the midpoint of [x1, x2] + // x is the new root approximation and an endpoint of the new interval + double x1 = min; + double y1 = computeObjectiveValue(x1); + double x2 = max; + double y2 = computeObjectiveValue(x2); + + // check for zeros before verifying bracketing + if (y1 == 0) { + return min; + } + if (y2 == 0) { + return max; + } + verifyBracketing(min, max); + + final double absoluteAccuracy = getAbsoluteAccuracy(); + final double functionValueAccuracy = getFunctionValueAccuracy(); + final double relativeAccuracy = getRelativeAccuracy(); + + double oldx = Double.POSITIVE_INFINITY; + while (true) { + // calculate the new root approximation + final double x3 = 0.5 * (x1 + x2); + final double y3 = computeObjectiveValue(x3); + if (FastMath.abs(y3) <= functionValueAccuracy) { + return x3; + } + final double delta = 1 - (y1 * y2) / (y3 * y3); // delta > 1 due to bracketing + final double correction = (FastMath.signum(y2) * FastMath.signum(y3)) * + (x3 - x1) / FastMath.sqrt(delta); + final double x = x3 - correction; // correction != 0 + final double y = computeObjectiveValue(x); + + // check for convergence + final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy); + if (FastMath.abs(x - oldx) <= tolerance) { + return x; + } + if (FastMath.abs(y) <= functionValueAccuracy) { + return x; + } + + // prepare the new interval for next iteration + // Ridders' method guarantees x1 < x < x2 + if (correction > 0.0) { // x1 < x < x3 + if (FastMath.signum(y1) + FastMath.signum(y) == 0.0) { + x2 = x; + y2 = y; + } else { + x1 = x; + x2 = x3; + y1 = y; + y2 = y3; + } + } else { // x3 < x < x2 + if (FastMath.signum(y2) + FastMath.signum(y) == 0.0) { + x1 = x; + y1 = y; + } else { + x1 = x3; + x2 = x; + y1 = y3; + y2 = y; + } + } + oldx = x; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/SecantSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/SecantSolver.java new file mode 100644 index 000000000..2b5fff592 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/SecantSolver.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the Secant method for root-finding (approximating a + * zero of a univariate real function). The solution that is maintained is + * not bracketed, and as such convergence is not guaranteed. + * + *

Implementation based on the following article: M. Dowell and P. Jarratt, + * A modified regula falsi method for computing the root of an + * equation, BIT Numerical Mathematics, volume 11, number 2, + * pages 168-174, Springer, 1971.

+ * + *

Note that since release 3.0 this class implements the actual + * Secant algorithm, and not a modified one. As such, the 3.0 version + * is not backwards compatible with previous versions. To use an algorithm + * similar to the pre-3.0 releases, use the + * {@link IllinoisSolver Illinois} algorithm or the + * {@link PegasusSolver Pegasus} algorithm.

+ * + */ +public class SecantSolver extends AbstractUnivariateSolver { + + /** Default absolute accuracy. */ + protected static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + + /** Construct a solver with default accuracy (1e-6). */ + public SecantSolver() { + super(DEFAULT_ABSOLUTE_ACCURACY); + } + + /** + * Construct a solver. + * + * @param absoluteAccuracy absolute accuracy + */ + public SecantSolver(final double absoluteAccuracy) { + super(absoluteAccuracy); + } + + /** + * Construct a solver. + * + * @param relativeAccuracy relative accuracy + * @param absoluteAccuracy absolute accuracy + */ + public SecantSolver(final double relativeAccuracy, + final double absoluteAccuracy) { + super(relativeAccuracy, absoluteAccuracy); + } + + /** {@inheritDoc} */ + @Override + protected final double doSolve() + throws TooManyEvaluationsException, + NoBracketingException { + // Get initial solution + double x0 = getMin(); + double x1 = getMax(); + double f0 = computeObjectiveValue(x0); + double f1 = computeObjectiveValue(x1); + + // If one of the bounds is the exact root, return it. Since these are + // not under-approximations or over-approximations, we can return them + // regardless of the allowed solutions. + if (f0 == 0.0) { + return x0; + } + if (f1 == 0.0) { + return x1; + } + + // Verify bracketing of initial solution. + verifyBracketing(x0, x1); + + // Get accuracies. + final double ftol = getFunctionValueAccuracy(); + final double atol = getAbsoluteAccuracy(); + final double rtol = getRelativeAccuracy(); + + // Keep finding better approximations. + while (true) { + // Calculate the next approximation. + final double x = x1 - ((f1 * (x1 - x0)) / (f1 - f0)); + final double fx = computeObjectiveValue(x); + + // If the new approximation is the exact root, return it. Since + // this is not an under-approximation or an over-approximation, + // we can return it regardless of the allowed solutions. + if (fx == 0.0) { + return x; + } + + // Update the bounds with the new approximation. + x0 = x1; + f0 = f1; + x1 = x; + f1 = fx; + + // If the function value of the last approximation is too small, + // given the function value accuracy, then we can't get closer to + // the root than we already are. + if (FastMath.abs(f1) <= ftol) { + return x1; + } + + // If the current interval is within the given accuracies, we + // are satisfied with the current approximation. + if (FastMath.abs(x1 - x0) < FastMath.max(rtol * FastMath.abs(x1), atol)) { + return x1; + } + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java new file mode 100644 index 000000000..88b1f430d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction; + + +/** + * Interface for (univariate real) rootfinding algorithms. + * Implementations will search for only one zero in the given interval. + * + * @since 3.1 + */ +public interface UnivariateDifferentiableSolver + extends BaseUnivariateSolver {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java new file mode 100644 index 000000000..9309d9ad1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + + +/** + * Interface for (univariate real) root-finding algorithms. + * Implementations will search for only one zero in the given interval. + * + */ +public interface UnivariateSolver + extends BaseUnivariateSolver {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java new file mode 100644 index 000000000..084ea996a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java @@ -0,0 +1,467 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Utility routines for {@link UnivariateSolver} objects. + * + */ +public class UnivariateSolverUtils { + /** + * Class contains only static methods. + */ + private UnivariateSolverUtils() {} + + /** + * Convenience method to find a zero of a univariate real function. A default + * solver is used. + * + * @param function Function. + * @param x0 Lower bound for the interval. + * @param x1 Upper bound for the interval. + * @return a value where the function is zero. + * @throws NoBracketingException if the function has the same sign at the + * endpoints. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static double solve(UnivariateFunction function, double x0, double x1) + throws NullArgumentException, + NoBracketingException { + if (function == null) { + throw new NullArgumentException(LocalizedFormats.FUNCTION); + } + final UnivariateSolver solver = new BrentSolver(); + return solver.solve(Integer.MAX_VALUE, function, x0, x1); + } + + /** + * Convenience method to find a zero of a univariate real function. A default + * solver is used. + * + * @param function Function. + * @param x0 Lower bound for the interval. + * @param x1 Upper bound for the interval. + * @param absoluteAccuracy Accuracy to be used by the solver. + * @return a value where the function is zero. + * @throws NoBracketingException if the function has the same sign at the + * endpoints. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static double solve(UnivariateFunction function, + double x0, double x1, + double absoluteAccuracy) + throws NullArgumentException, + NoBracketingException { + if (function == null) { + throw new NullArgumentException(LocalizedFormats.FUNCTION); + } + final UnivariateSolver solver = new BrentSolver(absoluteAccuracy); + return solver.solve(Integer.MAX_VALUE, function, x0, x1); + } + + /** + * Force a root found by a non-bracketing solver to lie on a specified side, + * as if the solver were a bracketing one. + * + * @param maxEval maximal number of new evaluations of the function + * (evaluations already done for finding the root should have already been subtracted + * from this number) + * @param f function to solve + * @param bracketing bracketing solver to use for shifting the root + * @param baseRoot original root found by a previous non-bracketing solver + * @param min minimal bound of the search interval + * @param max maximal bound of the search interval + * @param allowedSolution the kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return a root approximation, on the specified side of the exact root + * @throws NoBracketingException if the function has the same sign at the + * endpoints. + */ + public static double forceSide(final int maxEval, final UnivariateFunction f, + final BracketedUnivariateSolver bracketing, + final double baseRoot, final double min, final double max, + final AllowedSolution allowedSolution) + throws NoBracketingException { + + if (allowedSolution == AllowedSolution.ANY_SIDE) { + // no further bracketing required + return baseRoot; + } + + // find a very small interval bracketing the root + final double step = FastMath.max(bracketing.getAbsoluteAccuracy(), + FastMath.abs(baseRoot * bracketing.getRelativeAccuracy())); + double xLo = FastMath.max(min, baseRoot - step); + double fLo = f.value(xLo); + double xHi = FastMath.min(max, baseRoot + step); + double fHi = f.value(xHi); + int remainingEval = maxEval - 2; + while (remainingEval > 0) { + + if ((fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0)) { + // compute the root on the selected side + return bracketing.solve(remainingEval, f, xLo, xHi, baseRoot, allowedSolution); + } + + // try increasing the interval + boolean changeLo = false; + boolean changeHi = false; + if (fLo < fHi) { + // increasing function + if (fLo >= 0) { + changeLo = true; + } else { + changeHi = true; + } + } else if (fLo > fHi) { + // decreasing function + if (fLo <= 0) { + changeLo = true; + } else { + changeHi = true; + } + } else { + // unknown variation + changeLo = true; + changeHi = true; + } + + // update the lower bound + if (changeLo) { + xLo = FastMath.max(min, xLo - step); + fLo = f.value(xLo); + remainingEval--; + } + + // update the higher bound + if (changeHi) { + xHi = FastMath.min(max, xHi + step); + fHi = f.value(xHi); + remainingEval--; + } + + } + + throw new NoBracketingException(LocalizedFormats.FAILED_BRACKETING, + xLo, xHi, fLo, fHi, + maxEval - remainingEval, maxEval, baseRoot, + min, max); + + } + + /** + * This method simply calls {@link #bracket(UnivariateFunction, double, double, double, + * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)} + * with {@code q} and {@code r} set to 1.0 and {@code maximumIterations} set to {@code Integer.MAX_VALUE}. + *

+ * Note: this method can take {@code Integer.MAX_VALUE} + * iterations to throw a {@code ConvergenceException.} Unless you are + * confident that there is a root between {@code lowerBound} and + * {@code upperBound} near {@code initial}, it is better to use + * {@link #bracket(UnivariateFunction, double, double, double, double,double, int) + * bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}, + * explicitly specifying the maximum number of iterations.

+ * + * @param function Function. + * @param initial Initial midpoint of interval being expanded to + * bracket a root. + * @param lowerBound Lower bound (a is never lower than this value) + * @param upperBound Upper bound (b never is greater than this + * value). + * @return a two-element array holding a and b. + * @throws NoBracketingException if a root cannot be bracketted. + * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static double[] bracket(UnivariateFunction function, + double initial, + double lowerBound, double upperBound) + throws NullArgumentException, + NotStrictlyPositiveException, + NoBracketingException { + return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, Integer.MAX_VALUE); + } + + /** + * This method simply calls {@link #bracket(UnivariateFunction, double, double, double, + * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)} + * with {@code q} and {@code r} set to 1.0. + * @param function Function. + * @param initial Initial midpoint of interval being expanded to + * bracket a root. + * @param lowerBound Lower bound (a is never lower than this value). + * @param upperBound Upper bound (b never is greater than this + * value). + * @param maximumIterations Maximum number of iterations to perform + * @return a two element array holding a and b. + * @throws NoBracketingException if the algorithm fails to find a and b + * satisfying the desired conditions. + * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static double[] bracket(UnivariateFunction function, + double initial, + double lowerBound, double upperBound, + int maximumIterations) + throws NullArgumentException, + NotStrictlyPositiveException, + NoBracketingException { + return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, maximumIterations); + } + + /** + * This method attempts to find two values a and b satisfying
    + *
  • {@code lowerBound <= a < initial < b <= upperBound}
  • + *
  • {@code f(a) * f(b) <= 0}
  • + *
+ * If {@code f} is continuous on {@code [a,b]}, this means that {@code a} + * and {@code b} bracket a root of {@code f}. + *

+ * The algorithm checks the sign of \( f(l_k) \) and \( f(u_k) \) for increasing + * values of k, where \( l_k = max(lower, initial - \delta_k) \), + * \( u_k = min(upper, initial + \delta_k) \), using recurrence + * \( \delta_{k+1} = r \delta_k + q, \delta_0 = 0\) and starting search with \( k=1 \). + * The algorithm stops when one of the following happens:

    + *
  • at least one positive and one negative value have been found -- success!
  • + *
  • both endpoints have reached their respective limits -- NoBracketingException
  • + *
  • {@code maximumIterations} iterations elapse -- NoBracketingException
+ *

+ * If different signs are found at first iteration ({@code k=1}), then the returned + * interval will be \( [a, b] = [l_1, u_1] \). If different signs are found at a later + * iteration {@code k>1}, then the returned interval will be either + * \( [a, b] = [l_{k+1}, l_{k}] \) or \( [a, b] = [u_{k}, u_{k+1}] \). A root solver called + * with these parameters will therefore start with the smallest bracketing interval known + * at this step. + *

+ *

+ * Interval expansion rate is tuned by changing the recurrence parameters {@code r} and + * {@code q}. When the multiplicative factor {@code r} is set to 1, the sequence is a + * simple arithmetic sequence with linear increase. When the multiplicative factor {@code r} + * is larger than 1, the sequence has an asymptotically exponential rate. Note than the + * additive parameter {@code q} should never be set to zero, otherwise the interval would + * degenerate to the single initial point for all values of {@code k}. + *

+ *

+ * As a rule of thumb, when the location of the root is expected to be approximately known + * within some error margin, {@code r} should be set to 1 and {@code q} should be set to the + * order of magnitude of the error margin. When the location of the root is really a wild guess, + * then {@code r} should be set to a value larger than 1 (typically 2 to double the interval + * length at each iteration) and {@code q} should be set according to half the initial + * search interval length. + *

+ *

+ * As an example, if we consider the trivial function {@code f(x) = 1 - x} and use + * {@code initial = 4}, {@code r = 1}, {@code q = 2}, the algorithm will compute + * {@code f(4-2) = f(2) = -1} and {@code f(4+2) = f(6) = -5} for {@code k = 1}, then + * {@code f(4-4) = f(0) = +1} and {@code f(4+4) = f(8) = -7} for {@code k = 2}. Then it will + * return the interval {@code [0, 2]} as the smallest one known to be bracketing the root. + * As shown by this example, the initial value (here {@code 4}) may lie outside of the returned + * bracketing interval. + *

+ * @param function function to check + * @param initial Initial midpoint of interval being expanded to + * bracket a root. + * @param lowerBound Lower bound (a is never lower than this value). + * @param upperBound Upper bound (b never is greater than this + * value). + * @param q additive offset used to compute bounds sequence (must be strictly positive) + * @param r multiplicative factor used to compute bounds sequence + * @param maximumIterations Maximum number of iterations to perform + * @return a two element array holding the bracketing values. + * @exception NoBracketingException if function cannot be bracketed in the search interval + */ + public static double[] bracket(final UnivariateFunction function, final double initial, + final double lowerBound, final double upperBound, + final double q, final double r, final int maximumIterations) + throws NoBracketingException { + + if (function == null) { + throw new NullArgumentException(LocalizedFormats.FUNCTION); + } + if (q <= 0) { + throw new NotStrictlyPositiveException(q); + } + if (maximumIterations <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations); + } + verifySequence(lowerBound, initial, upperBound); + + // initialize the recurrence + double a = initial; + double b = initial; + double fa = Double.NaN; + double fb = Double.NaN; + double delta = 0; + + for (int numIterations = 0; + (numIterations < maximumIterations) && (a > lowerBound || b < upperBound); + ++numIterations) { + + final double previousA = a; + final double previousFa = fa; + final double previousB = b; + final double previousFb = fb; + + delta = r * delta + q; + a = FastMath.max(initial - delta, lowerBound); + b = FastMath.min(initial + delta, upperBound); + fa = function.value(a); + fb = function.value(b); + + if (numIterations == 0) { + // at first iteration, we don't have a previous interval + // we simply compare both sides of the initial interval + if (fa * fb <= 0) { + // the first interval already brackets a root + return new double[] { a, b }; + } + } else { + // we have a previous interval with constant sign and expand it, + // we expect sign changes to occur at boundaries + if (fa * previousFa <= 0) { + // sign change detected at near lower bound + return new double[] { a, previousA }; + } else if (fb * previousFb <= 0) { + // sign change detected at near upper bound + return new double[] { previousB, b }; + } + } + + } + + // no bracketing found + throw new NoBracketingException(a, b, fa, fb); + + } + + /** + * Compute the midpoint of two values. + * + * @param a first value. + * @param b second value. + * @return the midpoint. + */ + public static double midpoint(double a, double b) { + return (a + b) * 0.5; + } + + /** + * Check whether the interval bounds bracket a root. That is, if the + * values at the endpoints are not equal to zero, then the function takes + * opposite signs at the endpoints. + * + * @param function Function. + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @return {@code true} if the function values have opposite signs at the + * given points. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static boolean isBracketing(UnivariateFunction function, + final double lower, + final double upper) + throws NullArgumentException { + if (function == null) { + throw new NullArgumentException(LocalizedFormats.FUNCTION); + } + final double fLo = function.value(lower); + final double fHi = function.value(upper); + return (fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0); + } + + /** + * Check whether the arguments form a (strictly) increasing sequence. + * + * @param start First number. + * @param mid Second number. + * @param end Third number. + * @return {@code true} if the arguments form an increasing sequence. + */ + public static boolean isSequence(final double start, + final double mid, + final double end) { + return (start < mid) && (mid < end); + } + + /** + * Check that the endpoints specify an interval. + * + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + public static void verifyInterval(final double lower, + final double upper) + throws NumberIsTooLargeException { + if (lower >= upper) { + throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, + lower, upper, false); + } + } + + /** + * Check that {@code lower < initial < upper}. + * + * @param lower Lower endpoint. + * @param initial Initial value. + * @param upper Upper endpoint. + * @throws NumberIsTooLargeException if {@code lower >= initial} or + * {@code initial >= upper}. + */ + public static void verifySequence(final double lower, + final double initial, + final double upper) + throws NumberIsTooLargeException { + verifyInterval(lower, initial); + verifyInterval(initial, upper); + } + + /** + * Check that the endpoints specify an interval and the end points + * bracket a root. + * + * @param function Function. + * @param lower Lower endpoint. + * @param upper Upper endpoint. + * @throws NoBracketingException if the function has the same sign at the + * endpoints. + * @throws NullArgumentException if {@code function} is {@code null}. + */ + public static void verifyBracketing(UnivariateFunction function, + final double lower, + final double upper) + throws NullArgumentException, + NoBracketingException { + if (function == null) { + throw new NullArgumentException(LocalizedFormats.FUNCTION); + } + verifyInterval(lower, upper); + if (!isBracketing(function, lower, upper)) { + throw new NoBracketingException(lower, upper, + function.value(lower), + function.value(upper)); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/package-info.java new file mode 100644 index 000000000..52331c648 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/analysis/solvers/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Root finding algorithms, for univariate real functions. + * + */ +package com.fr.third.org.apache.commons.math3.analysis.solvers; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Complex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Complex.java new file mode 100644 index 000000000..aabbed5f9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Complex.java @@ -0,0 +1,1300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.complex; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Representation of a Complex number, i.e. a number which has both a + * real and imaginary part. + *

+ * Implementations of arithmetic operations handle {@code NaN} and + * infinite values according to the rules for {@link java.lang.Double}, i.e. + * {@link #equals} is an equivalence relation for all instances that have + * a {@code NaN} in either real or imaginary part, e.g. the following are + * considered equal: + *

    + *
  • {@code 1 + NaNi}
  • + *
  • {@code NaN + i}
  • + *
  • {@code NaN + NaNi}
  • + *

+ * Note that this contradicts the IEEE-754 standard for floating + * point numbers (according to which the test {@code x == x} must fail if + * {@code x} is {@code NaN}). The method + * {@link Precision#equals(double,double,int) + * equals for primitive double} in {@link Precision} + * conforms with IEEE-754 while this class conforms with the standard behavior + * for Java object types.

+ * + */ +public class Complex implements FieldElement, Serializable { + /** The square root of -1. A number representing "0.0 + 1.0i" */ + public static final Complex I = new Complex(0.0, 1.0); + // CHECKSTYLE: stop ConstantName + /** A complex number representing "NaN + NaNi" */ + public static final Complex NaN = new Complex(Double.NaN, Double.NaN); + // CHECKSTYLE: resume ConstantName + /** A complex number representing "+INF + INFi" */ + public static final Complex INF = new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + /** A complex number representing "1.0 + 0.0i" */ + public static final Complex ONE = new Complex(1.0, 0.0); + /** A complex number representing "0.0 + 0.0i" */ + public static final Complex ZERO = new Complex(0.0, 0.0); + + /** Serializable version identifier */ + private static final long serialVersionUID = -6195664516687396620L; + + /** The imaginary part. */ + private final double imaginary; + /** The real part. */ + private final double real; + /** Record whether this complex number is equal to NaN. */ + private final transient boolean isNaN; + /** Record whether this complex number is infinite. */ + private final transient boolean isInfinite; + + /** + * Create a complex number given only the real part. + * + * @param real Real part. + */ + public Complex(double real) { + this(real, 0.0); + } + + /** + * Create a complex number given the real and imaginary parts. + * + * @param real Real part. + * @param imaginary Imaginary part. + */ + public Complex(double real, double imaginary) { + this.real = real; + this.imaginary = imaginary; + + isNaN = Double.isNaN(real) || Double.isNaN(imaginary); + isInfinite = !isNaN && + (Double.isInfinite(real) || Double.isInfinite(imaginary)); + } + + /** + * Return the absolute value of this complex number. + * Returns {@code NaN} if either real or imaginary part is {@code NaN} + * and {@code Double.POSITIVE_INFINITY} if neither part is {@code NaN}, + * but at least one part is infinite. + * + * @return the absolute value. + */ + public double abs() { + if (isNaN) { + return Double.NaN; + } + if (isInfinite()) { + return Double.POSITIVE_INFINITY; + } + if (FastMath.abs(real) < FastMath.abs(imaginary)) { + if (imaginary == 0.0) { + return FastMath.abs(real); + } + double q = real / imaginary; + return FastMath.abs(imaginary) * FastMath.sqrt(1 + q * q); + } else { + if (real == 0.0) { + return FastMath.abs(imaginary); + } + double q = imaginary / real; + return FastMath.abs(real) * FastMath.sqrt(1 + q * q); + } + } + + /** + * Returns a {@code Complex} whose value is + * {@code (this + addend)}. + * Uses the definitional formula + *

+ * {@code (a + bi) + (c + di) = (a+c) + (b+d)i} + *

+ * If either {@code this} or {@code addend} has a {@code NaN} value in + * either part, {@link #NaN} is returned; otherwise {@code Infinite} + * and {@code NaN} values are returned in the parts of the result + * according to the rules for {@link java.lang.Double} arithmetic. + * + * @param addend Value to be added to this {@code Complex}. + * @return {@code this + addend}. + * @throws NullArgumentException if {@code addend} is {@code null}. + */ + public Complex add(Complex addend) throws NullArgumentException { + MathUtils.checkNotNull(addend); + if (isNaN || addend.isNaN) { + return NaN; + } + + return createComplex(real + addend.getReal(), + imaginary + addend.getImaginary()); + } + + /** + * Returns a {@code Complex} whose value is {@code (this + addend)}, + * with {@code addend} interpreted as a real number. + * + * @param addend Value to be added to this {@code Complex}. + * @return {@code this + addend}. + * @see #add(Complex) + */ + public Complex add(double addend) { + if (isNaN || Double.isNaN(addend)) { + return NaN; + } + + return createComplex(real + addend, imaginary); + } + + /** + * Returns the conjugate of this complex number. + * The conjugate of {@code a + bi} is {@code a - bi}. + *

+ * {@link #NaN} is returned if either the real or imaginary + * part of this Complex number equals {@code Double.NaN}. + *

+ * If the imaginary part is infinite, and the real part is not + * {@code NaN}, the returned value has infinite imaginary part + * of the opposite sign, e.g. the conjugate of + * {@code 1 + POSITIVE_INFINITY i} is {@code 1 - NEGATIVE_INFINITY i}. + *

+ * @return the conjugate of this Complex object. + */ + public Complex conjugate() { + if (isNaN) { + return NaN; + } + + return createComplex(real, -imaginary); + } + + /** + * Returns a {@code Complex} whose value is + * {@code (this / divisor)}. + * Implements the definitional formula + *
+     *  
+     *    a + bi          ac + bd + (bc - ad)i
+     *    ----------- = -------------------------
+     *    c + di         c2 + d2
+     *  
+     * 
+ * but uses + * + * prescaling of operands to limit the effects of overflows and + * underflows in the computation. + *

+ * {@code Infinite} and {@code NaN} values are handled according to the + * following rules, applied in the order presented: + *

    + *
  • If either {@code this} or {@code divisor} has a {@code NaN} value + * in either part, {@link #NaN} is returned. + *
  • + *
  • If {@code divisor} equals {@link #ZERO}, {@link #NaN} is returned. + *
  • + *
  • If {@code this} and {@code divisor} are both infinite, + * {@link #NaN} is returned. + *
  • + *
  • If {@code this} is finite (i.e., has no {@code Infinite} or + * {@code NaN} parts) and {@code divisor} is infinite (one or both parts + * infinite), {@link #ZERO} is returned. + *
  • + *
  • If {@code this} is infinite and {@code divisor} is finite, + * {@code NaN} values are returned in the parts of the result if the + * {@link java.lang.Double} rules applied to the definitional formula + * force {@code NaN} results. + *
  • + *
+ * + * @param divisor Value by which this {@code Complex} is to be divided. + * @return {@code this / divisor}. + * @throws NullArgumentException if {@code divisor} is {@code null}. + */ + public Complex divide(Complex divisor) + throws NullArgumentException { + MathUtils.checkNotNull(divisor); + if (isNaN || divisor.isNaN) { + return NaN; + } + + final double c = divisor.getReal(); + final double d = divisor.getImaginary(); + if (c == 0.0 && d == 0.0) { + return NaN; + } + + if (divisor.isInfinite() && !isInfinite()) { + return ZERO; + } + + if (FastMath.abs(c) < FastMath.abs(d)) { + double q = c / d; + double denominator = c * q + d; + return createComplex((real * q + imaginary) / denominator, + (imaginary * q - real) / denominator); + } else { + double q = d / c; + double denominator = d * q + c; + return createComplex((imaginary * q + real) / denominator, + (imaginary - real * q) / denominator); + } + } + + /** + * Returns a {@code Complex} whose value is {@code (this / divisor)}, + * with {@code divisor} interpreted as a real number. + * + * @param divisor Value by which this {@code Complex} is to be divided. + * @return {@code this / divisor}. + * @see #divide(Complex) + */ + public Complex divide(double divisor) { + if (isNaN || Double.isNaN(divisor)) { + return NaN; + } + if (divisor == 0d) { + return NaN; + } + if (Double.isInfinite(divisor)) { + return !isInfinite() ? ZERO : NaN; + } + return createComplex(real / divisor, + imaginary / divisor); + } + + /** {@inheritDoc} */ + public Complex reciprocal() { + if (isNaN) { + return NaN; + } + + if (real == 0.0 && imaginary == 0.0) { + return INF; + } + + if (isInfinite) { + return ZERO; + } + + if (FastMath.abs(real) < FastMath.abs(imaginary)) { + double q = real / imaginary; + double scale = 1. / (real * q + imaginary); + return createComplex(scale * q, -scale); + } else { + double q = imaginary / real; + double scale = 1. / (imaginary * q + real); + return createComplex(scale, -scale * q); + } + } + + /** + * Test for equality with another object. + * If both the real and imaginary parts of two complex numbers + * are exactly the same, and neither is {@code Double.NaN}, the two + * Complex objects are considered to be equal. + * The behavior is the same as for JDK's {@link Double#equals(Object) + * Double}: + *
    + *
  • All {@code NaN} values are considered to be equal, + * i.e, if either (or both) real and imaginary parts of the complex + * number are equal to {@code Double.NaN}, the complex number is equal + * to {@code NaN}. + *
  • + *
  • + * Instances constructed with different representations of zero (i.e. + * either "0" or "-0") are not considered to be equal. + *
  • + *
+ * + * @param other Object to test for equality with this instance. + * @return {@code true} if the objects are equal, {@code false} if object + * is {@code null}, not an instance of {@code Complex}, or not equal to + * this instance. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Complex){ + Complex c = (Complex) other; + if (c.isNaN) { + return isNaN; + } else { + return MathUtils.equals(real, c.real) && + MathUtils.equals(imaginary, c.imaginary); + } + } + return false; + } + + /** + * Test for the floating-point equality between Complex objects. + * It returns {@code true} if both arguments are equal or within the + * range of allowed error (inclusive). + * + * @param x First value (cannot be {@code null}). + * @param y Second value (cannot be {@code null}). + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between the real (resp. imaginary) parts of {@code x} and + * {@code y}. + * @return {@code true} if there are fewer than {@code maxUlps} floating + * point values between the real (resp. imaginary) parts of {@code x} + * and {@code y}. + * + * @see Precision#equals(double,double,int) + * @since 3.3 + */ + public static boolean equals(Complex x, Complex y, int maxUlps) { + return Precision.equals(x.real, y.real, maxUlps) && + Precision.equals(x.imaginary, y.imaginary, maxUlps); + } + + /** + * Returns {@code true} iff the values are equal as defined by + * {@link #equals(Complex,Complex,int) equals(x, y, 1)}. + * + * @param x First value (cannot be {@code null}). + * @param y Second value (cannot be {@code null}). + * @return {@code true} if the values are equal. + * + * @since 3.3 + */ + public static boolean equals(Complex x, Complex y) { + return equals(x, y, 1); + } + + /** + * Returns {@code true} if, both for the real part and for the imaginary + * part, there is no double value strictly between the arguments or the + * difference between them is within the range of allowed error + * (inclusive). Returns {@code false} if either of the arguments is NaN. + * + * @param x First value (cannot be {@code null}). + * @param y Second value (cannot be {@code null}). + * @param eps Amount of allowed absolute error. + * @return {@code true} if the values are two adjacent floating point + * numbers or they are within range of each other. + * + * @see Precision#equals(double,double,double) + * @since 3.3 + */ + public static boolean equals(Complex x, Complex y, double eps) { + return Precision.equals(x.real, y.real, eps) && + Precision.equals(x.imaginary, y.imaginary, eps); + } + + /** + * Returns {@code true} if, both for the real part and for the imaginary + * part, there is no double value strictly between the arguments or the + * relative difference between them is smaller or equal to the given + * tolerance. Returns {@code false} if either of the arguments is NaN. + * + * @param x First value (cannot be {@code null}). + * @param y Second value (cannot be {@code null}). + * @param eps Amount of allowed relative error. + * @return {@code true} if the values are two adjacent floating point + * numbers or they are within range of each other. + * + * @see Precision#equalsWithRelativeTolerance(double,double,double) + * @since 3.3 + */ + public static boolean equalsWithRelativeTolerance(Complex x, Complex y, + double eps) { + return Precision.equalsWithRelativeTolerance(x.real, y.real, eps) && + Precision.equalsWithRelativeTolerance(x.imaginary, y.imaginary, eps); + } + + /** + * Get a hashCode for the complex number. + * Any {@code Double.NaN} value in real or imaginary part produces + * the same hash code {@code 7}. + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() { + if (isNaN) { + return 7; + } + return 37 * (17 * MathUtils.hash(imaginary) + + MathUtils.hash(real)); + } + + /** + * Access the imaginary part. + * + * @return the imaginary part. + */ + public double getImaginary() { + return imaginary; + } + + /** + * Access the real part. + * + * @return the real part. + */ + public double getReal() { + return real; + } + + /** + * Checks whether either or both parts of this complex number is + * {@code NaN}. + * + * @return true if either or both parts of this complex number is + * {@code NaN}; false otherwise. + */ + public boolean isNaN() { + return isNaN; + } + + /** + * Checks whether either the real or imaginary part of this complex number + * takes an infinite value (either {@code Double.POSITIVE_INFINITY} or + * {@code Double.NEGATIVE_INFINITY}) and neither part + * is {@code NaN}. + * + * @return true if one or both parts of this complex number are infinite + * and neither part is {@code NaN}. + */ + public boolean isInfinite() { + return isInfinite; + } + + /** + * Returns a {@code Complex} whose value is {@code this * factor}. + * Implements preliminary checks for {@code NaN} and infinity followed by + * the definitional formula: + *

+ * {@code (a + bi)(c + di) = (ac - bd) + (ad + bc)i} + *

+ * Returns {@link #NaN} if either {@code this} or {@code factor} has one or + * more {@code NaN} parts. + *

+ * Returns {@link #INF} if neither {@code this} nor {@code factor} has one + * or more {@code NaN} parts and if either {@code this} or {@code factor} + * has one or more infinite parts (same result is returned regardless of + * the sign of the components). + *

+ * Returns finite values in components of the result per the definitional + * formula in all remaining cases.

+ * + * @param factor value to be multiplied by this {@code Complex}. + * @return {@code this * factor}. + * @throws NullArgumentException if {@code factor} is {@code null}. + */ + public Complex multiply(Complex factor) + throws NullArgumentException { + MathUtils.checkNotNull(factor); + if (isNaN || factor.isNaN) { + return NaN; + } + if (Double.isInfinite(real) || + Double.isInfinite(imaginary) || + Double.isInfinite(factor.real) || + Double.isInfinite(factor.imaginary)) { + // we don't use isInfinite() to avoid testing for NaN again + return INF; + } + return createComplex(real * factor.real - imaginary * factor.imaginary, + real * factor.imaginary + imaginary * factor.real); + } + + /** + * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor} + * interpreted as a integer number. + * + * @param factor value to be multiplied by this {@code Complex}. + * @return {@code this * factor}. + * @see #multiply(Complex) + */ + public Complex multiply(final int factor) { + if (isNaN) { + return NaN; + } + if (Double.isInfinite(real) || + Double.isInfinite(imaginary)) { + return INF; + } + return createComplex(real * factor, imaginary * factor); + } + + /** + * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor} + * interpreted as a real number. + * + * @param factor value to be multiplied by this {@code Complex}. + * @return {@code this * factor}. + * @see #multiply(Complex) + */ + public Complex multiply(double factor) { + if (isNaN || Double.isNaN(factor)) { + return NaN; + } + if (Double.isInfinite(real) || + Double.isInfinite(imaginary) || + Double.isInfinite(factor)) { + // we don't use isInfinite() to avoid testing for NaN again + return INF; + } + return createComplex(real * factor, imaginary * factor); + } + + /** + * Returns a {@code Complex} whose value is {@code (-this)}. + * Returns {@code NaN} if either real or imaginary + * part of this Complex number is {@code Double.NaN}. + * + * @return {@code -this}. + */ + public Complex negate() { + if (isNaN) { + return NaN; + } + + return createComplex(-real, -imaginary); + } + + /** + * Returns a {@code Complex} whose value is + * {@code (this - subtrahend)}. + * Uses the definitional formula + *

+ * {@code (a + bi) - (c + di) = (a-c) + (b-d)i} + *

+ * If either {@code this} or {@code subtrahend} has a {@code NaN]} value in either part, + * {@link #NaN} is returned; otherwise infinite and {@code NaN} values are + * returned in the parts of the result according to the rules for + * {@link java.lang.Double} arithmetic. + * + * @param subtrahend value to be subtracted from this {@code Complex}. + * @return {@code this - subtrahend}. + * @throws NullArgumentException if {@code subtrahend} is {@code null}. + */ + public Complex subtract(Complex subtrahend) + throws NullArgumentException { + MathUtils.checkNotNull(subtrahend); + if (isNaN || subtrahend.isNaN) { + return NaN; + } + + return createComplex(real - subtrahend.getReal(), + imaginary - subtrahend.getImaginary()); + } + + /** + * Returns a {@code Complex} whose value is + * {@code (this - subtrahend)}. + * + * @param subtrahend value to be subtracted from this {@code Complex}. + * @return {@code this - subtrahend}. + * @see #subtract(Complex) + */ + public Complex subtract(double subtrahend) { + if (isNaN || Double.isNaN(subtrahend)) { + return NaN; + } + return createComplex(real - subtrahend, imaginary); + } + + /** + * Compute the + * + * inverse cosine of this complex number. + * Implements the formula: + *

+ * {@code acos(z) = -i (log(z + i (sqrt(1 - z2))))} + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN} or infinite. + * + * @return the inverse cosine of this complex number. + * @since 1.2 + */ + public Complex acos() { + if (isNaN) { + return NaN; + } + + return this.add(this.sqrt1z().multiply(I)).log().multiply(I.negate()); + } + + /** + * Compute the + * + * inverse sine of this complex number. + * Implements the formula: + *

+ * {@code asin(z) = -i (log(sqrt(1 - z2) + iz))} + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN} or infinite.

+ * + * @return the inverse sine of this complex number. + * @since 1.2 + */ + public Complex asin() { + if (isNaN) { + return NaN; + } + + return sqrt1z().add(this.multiply(I)).log().multiply(I.negate()); + } + + /** + * Compute the + * + * inverse tangent of this complex number. + * Implements the formula: + *

+ * {@code atan(z) = (i/2) log((i + z)/(i - z))} + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN} or infinite.

+ * + * @return the inverse tangent of this complex number + * @since 1.2 + */ + public Complex atan() { + if (isNaN) { + return NaN; + } + + return this.add(I).divide(I.subtract(this)).log() + .multiply(I.divide(createComplex(2.0, 0.0))); + } + + /** + * Compute the + * + * cosine of this complex number. + * Implements the formula: + *

+ * {@code cos(a + bi) = cos(a)cosh(b) - sin(a)sinh(b)i} + *

+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, + * {@link FastMath#cosh} and {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result.

+ *
+     *  Examples:
+     *  
+     *   cos(1 ± INFINITY i) = 1 \u2213 INFINITY i
+     *   cos(±INFINITY + i) = NaN + NaN i
+     *   cos(±INFINITY ± INFINITY i) = NaN + NaN i
+     *  
+     * 
+ * + * @return the cosine of this complex number. + * @since 1.2 + */ + public Complex cos() { + if (isNaN) { + return NaN; + } + + return createComplex(FastMath.cos(real) * FastMath.cosh(imaginary), + -FastMath.sin(real) * FastMath.sinh(imaginary)); + } + + /** + * Compute the + * + * hyperbolic cosine of this complex number. + * Implements the formula: + *
+     *  
+     *   cosh(a + bi) = cosh(a)cos(b) + sinh(a)sin(b)i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, + * {@link FastMath#cosh} and {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   cosh(1 ± INFINITY i) = NaN + NaN i
+     *   cosh(±INFINITY + i) = INFINITY ± INFINITY i
+     *   cosh(±INFINITY ± INFINITY i) = NaN + NaN i
+     *  
+     * 
+ * + * @return the hyperbolic cosine of this complex number. + * @since 1.2 + */ + public Complex cosh() { + if (isNaN) { + return NaN; + } + + return createComplex(FastMath.cosh(real) * FastMath.cos(imaginary), + FastMath.sinh(real) * FastMath.sin(imaginary)); + } + + /** + * Compute the + * + * exponential function of this complex number. + * Implements the formula: + *
+     *  
+     *   exp(a + bi) = exp(a)cos(b) + exp(a)sin(b)i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#exp}, {@link FastMath#cos}, and + * {@link FastMath#sin}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   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
+     *  
+     * 
+ * + * @return ethis. + * @since 1.2 + */ + public Complex exp() { + if (isNaN) { + return NaN; + } + + double expReal = FastMath.exp(real); + return createComplex(expReal * FastMath.cos(imaginary), + expReal * FastMath.sin(imaginary)); + } + + /** + * Compute the + * + * natural logarithm of this complex number. + * Implements the formula: + *
+     *  
+     *   log(a + bi) = ln(|a + bi|) + arg(a + bi)i
+     *  
+     * 
+ * where ln on the right hand side is {@link FastMath#log}, + * {@code |a + bi|} is the modulus, {@link Complex#abs}, and + * {@code arg(a + bi) = }{@link FastMath#atan2}(b, a). + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite (or critical) values in real or imaginary parts of the input may + * result in infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   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
+     *  
+     * 
+ * + * @return the value ln   this, the natural logarithm + * of {@code this}. + * @since 1.2 + */ + public Complex log() { + if (isNaN) { + return NaN; + } + + return createComplex(FastMath.log(abs()), + FastMath.atan2(imaginary, real)); + } + + /** + * Returns of value of this complex number raised to the power of {@code x}. + * Implements the formula: + *
+     *  
+     *   yx = exp(x·log(y))
+     *  
+     * 
+ * where {@code exp} and {@code log} are {@link #exp} and + * {@link #log}, respectively. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN} or infinite, or if {@code y} + * equals {@link Complex#ZERO}.

+ * + * @param x exponent to which this {@code Complex} is to be raised. + * @return thisx. + * @throws NullArgumentException if x is {@code null}. + * @since 1.2 + */ + public Complex pow(Complex x) + throws NullArgumentException { + MathUtils.checkNotNull(x); + return this.log().multiply(x).exp(); + } + + /** + * Returns of value of this complex number raised to the power of {@code x}. + * + * @param x exponent to which this {@code Complex} is to be raised. + * @return thisx. + * @see #pow(Complex) + */ + public Complex pow(double x) { + return this.log().multiply(x).exp(); + } + + /** + * Compute the + * + * sine + * of this complex number. + * Implements the formula: + *
+     *  
+     *   sin(a + bi) = sin(a)cosh(b) - cos(a)sinh(b)i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, + * {@link FastMath#cosh} and {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or {@code NaN} values returned in parts of the result. + *

+     *  Examples:
+     *  
+     *   sin(1 ± INFINITY i) = 1 ± INFINITY i
+     *   sin(±INFINITY + i) = NaN + NaN i
+     *   sin(±INFINITY ± INFINITY i) = NaN + NaN i
+     *  
+     * 
+ * + * @return the sine of this complex number. + * @since 1.2 + */ + public Complex sin() { + if (isNaN) { + return NaN; + } + + return createComplex(FastMath.sin(real) * FastMath.cosh(imaginary), + FastMath.cos(real) * FastMath.sinh(imaginary)); + } + + /** + * Compute the + * + * hyperbolic sine of this complex number. + * Implements the formula: + *
+     *  
+     *   sinh(a + bi) = sinh(a)cos(b)) + cosh(a)sin(b)i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, + * {@link FastMath#cosh} and {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + *

+     *  Examples:
+     *  
+     *   sinh(1 ± INFINITY i) = NaN + NaN i
+     *   sinh(±INFINITY + i) = ± INFINITY + INFINITY i
+     *   sinh(±INFINITY ± INFINITY i) = NaN + NaN i
+     *  
+     * 
+ * + * @return the hyperbolic sine of {@code this}. + * @since 1.2 + */ + public Complex sinh() { + if (isNaN) { + return NaN; + } + + return createComplex(FastMath.sinh(real) * FastMath.cos(imaginary), + FastMath.cosh(real) * FastMath.sin(imaginary)); + } + + /** + * Compute the + * + * square root of this complex number. + * Implements the following algorithm to compute {@code sqrt(a + bi)}: + *
  1. Let {@code t = sqrt((|a| + |a + bi|) / 2)}
  2. + *
  3. if {@code  a ≥ 0} return {@code t + (b/2t)i}
    +     *  else return {@code |b|/2t + sign(b)t i }
  4. + *
+ * where
    + *
  • {@code |a| = }{@link FastMath#abs}(a)
  • + *
  • {@code |a + bi| = }{@link Complex#abs}(a + bi)
  • + *
  • {@code sign(b) = }{@link FastMath#copySign(double,double) copySign(1d, b)} + *
+ *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   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
+     *  
+     * 
+ * + * @return the square root of {@code this}. + * @since 1.2 + */ + public Complex sqrt() { + if (isNaN) { + return NaN; + } + + if (real == 0.0 && imaginary == 0.0) { + return createComplex(0.0, 0.0); + } + + double t = FastMath.sqrt((FastMath.abs(real) + abs()) / 2.0); + if (real >= 0.0) { + return createComplex(t, imaginary / (2.0 * t)); + } else { + return createComplex(FastMath.abs(imaginary) / (2.0 * t), + FastMath.copySign(1d, imaginary) * t); + } + } + + /** + * Compute the + * + * square root of 1 - this2 for this complex + * number. + * Computes the result directly as + * {@code sqrt(ONE.subtract(z.multiply(z)))}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + * + * @return the square root of 1 - this2. + * @since 1.2 + */ + public Complex sqrt1z() { + return createComplex(1.0, 0.0).subtract(this.multiply(this)).sqrt(); + } + + /** + * Compute the + * + * tangent of this complex number. + * Implements the formula: + *
+     *  
+     *   tan(a + bi) = sin(2a)/(cos(2a)+cosh(2b)) + [sinh(2b)/(cos(2a)+cosh(2b))]i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, {@link FastMath#cosh} and + * {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite (or critical) values in real or imaginary parts of the input may + * result in infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   tan(a ± INFINITY i) = 0 ± i
+     *   tan(±INFINITY + bi) = NaN + NaN i
+     *   tan(±INFINITY ± INFINITY i) = NaN + NaN i
+     *   tan(±π/2 + 0 i) = ±INFINITY + NaN i
+     *  
+     * 
+ * + * @return the tangent of {@code this}. + * @since 1.2 + */ + public Complex tan() { + if (isNaN || Double.isInfinite(real)) { + return NaN; + } + if (imaginary > 20.0) { + return createComplex(0.0, 1.0); + } + if (imaginary < -20.0) { + return createComplex(0.0, -1.0); + } + + double real2 = 2.0 * real; + double imaginary2 = 2.0 * imaginary; + double d = FastMath.cos(real2) + FastMath.cosh(imaginary2); + + return createComplex(FastMath.sin(real2) / d, + FastMath.sinh(imaginary2) / d); + } + + /** + * Compute the + * + * hyperbolic tangent of this complex number. + * Implements the formula: + *
+     *  
+     *   tan(a + bi) = sinh(2a)/(cosh(2a)+cos(2b)) + [sin(2b)/(cosh(2a)+cos(2b))]i
+     *  
+     * 
+ * where the (real) functions on the right-hand side are + * {@link FastMath#sin}, {@link FastMath#cos}, {@link FastMath#cosh} and + * {@link FastMath#sinh}. + *

+ * Returns {@link Complex#NaN} if either real or imaginary part of the + * input argument is {@code NaN}. + *

+ * Infinite values in real or imaginary parts of the input may result in + * infinite or NaN values returned in parts of the result. + *
+     *  Examples:
+     *  
+     *   tanh(a ± INFINITY i) = NaN + NaN i
+     *   tanh(±INFINITY + bi) = ±1 + 0 i
+     *   tanh(±INFINITY ± INFINITY i) = NaN + NaN i
+     *   tanh(0 + (π/2)i) = NaN + INFINITY i
+     *  
+     * 
+ * + * @return the hyperbolic tangent of {@code this}. + * @since 1.2 + */ + public Complex tanh() { + if (isNaN || Double.isInfinite(imaginary)) { + return NaN; + } + if (real > 20.0) { + return createComplex(1.0, 0.0); + } + if (real < -20.0) { + return createComplex(-1.0, 0.0); + } + double real2 = 2.0 * real; + double imaginary2 = 2.0 * imaginary; + double d = FastMath.cosh(real2) + FastMath.cos(imaginary2); + + return createComplex(FastMath.sinh(real2) / d, + FastMath.sin(imaginary2) / d); + } + + + + /** + * Compute the argument of this complex number. + * The argument is the angle phi between the positive real axis and + * the point representing this number in the complex plane. + * The value returned is between -PI (not inclusive) + * and PI (inclusive), with negative values returned for numbers with + * negative imaginary parts. + *

+ * If either real or imaginary part (or both) is NaN, NaN is returned. + * Infinite parts are handled as {@code Math.atan2} handles them, + * essentially treating finite parts as zero in the presence of an + * infinite coordinate and returning a multiple of pi/4 depending on + * the signs of the infinite parts. + * See the javadoc for {@code Math.atan2} for full details. + * + * @return the argument of {@code this}. + */ + public double getArgument() { + return FastMath.atan2(getImaginary(), getReal()); + } + + /** + * Computes the n-th roots of this complex number. + * The nth roots are defined by the formula: + *

+     *  
+     *   zk = abs1/n (cos(phi + 2πk/n) + i (sin(phi + 2πk/n))
+     *  
+     * 
+ * for {@code k=0, 1, ..., n-1}, where {@code abs} and {@code phi} + * are respectively the {@link #abs() modulus} and + * {@link #getArgument() argument} of this complex number. + *

+ * If one or both parts of this complex number is NaN, a list with just + * one element, {@link #NaN} is returned. + * if neither part is NaN, but at least one part is infinite, the result + * is a one-element list containing {@link #INF}. + * + * @param n Degree of root. + * @return a List of all {@code n}-th roots of {@code this}. + * @throws NotPositiveException if {@code n <= 0}. + * @since 2.0 + */ + public List nthRoot(int n) throws NotPositiveException { + + if (n <= 0) { + throw new NotPositiveException(LocalizedFormats.CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N, + n); + } + + final List result = new ArrayList(); + + if (isNaN) { + result.add(NaN); + return result; + } + if (isInfinite()) { + result.add(INF); + return result; + } + + // nth root of abs -- faster / more accurate to use a solver here? + final double nthRootOfAbs = FastMath.pow(abs(), 1.0 / n); + + // Compute nth roots of complex number with k = 0, 1, ... n-1 + final double nthPhi = getArgument() / n; + final double slice = 2 * FastMath.PI / n; + double innerPart = nthPhi; + for (int k = 0; k < n ; k++) { + // inner part + final double realPart = nthRootOfAbs * FastMath.cos(innerPart); + final double imaginaryPart = nthRootOfAbs * FastMath.sin(innerPart); + result.add(createComplex(realPart, imaginaryPart)); + innerPart += slice; + } + + return result; + } + + /** + * Create a complex number given the real and imaginary parts. + * + * @param realPart Real part. + * @param imaginaryPart Imaginary part. + * @return a new complex number instance. + * @since 1.2 + * @see #valueOf(double, double) + */ + protected Complex createComplex(double realPart, + double imaginaryPart) { + return new Complex(realPart, imaginaryPart); + } + + /** + * Create a complex number given the real and imaginary parts. + * + * @param realPart Real part. + * @param imaginaryPart Imaginary part. + * @return a Complex instance. + */ + public static Complex valueOf(double realPart, + double imaginaryPart) { + if (Double.isNaN(realPart) || + Double.isNaN(imaginaryPart)) { + return NaN; + } + return new Complex(realPart, imaginaryPart); + } + + /** + * Create a complex number given only the real part. + * + * @param realPart Real part. + * @return a Complex instance. + */ + public static Complex valueOf(double realPart) { + if (Double.isNaN(realPart)) { + return NaN; + } + return new Complex(realPart); + } + + /** + * Resolve the transient fields in a deserialized Complex Object. + * Subclasses will need to override {@link #createComplex} to + * deserialize properly. + * + * @return A Complex instance with all fields resolved. + * @since 2.0 + */ + protected final Object readResolve() { + return createComplex(real, imaginary); + } + + /** {@inheritDoc} */ + public ComplexField getField() { + return ComplexField.getInstance(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "(" + real + ", " + imaginary + ")"; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexField.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexField.java new file mode 100644 index 000000000..89cab108f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexField.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 com.fr.third.org.apache.commons.math3.complex; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Representation of the complex numbers field. + *

+ * This class is a singleton. + *

+ * @see Complex + * @since 2.0 + */ +public class ComplexField implements Field, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -6130362688700788798L; + + /** Private constructor for the singleton. + */ + private ComplexField() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static ComplexField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public Complex getOne() { + return Complex.ONE; + } + + /** {@inheritDoc} */ + public Complex getZero() { + return Complex.ZERO; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return Complex.class; + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

We use here the Initialization On Demand Holder Idiom.

+ */ + private static class LazyHolder { + /** Cached field instance. */ + private static final ComplexField INSTANCE = new ComplexField(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexFormat.java new file mode 100644 index 000000000..489a72e52 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexFormat.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.complex; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a Complex number in cartesian format "Re(c) + Im(c)i". 'i' can + * be replaced with 'j' (or anything else), and the number format for both real + * and imaginary parts can be configured. + * + */ +public class ComplexFormat { + + /** The default imaginary character. */ + private static final String DEFAULT_IMAGINARY_CHARACTER = "i"; + /** The notation used to signify the imaginary part of the complex number. */ + private final String imaginaryCharacter; + /** The format used for the imaginary part. */ + private final NumberFormat imaginaryFormat; + /** The format used for the real part. */ + private final NumberFormat realFormat; + + /** + * Create an instance with the default imaginary character, 'i', and the + * default number format for both real and imaginary parts. + */ + public ComplexFormat() { + this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER; + this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat(); + this.realFormat = imaginaryFormat; + } + + /** + * Create an instance with a custom number format for both real and + * imaginary parts. + * @param format the custom format for both real and imaginary parts. + * @throws NullArgumentException if {@code realFormat} is {@code null}. + */ + public ComplexFormat(NumberFormat format) throws NullArgumentException { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT); + } + this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER; + this.imaginaryFormat = format; + this.realFormat = format; + } + + /** + * Create an instance with a custom number format for the real part and a + * custom number format for the imaginary part. + * @param realFormat the custom format for the real part. + * @param imaginaryFormat the custom format for the imaginary part. + * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}. + * @throws NullArgumentException if {@code realFormat} is {@code null}. + */ + public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat) + throws NullArgumentException { + if (imaginaryFormat == null) { + throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT); + } + if (realFormat == null) { + throw new NullArgumentException(LocalizedFormats.REAL_FORMAT); + } + + this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER; + this.imaginaryFormat = imaginaryFormat; + this.realFormat = realFormat; + } + + /** + * Create an instance with a custom imaginary character, and the default + * number format for both real and imaginary parts. + * @param imaginaryCharacter The custom imaginary character. + * @throws NullArgumentException if {@code imaginaryCharacter} is + * {@code null}. + * @throws NoDataException if {@code imaginaryCharacter} is an + * empty string. + */ + public ComplexFormat(String imaginaryCharacter) + throws NullArgumentException, NoDataException { + this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom imaginary character, and a custom number + * format for both real and imaginary parts. + * @param imaginaryCharacter The custom imaginary character. + * @param format the custom format for both real and imaginary parts. + * @throws NullArgumentException if {@code imaginaryCharacter} is + * {@code null}. + * @throws NoDataException if {@code imaginaryCharacter} is an + * empty string. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public ComplexFormat(String imaginaryCharacter, NumberFormat format) + throws NullArgumentException, NoDataException { + this(imaginaryCharacter, format, format); + } + + /** + * Create an instance with a custom imaginary character, a custom number + * format for the real part, and a custom number format for the imaginary + * part. + * + * @param imaginaryCharacter The custom imaginary character. + * @param realFormat the custom format for the real part. + * @param imaginaryFormat the custom format for the imaginary part. + * @throws NullArgumentException if {@code imaginaryCharacter} is + * {@code null}. + * @throws NoDataException if {@code imaginaryCharacter} is an + * empty string. + * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}. + * @throws NullArgumentException if {@code realFormat} is {@code null}. + */ + public ComplexFormat(String imaginaryCharacter, + NumberFormat realFormat, + NumberFormat imaginaryFormat) + throws NullArgumentException, NoDataException { + if (imaginaryCharacter == null) { + throw new NullArgumentException(); + } + if (imaginaryCharacter.length() == 0) { + throw new NoDataException(); + } + if (imaginaryFormat == null) { + throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT); + } + if (realFormat == null) { + throw new NullArgumentException(LocalizedFormats.REAL_FORMAT); + } + + this.imaginaryCharacter = imaginaryCharacter; + this.imaginaryFormat = imaginaryFormat; + this.realFormat = realFormat; + } + + /** + * Get the set of locales for which complex formats are available. + *

This is the same set as the {@link NumberFormat} set.

+ * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * This method calls {@link #format(Object,StringBuffer,FieldPosition)}. + * + * @param c Complex object to format. + * @return A formatted number in the form "Re(c) + Im(c)i". + */ + public String format(Complex c) { + return format(c, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * This method calls {@link #format(Object,StringBuffer,FieldPosition)}. + * + * @param c Double object to format. + * @return A formatted number. + */ + public String format(Double c) { + return format(new Complex(c, 0), new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats a {@link Complex} object to produce a string. + * + * @param complex the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(Complex complex, StringBuffer toAppendTo, + FieldPosition pos) { + pos.setBeginIndex(0); + pos.setEndIndex(0); + + // format real + double re = complex.getReal(); + CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos); + + // format sign and imaginary + double im = complex.getImaginary(); + StringBuffer imAppendTo; + if (im < 0.0) { + toAppendTo.append(" - "); + imAppendTo = formatImaginary(-im, new StringBuffer(), pos); + toAppendTo.append(imAppendTo); + toAppendTo.append(getImaginaryCharacter()); + } else if (im > 0.0 || Double.isNaN(im)) { + toAppendTo.append(" + "); + imAppendTo = formatImaginary(im, new StringBuffer(), pos); + toAppendTo.append(imAppendTo); + toAppendTo.append(getImaginaryCharacter()); + } + + return toAppendTo; + } + + /** + * Format the absolute value of the imaginary part. + * + * @param absIm Absolute value of the imaginary part of a complex number. + * @param toAppendTo where the text is to be appended. + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field. + * @return the value passed in as toAppendTo. + */ + private StringBuffer formatImaginary(double absIm, + StringBuffer toAppendTo, + FieldPosition pos) { + pos.setBeginIndex(0); + pos.setEndIndex(0); + + CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos); + if (toAppendTo.toString().equals("1")) { + // Remove the character "1" if it is the only one. + toAppendTo.setLength(0); + } + + return toAppendTo; + } + + /** + * Formats a object to produce a string. {@code obj} must be either a + * {@link Complex} object or a {@link Number} object. Any other type of + * object will result in an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) + * @throws MathIllegalArgumentException is {@code obj} is not a valid type. + */ + public StringBuffer format(Object obj, StringBuffer toAppendTo, + FieldPosition pos) + throws MathIllegalArgumentException { + + StringBuffer ret = null; + + if (obj instanceof Complex) { + ret = format( (Complex)obj, toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new Complex(((Number)obj).doubleValue(), 0.0), + toAppendTo, pos); + } else { + throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX, + obj.getClass().getName()); + } + + return ret; + } + + /** + * Access the imaginaryCharacter. + * @return the imaginaryCharacter. + */ + public String getImaginaryCharacter() { + return imaginaryCharacter; + } + + /** + * Access the imaginaryFormat. + * @return the imaginaryFormat. + */ + public NumberFormat getImaginaryFormat() { + return imaginaryFormat; + } + + /** + * Returns the default complex format for the current locale. + * @return the default complex format. + */ + public static ComplexFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static ComplexFormat getInstance(Locale locale) { + NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale); + return new ComplexFormat(f); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @param imaginaryCharacter Imaginary character. + * @return the complex format specific to the given locale. + * @throws NullArgumentException if {@code imaginaryCharacter} is + * {@code null}. + * @throws NoDataException if {@code imaginaryCharacter} is an + * empty string. + */ + public static ComplexFormat getInstance(String imaginaryCharacter, Locale locale) + throws NullArgumentException, NoDataException { + NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale); + return new ComplexFormat(imaginaryCharacter, f); + } + + /** + * Access the realFormat. + * @return the realFormat. + */ + public NumberFormat getRealFormat() { + return realFormat; + } + + /** + * Parses a string to produce a {@link Complex} object. + * + * @param source the string to parse. + * @return the parsed {@link Complex} object. + * @throws MathParseException if the beginning of the specified string + * cannot be parsed. + */ + public Complex parse(String source) throws MathParseException { + ParsePosition parsePosition = new ParsePosition(0); + Complex result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + Complex.class); + } + return result; + } + + /** + * Parses a string to produce a {@link Complex} object. + * + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link Complex} object. + */ + public Complex parse(String source, ParsePosition pos) { + int initialIndex = pos.getIndex(); + + // parse whitespace + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + + // parse real + Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos); + if (re == null) { + // invalid real number + // set index back to initial, error index should already be set + pos.setIndex(initialIndex); + return null; + } + + // parse sign + int startIndex = pos.getIndex(); + char c = CompositeFormat.parseNextCharacter(source, pos); + int sign = 0; + switch (c) { + case 0 : + // no sign + // return real only complex number + return new Complex(re.doubleValue(), 0.0); + case '-' : + sign = -1; + break; + case '+' : + sign = 1; + break; + default : + // invalid sign + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + + // parse imaginary + Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos); + if (im == null) { + // invalid imaginary number + // set index back to initial, error index should already be set + pos.setIndex(initialIndex); + return null; + } + + // parse imaginary character + if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) { + return null; + } + + return new Complex(re.doubleValue(), im.doubleValue() * sign); + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexUtils.java new file mode 100644 index 000000000..a63824632 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/ComplexUtils.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 com.fr.third.org.apache.commons.math3.complex; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Static implementations of common + * {@link Complex} utilities functions. + * + */ +public class ComplexUtils { + + /** + * Default constructor. + */ + private ComplexUtils() {} + + /** + * Creates a complex number from the given polar representation. + *

+ * The value returned is r·ei·theta, + * computed as r·cos(theta) + r·sin(theta)i

+ *

+ * If either r or theta is NaN, or + * theta is infinite, {@link Complex#NaN} is returned.

+ *

+ * If r is infinite and theta is finite, + * infinite or NaN values may be returned in parts of the result, following + * the rules for double arithmetic.

+     * Examples:
+     * 
+     * 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 

+ * + * @param r the modulus of the complex number to create + * @param theta the argument of the complex number to create + * @return r·ei·theta + * @throws MathIllegalArgumentException if {@code r} is negative. + * @since 1.1 + */ + public static Complex polar2Complex(double r, double theta) throws MathIllegalArgumentException { + if (r < 0) { + throw new MathIllegalArgumentException( + LocalizedFormats.NEGATIVE_COMPLEX_MODULE, r); + } + return new Complex(r * FastMath.cos(theta), r * FastMath.sin(theta)); + } + + /** + * Convert an array of primitive doubles to an array of {@code Complex} objects. + * + * @param real Array of numbers to be converted to their {@code Complex} + * equivalent. + * @return an array of {@code Complex} objects. + * + * @since 3.1 + */ + public static Complex[] convertToComplex(double[] real) { + final Complex c[] = new Complex[real.length]; + for (int i = 0; i < real.length; i++) { + c[i] = new Complex(real[i], 0); + } + + return c; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Quaternion.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Quaternion.java new file mode 100644 index 000000000..f32b8f33b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/Quaternion.java @@ -0,0 +1,465 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.complex; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class implements + * quaternions (Hamilton's hypercomplex numbers). + *
+ * Instance of this class are guaranteed to be immutable. + * + * @since 3.1 + */ +public final class Quaternion implements Serializable { + /** Identity quaternion. */ + public static final Quaternion IDENTITY = new Quaternion(1, 0, 0, 0); + /** Zero quaternion. */ + public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); + /** i */ + public static final Quaternion I = new Quaternion(0, 1, 0, 0); + /** j */ + public static final Quaternion J = new Quaternion(0, 0, 1, 0); + /** k */ + public static final Quaternion K = new Quaternion(0, 0, 0, 1); + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20092012L; + + /** First component (scalar part). */ + private final double q0; + /** Second component (first vector part). */ + private final double q1; + /** Third component (second vector part). */ + private final double q2; + /** Fourth component (third vector part). */ + private final double q3; + + /** + * Builds a quaternion from its components. + * + * @param a Scalar component. + * @param b First vector component. + * @param c Second vector component. + * @param d Third vector component. + */ + public Quaternion(final double a, + final double b, + final double c, + final double d) { + this.q0 = a; + this.q1 = b; + this.q2 = c; + this.q3 = d; + } + + /** + * Builds a quaternion from scalar and vector parts. + * + * @param scalar Scalar part of the quaternion. + * @param v Components of the vector part of the quaternion. + * + * @throws DimensionMismatchException if the array length is not 3. + */ + public Quaternion(final double scalar, + final double[] v) + throws DimensionMismatchException { + if (v.length != 3) { + throw new DimensionMismatchException(v.length, 3); + } + this.q0 = scalar; + this.q1 = v[0]; + this.q2 = v[1]; + this.q3 = v[2]; + } + + /** + * Builds a pure quaternion from a vector (assuming that the scalar + * part is zero). + * + * @param v Components of the vector part of the pure quaternion. + */ + public Quaternion(final double[] v) { + this(0, v); + } + + /** + * Returns the conjugate quaternion of the instance. + * + * @return the conjugate quaternion + */ + public Quaternion getConjugate() { + return new Quaternion(q0, -q1, -q2, -q3); + } + + /** + * Returns the Hamilton product of two quaternions. + * + * @param q1 First quaternion. + * @param q2 Second quaternion. + * @return the product {@code q1} and {@code q2}, in that order. + */ + public static Quaternion multiply(final Quaternion q1, final Quaternion q2) { + // Components of the first quaternion. + final double q1a = q1.getQ0(); + final double q1b = q1.getQ1(); + final double q1c = q1.getQ2(); + final double q1d = q1.getQ3(); + + // Components of the second quaternion. + final double q2a = q2.getQ0(); + final double q2b = q2.getQ1(); + final double q2c = q2.getQ2(); + final double q2d = q2.getQ3(); + + // Components of the product. + final double w = q1a * q2a - q1b * q2b - q1c * q2c - q1d * q2d; + final double x = q1a * q2b + q1b * q2a + q1c * q2d - q1d * q2c; + final double y = q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b; + final double z = q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a; + + return new Quaternion(w, x, y, z); + } + + /** + * Returns the Hamilton product of the instance by a quaternion. + * + * @param q Quaternion. + * @return the product of this instance with {@code q}, in that order. + */ + public Quaternion multiply(final Quaternion q) { + return multiply(this, q); + } + + /** + * Computes the sum of two quaternions. + * + * @param q1 Quaternion. + * @param q2 Quaternion. + * @return the sum of {@code q1} and {@code q2}. + */ + public static Quaternion add(final Quaternion q1, + final Quaternion q2) { + return new Quaternion(q1.getQ0() + q2.getQ0(), + q1.getQ1() + q2.getQ1(), + q1.getQ2() + q2.getQ2(), + q1.getQ3() + q2.getQ3()); + } + + /** + * Computes the sum of the instance and another quaternion. + * + * @param q Quaternion. + * @return the sum of this instance and {@code q} + */ + public Quaternion add(final Quaternion q) { + return add(this, q); + } + + /** + * Subtracts two quaternions. + * + * @param q1 First Quaternion. + * @param q2 Second quaternion. + * @return the difference between {@code q1} and {@code q2}. + */ + public static Quaternion subtract(final Quaternion q1, + final Quaternion q2) { + return new Quaternion(q1.getQ0() - q2.getQ0(), + q1.getQ1() - q2.getQ1(), + q1.getQ2() - q2.getQ2(), + q1.getQ3() - q2.getQ3()); + } + + /** + * Subtracts a quaternion from the instance. + * + * @param q Quaternion. + * @return the difference between this instance and {@code q}. + */ + public Quaternion subtract(final Quaternion q) { + return subtract(this, q); + } + + /** + * Computes the dot-product of two quaternions. + * + * @param q1 Quaternion. + * @param q2 Quaternion. + * @return the dot product of {@code q1} and {@code q2}. + */ + public static double dotProduct(final Quaternion q1, + final Quaternion q2) { + return q1.getQ0() * q2.getQ0() + + q1.getQ1() * q2.getQ1() + + q1.getQ2() * q2.getQ2() + + q1.getQ3() * q2.getQ3(); + } + + /** + * Computes the dot-product of the instance by a quaternion. + * + * @param q Quaternion. + * @return the dot product of this instance and {@code q}. + */ + public double dotProduct(final Quaternion q) { + return dotProduct(this, q); + } + + /** + * Computes the norm of the quaternion. + * + * @return the norm. + */ + public double getNorm() { + return FastMath.sqrt(q0 * q0 + + q1 * q1 + + q2 * q2 + + q3 * q3); + } + + /** + * Computes the normalized quaternion (the versor of the instance). + * The norm of the quaternion must not be zero. + * + * @return a normalized quaternion. + * @throws ZeroException if the norm of the quaternion is zero. + */ + public Quaternion normalize() { + final double norm = getNorm(); + + if (norm < Precision.SAFE_MIN) { + throw new ZeroException(LocalizedFormats.NORM, norm); + } + + return new Quaternion(q0 / norm, + q1 / norm, + q2 / norm, + q3 / norm); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Quaternion) { + final Quaternion q = (Quaternion) other; + return q0 == q.getQ0() && + q1 == q.getQ1() && + q2 == q.getQ2() && + q3 == q.getQ3(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // "Effective Java" (second edition, p. 47). + int result = 17; + for (double comp : new double[] { q0, q1, q2, q3 }) { + final int c = MathUtils.hash(comp); + result = 31 * result + c; + } + return result; + } + + /** + * Checks whether this instance is equal to another quaternion + * within a given tolerance. + * + * @param q Quaternion with which to compare the current quaternion. + * @param eps Tolerance. + * @return {@code true} if the each of the components are equal + * within the allowed absolute error. + */ + public boolean equals(final Quaternion q, + final double eps) { + return Precision.equals(q0, q.getQ0(), eps) && + Precision.equals(q1, q.getQ1(), eps) && + Precision.equals(q2, q.getQ2(), eps) && + Precision.equals(q3, q.getQ3(), eps); + } + + /** + * Checks whether the instance is a unit quaternion within a given + * tolerance. + * + * @param eps Tolerance (absolute error). + * @return {@code true} if the norm is 1 within the given tolerance, + * {@code false} otherwise + */ + public boolean isUnitQuaternion(double eps) { + return Precision.equals(getNorm(), 1d, eps); + } + + /** + * Checks whether the instance is a pure quaternion within a given + * tolerance. + * + * @param eps Tolerance (absolute error). + * @return {@code true} if the scalar part of the quaternion is zero. + */ + public boolean isPureQuaternion(double eps) { + return FastMath.abs(getQ0()) <= eps; + } + + /** + * Returns the polar form of the quaternion. + * + * @return the unit quaternion with positive scalar part. + */ + public Quaternion getPositivePolarForm() { + if (getQ0() < 0) { + final Quaternion unitQ = normalize(); + // The quaternion of rotation (normalized quaternion) q and -q + // are equivalent (i.e. represent the same rotation). + return new Quaternion(-unitQ.getQ0(), + -unitQ.getQ1(), + -unitQ.getQ2(), + -unitQ.getQ3()); + } else { + return this.normalize(); + } + } + + /** + * Returns the inverse of this instance. + * The norm of the quaternion must not be zero. + * + * @return the inverse. + * @throws ZeroException if the norm (squared) of the quaternion is zero. + */ + public Quaternion getInverse() { + final double squareNorm = q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3; + if (squareNorm < Precision.SAFE_MIN) { + throw new ZeroException(LocalizedFormats.NORM, squareNorm); + } + + return new Quaternion(q0 / squareNorm, + -q1 / squareNorm, + -q2 / squareNorm, + -q3 / squareNorm); + } + + /** + * Gets the first component of the quaternion (scalar part). + * + * @return the scalar part. + */ + public double getQ0() { + return q0; + } + + /** + * Gets the second component of the quaternion (first component + * of the vector part). + * + * @return the first component of the vector part. + */ + public double getQ1() { + return q1; + } + + /** + * Gets the third component of the quaternion (second component + * of the vector part). + * + * @return the second component of the vector part. + */ + public double getQ2() { + return q2; + } + + /** + * Gets the fourth component of the quaternion (third component + * of the vector part). + * + * @return the third component of the vector part. + */ + public double getQ3() { + return q3; + } + + /** + * Gets the scalar part of the quaternion. + * + * @return the scalar part. + * @see #getQ0() + */ + public double getScalarPart() { + return getQ0(); + } + + /** + * Gets the three components of the vector part of the quaternion. + * + * @return the vector part. + * @see #getQ1() + * @see #getQ2() + * @see #getQ3() + */ + public double[] getVectorPart() { + return new double[] { getQ1(), getQ2(), getQ3() }; + } + + /** + * Multiplies the instance by a scalar. + * + * @param alpha Scalar factor. + * @return a scaled quaternion. + */ + public Quaternion multiply(final double alpha) { + return new Quaternion(alpha * q0, + alpha * q1, + alpha * q2, + alpha * q3); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + final String sp = " "; + final StringBuilder s = new StringBuilder(); + s.append("[") + .append(q0).append(sp) + .append(q1).append(sp) + .append(q2).append(sp) + .append(q3) + .append("]"); + + return s.toString(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/RootsOfUnity.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/RootsOfUnity.java new file mode 100644 index 000000000..b3d38ee0b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/RootsOfUnity.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 com.fr.third.org.apache.commons.math3.complex; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * A helper class for the computation and caching of the {@code n}-th roots of + * unity. + * + * @since 3.0 + */ +public class RootsOfUnity implements Serializable { + + /** Serializable version id. */ + private static final long serialVersionUID = 20120201L; + + /** Number of roots of unity. */ + private int omegaCount; + + /** Real part of the roots. */ + private double[] omegaReal; + + /** + * Imaginary part of the {@code n}-th roots of unity, for positive values + * of {@code n}. In this array, the roots are stored in counter-clockwise + * order. + */ + private double[] omegaImaginaryCounterClockwise; + + /** + * Imaginary part of the {@code n}-th roots of unity, for negative values + * of {@code n}. In this array, the roots are stored in clockwise order. + */ + private double[] omegaImaginaryClockwise; + + /** + * {@code true} if {@link #computeRoots(int)} was called with a positive + * value of its argument {@code n}. In this case, counter-clockwise ordering + * of the roots of unity should be used. + */ + private boolean isCounterClockWise; + + /** + * Build an engine for computing the {@code n}-th roots of unity. + */ + public RootsOfUnity() { + + omegaCount = 0; + omegaReal = null; + omegaImaginaryCounterClockwise = null; + omegaImaginaryClockwise = null; + isCounterClockWise = true; + } + + /** + * Returns {@code true} if {@link #computeRoots(int)} was called with a + * positive value of its argument {@code n}. If {@code true}, then + * counter-clockwise ordering of the roots of unity should be used. + * + * @return {@code true} if the roots of unity are stored in + * counter-clockwise order + * @throws MathIllegalStateException if no roots of unity have been computed + * yet + */ + public synchronized boolean isCounterClockWise() + throws MathIllegalStateException { + + if (omegaCount == 0) { + throw new MathIllegalStateException( + LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); + } + return isCounterClockWise; + } + + /** + *

+ * Computes the {@code n}-th roots of unity. The roots are stored in + * {@code omega[]}, such that {@code omega[k] = w ^ k}, where + * {@code k = 0, ..., n - 1}, {@code w = exp(2 * pi * i / n)} and + * {@code i = sqrt(-1)}. + *

+ *

+ * Note that {@code n} can be positive of negative + *

+ *
    + *
  • {@code abs(n)} is always the number of roots of unity.
  • + *
  • If {@code n > 0}, then the roots are stored in counter-clockwise order.
  • + *
  • If {@code n < 0}, then the roots are stored in clockwise order.

    + *
+ * + * @param n the (signed) number of roots of unity to be computed + * @throws ZeroException if {@code n = 0} + */ + public synchronized void computeRoots(int n) throws ZeroException { + + if (n == 0) { + throw new ZeroException( + LocalizedFormats.CANNOT_COMPUTE_0TH_ROOT_OF_UNITY); + } + + isCounterClockWise = n > 0; + + // avoid repetitive calculations + final int absN = FastMath.abs(n); + + if (absN == omegaCount) { + return; + } + + // calculate everything from scratch + final double t = 2.0 * FastMath.PI / absN; + final double cosT = FastMath.cos(t); + final double sinT = FastMath.sin(t); + omegaReal = new double[absN]; + omegaImaginaryCounterClockwise = new double[absN]; + omegaImaginaryClockwise = new double[absN]; + omegaReal[0] = 1.0; + omegaImaginaryCounterClockwise[0] = 0.0; + omegaImaginaryClockwise[0] = 0.0; + for (int i = 1; i < absN; i++) { + omegaReal[i] = omegaReal[i - 1] * cosT - + omegaImaginaryCounterClockwise[i - 1] * sinT; + omegaImaginaryCounterClockwise[i] = omegaReal[i - 1] * sinT + + omegaImaginaryCounterClockwise[i - 1] * cosT; + omegaImaginaryClockwise[i] = -omegaImaginaryCounterClockwise[i]; + } + omegaCount = absN; + } + + /** + * Get the real part of the {@code k}-th {@code n}-th root of unity. + * + * @param k index of the {@code n}-th root of unity + * @return real part of the {@code k}-th {@code n}-th root of unity + * @throws MathIllegalStateException if no roots of unity have been + * computed yet + * @throws MathIllegalArgumentException if {@code k} is out of range + */ + public synchronized double getReal(int k) + throws MathIllegalStateException, MathIllegalArgumentException { + + if (omegaCount == 0) { + throw new MathIllegalStateException( + LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); + } + if ((k < 0) || (k >= omegaCount)) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, + Integer.valueOf(k), + Integer.valueOf(0), + Integer.valueOf(omegaCount - 1)); + } + + return omegaReal[k]; + } + + /** + * Get the imaginary part of the {@code k}-th {@code n}-th root of unity. + * + * @param k index of the {@code n}-th root of unity + * @return imaginary part of the {@code k}-th {@code n}-th root of unity + * @throws MathIllegalStateException if no roots of unity have been + * computed yet + * @throws OutOfRangeException if {@code k} is out of range + */ + public synchronized double getImaginary(int k) + throws MathIllegalStateException, OutOfRangeException { + + if (omegaCount == 0) { + throw new MathIllegalStateException( + LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); + } + if ((k < 0) || (k >= omegaCount)) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, + Integer.valueOf(k), + Integer.valueOf(0), + Integer.valueOf(omegaCount - 1)); + } + + return isCounterClockWise ? omegaImaginaryCounterClockwise[k] : + omegaImaginaryClockwise[k]; + } + + /** + * Returns the number of roots of unity currently stored. If + * {@link #computeRoots(int)} was called with {@code n}, then this method + * returns {@code abs(n)}. If no roots of unity have been computed yet, this + * method returns 0. + * + * @return the number of roots of unity currently stored + */ + public synchronized int getNumberOfRoots() { + return omegaCount; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/package-info.java new file mode 100644 index 000000000..2eacb1fd5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/complex/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Complex number type and implementations of complex transcendental + * functions. + * + */ +package com.fr.third.org.apache.commons.math3.complex; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.java new file mode 100644 index 000000000..5f1293966 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.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 com.fr.third.org.apache.commons.math3.dfp; + + +import com.fr.third.org.apache.commons.math3.analysis.RealFieldUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.AllowedSolution; +import com.fr.third.org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This class implements a modification of the Brent algorithm. + *

+ * The changes with respect to the original Brent algorithm are: + *

    + *
  • the returned value is chosen in the current interval according + * to user specified {@link AllowedSolution},
  • + *
  • the maximal order for the invert polynomial root search is + * user-specified instead of being invert quadratic only
  • + *
+ *

+ * The given interval must bracket the root. + * @deprecated as of 3.6 replaced with {@link FieldBracketingNthOrderBrentSolver} + */ +@Deprecated +public class BracketingNthOrderBrentSolverDFP extends FieldBracketingNthOrderBrentSolver { + + /** + * Construct a solver. + * + * @param relativeAccuracy Relative accuracy. + * @param absoluteAccuracy Absolute accuracy. + * @param functionValueAccuracy Function value accuracy. + * @param maximalOrder maximal order. + * @exception NumberIsTooSmallException if maximal order is lower than 2 + */ + public BracketingNthOrderBrentSolverDFP(final Dfp relativeAccuracy, + final Dfp absoluteAccuracy, + final Dfp functionValueAccuracy, + final int maximalOrder) + throws NumberIsTooSmallException { + super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, maximalOrder); + } + + /** + * Get the absolute accuracy. + * @return absolute accuracy + */ + @Override + public Dfp getAbsoluteAccuracy() { + return super.getAbsoluteAccuracy(); + } + + /** + * Get the relative accuracy. + * @return relative accuracy + */ + @Override + public Dfp getRelativeAccuracy() { + return super.getRelativeAccuracy(); + } + + /** + * Get the function accuracy. + * @return function accuracy + */ + @Override + public Dfp getFunctionValueAccuracy() { + return super.getFunctionValueAccuracy(); + } + + /** + * Solve for a zero in the given interval. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return a value where the function is zero. + * @exception NullArgumentException if f is null. + * @exception NoBracketingException if root cannot be bracketed + */ + public Dfp solve(final int maxEval, final UnivariateDfpFunction f, + final Dfp min, final Dfp max, final AllowedSolution allowedSolution) + throws NullArgumentException, NoBracketingException { + return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution); + } + + /** + * Solve for a zero in the given interval, start at {@code startValue}. + * A solver may require that the interval brackets a single zero root. + * Solvers that do require bracketing should be able to handle the case + * where one of the endpoints is itself a root. + * + * @param maxEval Maximum number of evaluations. + * @param f Function to solve. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param allowedSolution The kind of solutions that the root-finding algorithm may + * accept as solutions. + * @return a value where the function is zero. + * @exception NullArgumentException if f is null. + * @exception NoBracketingException if root cannot be bracketed + */ + public Dfp solve(final int maxEval, final UnivariateDfpFunction f, + final Dfp min, final Dfp max, final Dfp startValue, + final AllowedSolution allowedSolution) + throws NullArgumentException, NoBracketingException { + + // checks + MathUtils.checkNotNull(f); + + // wrap the function + RealFieldUnivariateFunction fieldF = new RealFieldUnivariateFunction() { + + /** {@inheritDoc} */ + public Dfp value(final Dfp x) { + return f.value(x); + } + }; + + // delegate to general field solver + return solve(maxEval, fieldF, min, max, startValue, allowedSolution); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/Dfp.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/Dfp.java new file mode 100644 index 000000000..26293075c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/Dfp.java @@ -0,0 +1,2883 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.dfp; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * Decimal floating point library for Java + * + *

Another floating point class. This one is built using radix 10000 + * which is 104, so its almost decimal.

+ * + *

The design goals here are: + *

    + *
  1. Decimal math, or close to it
  2. + *
  3. Settable precision (but no mix between numbers using different settings)
  4. + *
  5. Portability. Code should be kept as portable as possible.
  6. + *
  7. Performance
  8. + *
  9. Accuracy - Results should always be +/- 1 ULP for basic + * algebraic operation
  10. + *
  11. Comply with IEEE 854-1987 as much as possible. + * (See IEEE 854-1987 notes below)
  12. + *

+ * + *

Trade offs: + *

    + *
  1. Memory foot print. I'm using more memory than necessary to + * represent numbers to get better performance.
  2. + *
  3. 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.
  4. + *

+ * + *

Numbers are represented in the following form: + *

+ *  n  =  sign × mant × (radix)exp;

+ *
+ * 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

+ * + *

IEEE 854-1987 Notes and differences

+ * + *

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.

+ * + *

The radix of 10000 was chosen because it should be faster to operate + * on 4 decimal digits at once instead of one at a time. Radix 10 behavior + * can be realized by adding an additional rounding step to ensure that + * the number of decimal digits represented is constant.

+ * + *

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.

+ * + *

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.

+ * + *

IEEE 854 defines that the implied radix point lies just to the right + * of the most significant digit and to the left of the remaining digits. + * This implementation puts the implied radix point to the left of all + * digits including the most significant one. The most significant digit + * here is the one just to the right of the radix point. This is a fine + * detail and is really only a matter of definition. Any side effects of + * this can be rendered invisible by a subclass.

+ * @see DfpField + * @since 2.2 + */ +public class Dfp implements RealFieldElement { + + /** The radix, or base of this system. Set to 10000 */ + public static final int RADIX = 10000; + + /** The minimum exponent before underflow is signaled. Flush to zero + * occurs at minExp-DIGITS */ + public static final int MIN_EXP = -32767; + + /** The maximum exponent before overflow is signaled and results flushed + * to infinity */ + public static final int MAX_EXP = 32768; + + /** The amount under/overflows are scaled by before going to trap handler */ + public static final int ERR_SCALE = 32760; + + /** Indicator value for normal finite numbers. */ + public static final byte FINITE = 0; + + /** Indicator value for Infinity. */ + public static final byte INFINITE = 1; + + /** Indicator value for signaling NaN. */ + public static final byte SNAN = 2; + + /** Indicator value for quiet NaN. */ + public static final byte QNAN = 3; + + /** String for NaN representation. */ + private static final String NAN_STRING = "NaN"; + + /** String for positive infinity representation. */ + private static final String POS_INFINITY_STRING = "Infinity"; + + /** String for negative infinity representation. */ + private static final String NEG_INFINITY_STRING = "-Infinity"; + + /** Name for traps triggered by addition. */ + private static final String ADD_TRAP = "add"; + + /** Name for traps triggered by multiplication. */ + private static final String MULTIPLY_TRAP = "multiply"; + + /** Name for traps triggered by division. */ + private static final String DIVIDE_TRAP = "divide"; + + /** Name for traps triggered by square root. */ + private static final String SQRT_TRAP = "sqrt"; + + /** Name for traps triggered by alignment. */ + private static final String ALIGN_TRAP = "align"; + + /** Name for traps triggered by truncation. */ + private static final String TRUNC_TRAP = "trunc"; + + /** Name for traps triggered by nextAfter. */ + private static final String NEXT_AFTER_TRAP = "nextAfter"; + + /** Name for traps triggered by lessThan. */ + private static final String LESS_THAN_TRAP = "lessThan"; + + /** Name for traps triggered by greaterThan. */ + private static final String GREATER_THAN_TRAP = "greaterThan"; + + /** Name for traps triggered by newInstance. */ + private static final String NEW_INSTANCE_TRAP = "newInstance"; + + /** Mantissa. */ + protected int[] mant; + + /** Sign bit: 1 for positive, -1 for negative. */ + protected byte sign; + + /** Exponent. */ + protected int exp; + + /** Indicator for non-finite / non-number values. */ + protected byte nans; + + /** Factory building similar Dfp's. */ + private final DfpField field; + + /** Makes an instance with a value of zero. + * @param field field to which this instance belongs + */ + protected Dfp(final DfpField field) { + mant = new int[field.getRadixDigits()]; + sign = 1; + exp = 0; + nans = FINITE; + this.field = field; + } + + /** Create an instance from a byte value. + * @param field field to which this instance belongs + * @param x value to convert to an instance + */ + protected Dfp(final DfpField field, byte x) { + this(field, (long) x); + } + + /** Create an instance from an int value. + * @param field field to which this instance belongs + * @param x value to convert to an instance + */ + protected Dfp(final DfpField field, int x) { + this(field, (long) x); + } + + /** Create an instance from a long value. + * @param field field to which this instance belongs + * @param x value to convert to an instance + */ + protected Dfp(final DfpField field, long x) { + + // initialize as if 0 + mant = new int[field.getRadixDigits()]; + nans = FINITE; + this.field = field; + + boolean isLongMin = false; + if (x == Long.MIN_VALUE) { + // special case for Long.MIN_VALUE (-9223372036854775808) + // we must shift it before taking its absolute value + isLongMin = true; + ++x; + } + + // set the sign + if (x < 0) { + sign = -1; + x = -x; + } else { + sign = 1; + } + + exp = 0; + while (x != 0) { + System.arraycopy(mant, mant.length - exp, mant, mant.length - 1 - exp, exp); + mant[mant.length - 1] = (int) (x % RADIX); + x /= RADIX; + exp++; + } + + if (isLongMin) { + // remove the shift added for Long.MIN_VALUE + // we know in this case that fixing the last digit is sufficient + for (int i = 0; i < mant.length - 1; i++) { + if (mant[i] != 0) { + mant[i]++; + break; + } + } + } + } + + /** Create an instance from a double value. + * @param field field to which this instance belongs + * @param x value to convert to an instance + */ + protected Dfp(final DfpField field, double x) { + + // initialize as if 0 + mant = new int[field.getRadixDigits()]; + sign = 1; + exp = 0; + nans = FINITE; + this.field = field; + + long bits = Double.doubleToLongBits(x); + long mantissa = bits & 0x000fffffffffffffL; + int exponent = (int) ((bits & 0x7ff0000000000000L) >> 52) - 1023; + + if (exponent == -1023) { + // Zero or sub-normal + if (x == 0) { + // make sure 0 has the right sign + if ((bits & 0x8000000000000000L) != 0) { + sign = -1; + } + return; + } + + exponent++; + + // Normalize the subnormal number + while ( (mantissa & 0x0010000000000000L) == 0) { + exponent--; + mantissa <<= 1; + } + mantissa &= 0x000fffffffffffffL; + } + + if (exponent == 1024) { + // infinity or NAN + if (x != x) { + sign = (byte) 1; + nans = QNAN; + } else if (x < 0) { + sign = (byte) -1; + nans = INFINITE; + } else { + sign = (byte) 1; + nans = INFINITE; + } + return; + } + + Dfp xdfp = new Dfp(field, mantissa); + xdfp = xdfp.divide(new Dfp(field, 4503599627370496l)).add(field.getOne()); // Divide by 2^52, then add one + xdfp = xdfp.multiply(DfpMath.pow(field.getTwo(), exponent)); + + if ((bits & 0x8000000000000000L) != 0) { + xdfp = xdfp.negate(); + } + + System.arraycopy(xdfp.mant, 0, mant, 0, mant.length); + sign = xdfp.sign; + exp = xdfp.exp; + nans = xdfp.nans; + + } + + /** Copy constructor. + * @param d instance to copy + */ + public Dfp(final Dfp d) { + mant = d.mant.clone(); + sign = d.sign; + exp = d.exp; + nans = d.nans; + field = d.field; + } + + /** Create an instance from a String representation. + * @param field field to which this instance belongs + * @param s string representation of the instance + */ + protected Dfp(final DfpField field, final String s) { + + // initialize as if 0 + mant = new int[field.getRadixDigits()]; + sign = 1; + exp = 0; + nans = FINITE; + this.field = field; + + boolean decimalFound = false; + final int rsize = 4; // size of radix in decimal digits + final int offset = 4; // Starting offset into Striped + final char[] striped = new char[getRadixDigits() * rsize + offset * 2]; + + // Check some special cases + if (s.equals(POS_INFINITY_STRING)) { + sign = (byte) 1; + nans = INFINITE; + return; + } + + if (s.equals(NEG_INFINITY_STRING)) { + sign = (byte) -1; + nans = INFINITE; + return; + } + + if (s.equals(NAN_STRING)) { + sign = (byte) 1; + nans = QNAN; + return; + } + + // Check for scientific notation + int p = s.indexOf("e"); + if (p == -1) { // try upper case? + p = s.indexOf("E"); + } + + final String fpdecimal; + int sciexp = 0; + if (p != -1) { + // scientific notation + fpdecimal = s.substring(0, p); + String fpexp = s.substring(p+1); + boolean negative = false; + + for (int i=0; i= '0' && fpexp.charAt(i) <= '9') { + sciexp = sciexp * 10 + fpexp.charAt(i) - '0'; + } + } + + if (negative) { + sciexp = -sciexp; + } + } else { + // normal case + fpdecimal = s; + } + + // If there is a minus sign in the number then it is negative + if (fpdecimal.indexOf("-") != -1) { + sign = -1; + } + + // First off, find all of the leading zeros, trailing zeros, and significant digits + p = 0; + + // Move p to first significant digit + int decimalPos = 0; + for (;;) { + if (fpdecimal.charAt(p) >= '1' && fpdecimal.charAt(p) <= '9') { + break; + } + + if (decimalFound && fpdecimal.charAt(p) == '0') { + decimalPos--; + } + + if (fpdecimal.charAt(p) == '.') { + decimalFound = true; + } + + p++; + + if (p == fpdecimal.length()) { + break; + } + } + + // Copy the string onto Stripped + int q = offset; + striped[0] = '0'; + striped[1] = '0'; + striped[2] = '0'; + striped[3] = '0'; + int significantDigits=0; + for(;;) { + if (p == (fpdecimal.length())) { + break; + } + + // Don't want to run pass the end of the array + if (q == mant.length*rsize+offset+1) { + break; + } + + if (fpdecimal.charAt(p) == '.') { + decimalFound = true; + decimalPos = significantDigits; + p++; + continue; + } + + if (fpdecimal.charAt(p) < '0' || fpdecimal.charAt(p) > '9') { + p++; + continue; + } + + striped[q] = fpdecimal.charAt(p); + q++; + p++; + significantDigits++; + } + + + // If the decimal point has been found then get rid of trailing zeros. + if (decimalFound && q != offset) { + for (;;) { + q--; + if (q == offset) { + break; + } + if (striped[q] == '0') { + significantDigits--; + } else { + break; + } + } + } + + // special case of numbers like "0.00000" + if (decimalFound && significantDigits == 0) { + decimalPos = 0; + } + + // Implicit decimal point at end of number if not present + if (!decimalFound) { + decimalPos = q-offset; + } + + // Find the number of significant trailing zeros + q = offset; // set q to point to first sig digit + p = significantDigits-1+offset; + + while (p > q) { + if (striped[p] != '0') { + break; + } + p--; + } + + // Make sure the decimal is on a mod 10000 boundary + int i = ((rsize * 100) - decimalPos - sciexp % rsize) % rsize; + q -= i; + decimalPos += i; + + // Make the mantissa length right by adding zeros at the end if necessary + while ((p - q) < (mant.length * rsize)) { + for (i = 0; i < rsize; i++) { + striped[++p] = '0'; + } + } + + // Ok, now we know how many trailing zeros there are, + // and where the least significant digit is + for (i = mant.length - 1; i >= 0; i--) { + mant[i] = (striped[q] - '0') * 1000 + + (striped[q+1] - '0') * 100 + + (striped[q+2] - '0') * 10 + + (striped[q+3] - '0'); + q += 4; + } + + + exp = (decimalPos+sciexp) / rsize; + + if (q < striped.length) { + // Is there possible another digit? + round((striped[q] - '0')*1000); + } + + } + + /** Creates an instance with a non-finite value. + * @param field field to which this instance belongs + * @param sign sign of the Dfp to create + * @param nans code of the value, must be one of {@link #INFINITE}, + * {@link #SNAN}, {@link #QNAN} + */ + protected Dfp(final DfpField field, final byte sign, final byte nans) { + this.field = field; + this.mant = new int[field.getRadixDigits()]; + this.sign = sign; + this.exp = 0; + this.nans = nans; + } + + /** Create an instance with a value of 0. + * Use this internally in preference to constructors to facilitate subclasses + * @return a new instance with a value of 0 + */ + public Dfp newInstance() { + return new Dfp(getField()); + } + + /** Create an instance from a byte value. + * @param x value to convert to an instance + * @return a new instance with value x + */ + public Dfp newInstance(final byte x) { + return new Dfp(getField(), x); + } + + /** Create an instance from an int value. + * @param x value to convert to an instance + * @return a new instance with value x + */ + public Dfp newInstance(final int x) { + return new Dfp(getField(), x); + } + + /** Create an instance from a long value. + * @param x value to convert to an instance + * @return a new instance with value x + */ + public Dfp newInstance(final long x) { + return new Dfp(getField(), x); + } + + /** Create an instance from a double value. + * @param x value to convert to an instance + * @return a new instance with value x + */ + public Dfp newInstance(final double x) { + return new Dfp(getField(), x); + } + + /** Create an instance by copying an existing one. + * Use this internally in preference to constructors to facilitate subclasses. + * @param d instance to copy + * @return a new instance with the same value as d + */ + public Dfp newInstance(final Dfp d) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != d.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, NEW_INSTANCE_TRAP, d, result); + } + + return new Dfp(d); + + } + + /** Create an instance from a String representation. + * Use this internally in preference to constructors to facilitate subclasses. + * @param s string representation of the instance + * @return a new instance parsed from specified string + */ + public Dfp newInstance(final String s) { + return new Dfp(field, s); + } + + /** Creates an instance with a non-finite value. + * @param sig sign of the Dfp to create + * @param code code of the value, must be one of {@link #INFINITE}, + * {@link #SNAN}, {@link #QNAN} + * @return a new instance with a non-finite value + */ + public Dfp newInstance(final byte sig, final byte code) { + return field.newDfp(sig, code); + } + + /** Get the {@link Field Field} (really a {@link DfpField}) to which the instance belongs. + *

+ * The field is linked to the number of digits and acts as a factory + * for {@link Dfp} instances. + *

+ * @return {@link Field Field} (really a {@link DfpField}) to which the instance belongs + */ + public DfpField getField() { + return field; + } + + /** Get the number of radix digits of the instance. + * @return number of radix digits + */ + public int getRadixDigits() { + return field.getRadixDigits(); + } + + /** Get the constant 0. + * @return a Dfp with value zero + */ + public Dfp getZero() { + return field.getZero(); + } + + /** Get the constant 1. + * @return a Dfp with value one + */ + public Dfp getOne() { + return field.getOne(); + } + + /** Get the constant 2. + * @return a Dfp with value two + */ + public Dfp getTwo() { + return field.getTwo(); + } + + /** Shift the mantissa left, and adjust the exponent to compensate. + */ + protected void shiftLeft() { + for (int i = mant.length - 1; i > 0; i--) { + mant[i] = mant[i-1]; + } + mant[0] = 0; + exp--; + } + + /* Note that shiftRight() does not call round() as that round() itself + uses shiftRight() */ + /** Shift the mantissa right, and adjust the exponent to compensate. + */ + protected void shiftRight() { + for (int i = 0; i < mant.length - 1; i++) { + mant[i] = mant[i+1]; + } + mant[mant.length - 1] = 0; + exp++; + } + + /** Make our exp equal to the supplied one, this may cause rounding. + * Also causes de-normalized numbers. These numbers are generally + * dangerous because most routines assume normalized numbers. + * Align doesn't round, so it will return the last digit destroyed + * by shifting right. + * @param e desired exponent + * @return last digit destroyed by shifting right + */ + protected int align(int e) { + int lostdigit = 0; + boolean inexact = false; + + int diff = exp - e; + + int adiff = diff; + if (adiff < 0) { + adiff = -adiff; + } + + if (diff == 0) { + return 0; + } + + if (adiff > (mant.length + 1)) { + // Special case + Arrays.fill(mant, 0); + exp = e; + + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + dotrap(DfpField.FLAG_INEXACT, ALIGN_TRAP, this, this); + + return 0; + } + + for (int i = 0; i < adiff; i++) { + if (diff < 0) { + /* Keep track of loss -- only signal inexact after losing 2 digits. + * the first lost digit is returned to add() and may be incorporated + * into the result. + */ + if (lostdigit != 0) { + inexact = true; + } + + lostdigit = mant[0]; + + shiftRight(); + } else { + shiftLeft(); + } + } + + if (inexact) { + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + dotrap(DfpField.FLAG_INEXACT, ALIGN_TRAP, this, this); + } + + return lostdigit; + + } + + /** Check if instance is less than x. + * @param x number to check instance against + * @return true if instance is less than x and neither are NaN, false otherwise + */ + public boolean lessThan(final Dfp x) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != x.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, x, result); + return false; + } + + /* if a nan is involved, signal invalid and return false */ + if (isNaN() || x.isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, x, newInstance(getZero())); + return false; + } + + return compare(this, x) < 0; + } + + /** Check if instance is greater than x. + * @param x number to check instance against + * @return true if instance is greater than x and neither are NaN, false otherwise + */ + public boolean greaterThan(final Dfp x) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != x.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + dotrap(DfpField.FLAG_INVALID, GREATER_THAN_TRAP, x, result); + return false; + } + + /* if a nan is involved, signal invalid and return false */ + if (isNaN() || x.isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, GREATER_THAN_TRAP, x, newInstance(getZero())); + return false; + } + + return compare(this, x) > 0; + } + + /** Check if instance is less than or equal to 0. + * @return true if instance is not NaN and less than or equal to 0, false otherwise + */ + public boolean negativeOrNull() { + + if (isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero())); + return false; + } + + return (sign < 0) || ((mant[mant.length - 1] == 0) && !isInfinite()); + + } + + /** Check if instance is strictly less than 0. + * @return true if instance is not NaN and less than or equal to 0, false otherwise + */ + public boolean strictlyNegative() { + + if (isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero())); + return false; + } + + return (sign < 0) && ((mant[mant.length - 1] != 0) || isInfinite()); + + } + + /** Check if instance is greater than or equal to 0. + * @return true if instance is not NaN and greater than or equal to 0, false otherwise + */ + public boolean positiveOrNull() { + + if (isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero())); + return false; + } + + return (sign > 0) || ((mant[mant.length - 1] == 0) && !isInfinite()); + + } + + /** Check if instance is strictly greater than 0. + * @return true if instance is not NaN and greater than or equal to 0, false otherwise + */ + public boolean strictlyPositive() { + + if (isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero())); + return false; + } + + return (sign > 0) && ((mant[mant.length - 1] != 0) || isInfinite()); + + } + + /** Get the absolute value of instance. + * @return absolute value of instance + * @since 3.2 + */ + public Dfp abs() { + Dfp result = newInstance(this); + result.sign = 1; + return result; + } + + /** Check if instance is infinite. + * @return true if instance is infinite + */ + public boolean isInfinite() { + return nans == INFINITE; + } + + /** Check if instance is not a number. + * @return true if instance is not a number + */ + public boolean isNaN() { + return (nans == QNAN) || (nans == SNAN); + } + + /** Check if instance is equal to zero. + * @return true if instance is equal to zero + */ + public boolean isZero() { + + if (isNaN()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero())); + return false; + } + + return (mant[mant.length - 1] == 0) && !isInfinite(); + + } + + /** Check if instance is equal to x. + * @param other object to check instance against + * @return true if instance is equal to x and neither are NaN, false otherwise + */ + @Override + public boolean equals(final Object other) { + + if (other instanceof Dfp) { + final Dfp x = (Dfp) other; + if (isNaN() || x.isNaN() || field.getRadixDigits() != x.field.getRadixDigits()) { + return false; + } + + return compare(this, x) == 0; + } + + return false; + + } + + /** + * Gets a hashCode for the instance. + * @return a hash code value for this object + */ + @Override + public int hashCode() { + return 17 + (isZero() ? 0 : (sign << 8)) + (nans << 16) + exp + Arrays.hashCode(mant); + } + + /** Check if instance is not equal to x. + * @param x number to check instance against + * @return true if instance is not equal to x and neither are NaN, false otherwise + */ + public boolean unequal(final Dfp x) { + if (isNaN() || x.isNaN() || field.getRadixDigits() != x.field.getRadixDigits()) { + return false; + } + + return greaterThan(x) || lessThan(x); + } + + /** Compare two instances. + * @param a first instance in comparison + * @param b second instance in comparison + * @return -1 if ab and 0 if a==b + * Note this method does not properly handle NaNs or numbers with different precision. + */ + private static int compare(final Dfp a, final Dfp b) { + // Ignore the sign of zero + if (a.mant[a.mant.length - 1] == 0 && b.mant[b.mant.length - 1] == 0 && + a.nans == FINITE && b.nans == FINITE) { + return 0; + } + + if (a.sign != b.sign) { + if (a.sign == -1) { + return -1; + } else { + return 1; + } + } + + // deal with the infinities + if (a.nans == INFINITE && b.nans == FINITE) { + return a.sign; + } + + if (a.nans == FINITE && b.nans == INFINITE) { + return -b.sign; + } + + if (a.nans == INFINITE && b.nans == INFINITE) { + return 0; + } + + // Handle special case when a or b is zero, by ignoring the exponents + if (b.mant[b.mant.length-1] != 0 && a.mant[b.mant.length-1] != 0) { + if (a.exp < b.exp) { + return -a.sign; + } + + if (a.exp > b.exp) { + return a.sign; + } + } + + // compare the mantissas + for (int i = a.mant.length - 1; i >= 0; i--) { + if (a.mant[i] > b.mant[i]) { + return a.sign; + } + + if (a.mant[i] < b.mant[i]) { + return -a.sign; + } + } + + return 0; + + } + + /** Round to nearest integer using the round-half-even method. + * That is round to nearest integer unless both are equidistant. + * In which case round to the even one. + * @return rounded value + * @since 3.2 + */ + public Dfp rint() { + return trunc(DfpField.RoundingMode.ROUND_HALF_EVEN); + } + + /** Round to an integer using the round floor mode. + * That is, round toward -Infinity + * @return rounded value + * @since 3.2 + */ + public Dfp floor() { + return trunc(DfpField.RoundingMode.ROUND_FLOOR); + } + + /** Round to an integer using the round ceil mode. + * That is, round toward +Infinity + * @return rounded value + * @since 3.2 + */ + public Dfp ceil() { + return trunc(DfpField.RoundingMode.ROUND_CEIL); + } + + /** Returns the IEEE remainder. + * @param d divisor + * @return this less n × d, where n is the integer closest to this/d + * @since 3.2 + */ + public Dfp remainder(final Dfp d) { + + final Dfp result = this.subtract(this.divide(d).rint().multiply(d)); + + // IEEE 854-1987 says that if the result is zero, then it carries the sign of this + if (result.mant[mant.length-1] == 0) { + result.sign = sign; + } + + return result; + + } + + /** Does the integer conversions with the specified rounding. + * @param rmode rounding mode to use + * @return truncated value + */ + protected Dfp trunc(final DfpField.RoundingMode rmode) { + boolean changed = false; + + if (isNaN()) { + return newInstance(this); + } + + if (nans == INFINITE) { + return newInstance(this); + } + + if (mant[mant.length-1] == 0) { + // a is zero + return newInstance(this); + } + + /* If the exponent is less than zero then we can certainly + * return zero */ + if (exp < 0) { + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + Dfp result = newInstance(getZero()); + result = dotrap(DfpField.FLAG_INEXACT, TRUNC_TRAP, this, result); + return result; + } + + /* If the exponent is greater than or equal to digits, then it + * must already be an integer since there is no precision left + * for any fractional part */ + + if (exp >= mant.length) { + return newInstance(this); + } + + /* General case: create another dfp, result, that contains the + * a with the fractional part lopped off. */ + + Dfp result = newInstance(this); + for (int i = 0; i < mant.length-result.exp; i++) { + changed |= result.mant[i] != 0; + result.mant[i] = 0; + } + + if (changed) { + switch (rmode) { + case ROUND_FLOOR: + if (result.sign == -1) { + // then we must increment the mantissa by one + result = result.add(newInstance(-1)); + } + break; + + case ROUND_CEIL: + if (result.sign == 1) { + // then we must increment the mantissa by one + result = result.add(getOne()); + } + break; + + case ROUND_HALF_EVEN: + default: + final Dfp half = newInstance("0.5"); + Dfp a = subtract(result); // difference between this and result + a.sign = 1; // force positive (take abs) + if (a.greaterThan(half)) { + a = newInstance(getOne()); + a.sign = sign; + result = result.add(a); + } + + /** If exactly equal to 1/2 and odd then increment */ + if (a.equals(half) && result.exp > 0 && (result.mant[mant.length-result.exp]&1) != 0) { + a = newInstance(getOne()); + a.sign = sign; + result = result.add(a); + } + break; + } + + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); // signal inexact + result = dotrap(DfpField.FLAG_INEXACT, TRUNC_TRAP, this, result); + return result; + } + + return result; + } + + /** Convert this to an integer. + * If greater than 2147483647, it returns 2147483647. If less than -2147483648 it returns -2147483648. + * @return converted number + */ + public int intValue() { + Dfp rounded; + int result = 0; + + rounded = rint(); + + if (rounded.greaterThan(newInstance(2147483647))) { + return 2147483647; + } + + if (rounded.lessThan(newInstance(-2147483648))) { + return -2147483648; + } + + for (int i = mant.length - 1; i >= mant.length - rounded.exp; i--) { + result = result * RADIX + rounded.mant[i]; + } + + if (rounded.sign == -1) { + result = -result; + } + + return result; + } + + /** Get the exponent of the greatest power of 10000 that is + * less than or equal to the absolute value of this. I.E. if + * this is 106 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 10000e + */ + public Dfp power10K(final int e) { + Dfp d = newInstance(getOne()); + d.exp = e + 1; + return d; + } + + /** Get the exponent of the greatest power of 10 that is less than or equal to abs(this). + * @return integer base 10 logarithm + * @since 3.2 + */ + public int intLog10() { + if (mant[mant.length-1] > 1000) { + return exp * 4 - 1; + } + if (mant[mant.length-1] > 100) { + return exp * 4 - 2; + } + if (mant[mant.length-1] > 10) { + return exp * 4 - 3; + } + return exp * 4 - 4; + } + + /** Return the specified power of 10. + * @param e desired power + * @return 10e + */ + public Dfp power10(final int e) { + Dfp d = newInstance(getOne()); + + if (e >= 0) { + d.exp = e / 4 + 1; + } else { + d.exp = (e + 1) / 4; + } + + switch ((e % 4 + 4) % 4) { + case 0: + break; + case 1: + d = d.multiply(10); + break; + case 2: + d = d.multiply(100); + break; + default: + d = d.multiply(1000); + } + + return d; + } + + /** Negate the mantissa of this by computing the complement. + * Leaves the sign bit unchanged, used internally by add. + * Denormalized numbers are handled properly here. + * @param extra ??? + * @return ??? + */ + protected int complement(int extra) { + + extra = RADIX-extra; + for (int i = 0; i < mant.length; i++) { + mant[i] = RADIX-mant[i]-1; + } + + int rh = extra / RADIX; + extra -= rh * RADIX; + for (int i = 0; i < mant.length; i++) { + final int r = mant[i] + rh; + rh = r / RADIX; + mant[i] = r - rh * RADIX; + } + + return extra; + } + + /** Add x to this. + * @param x number to add + * @return sum of this and x + */ + public Dfp add(final Dfp x) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != x.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, ADD_TRAP, x, result); + } + + /* handle special cases */ + if (nans != FINITE || x.nans != FINITE) { + if (isNaN()) { + return this; + } + + if (x.isNaN()) { + return x; + } + + if (nans == INFINITE && x.nans == FINITE) { + return this; + } + + if (x.nans == INFINITE && nans == FINITE) { + return x; + } + + if (x.nans == INFINITE && nans == INFINITE && sign == x.sign) { + return x; + } + + if (x.nans == INFINITE && nans == INFINITE && sign != x.sign) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + Dfp result = newInstance(getZero()); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, ADD_TRAP, x, result); + return result; + } + } + + /* copy this and the arg */ + Dfp a = newInstance(this); + Dfp b = newInstance(x); + + /* initialize the result object */ + Dfp result = newInstance(getZero()); + + /* Make all numbers positive, but remember their sign */ + final byte asign = a.sign; + final byte bsign = b.sign; + + a.sign = 1; + b.sign = 1; + + /* The result will be signed like the arg with greatest magnitude */ + byte rsign = bsign; + if (compare(a, b) > 0) { + rsign = asign; + } + + /* Handle special case when a or b is zero, by setting the exponent + of the zero number equal to the other one. This avoids an alignment + which would cause catastropic loss of precision */ + if (b.mant[mant.length-1] == 0) { + b.exp = a.exp; + } + + if (a.mant[mant.length-1] == 0) { + a.exp = b.exp; + } + + /* align number with the smaller exponent */ + int aextradigit = 0; + int bextradigit = 0; + if (a.exp < b.exp) { + aextradigit = a.align(b.exp); + } else { + bextradigit = b.align(a.exp); + } + + /* complement the smaller of the two if the signs are different */ + if (asign != bsign) { + if (asign == rsign) { + bextradigit = b.complement(bextradigit); + } else { + aextradigit = a.complement(aextradigit); + } + } + + /* add the mantissas */ + int rh = 0; /* acts as a carry */ + for (int i = 0; i < mant.length; i++) { + final int r = a.mant[i]+b.mant[i]+rh; + rh = r / RADIX; + result.mant[i] = r - rh * RADIX; + } + result.exp = a.exp; + result.sign = rsign; + + /* handle overflow -- note, when asign!=bsign an overflow is + * normal and should be ignored. */ + + if (rh != 0 && (asign == bsign)) { + final int lostdigit = result.mant[0]; + result.shiftRight(); + result.mant[mant.length-1] = rh; + final int excp = result.round(lostdigit); + if (excp != 0) { + result = dotrap(excp, ADD_TRAP, x, result); + } + } + + /* normalize the result */ + for (int i = 0; i < mant.length; i++) { + if (result.mant[mant.length-1] != 0) { + break; + } + result.shiftLeft(); + if (i == 0) { + result.mant[0] = aextradigit+bextradigit; + aextradigit = 0; + bextradigit = 0; + } + } + + /* result is zero if after normalization the most sig. digit is zero */ + if (result.mant[mant.length-1] == 0) { + result.exp = 0; + + if (asign != bsign) { + // Unless adding 2 negative zeros, sign is positive + result.sign = 1; // Per IEEE 854-1987 Section 6.3 + } + } + + /* Call round to test for over/under flows */ + final int excp = result.round(aextradigit + bextradigit); + if (excp != 0) { + result = dotrap(excp, ADD_TRAP, x, result); + } + + return result; + } + + /** Returns a number that is this number with the sign bit reversed. + * @return the opposite of this + */ + public Dfp negate() { + Dfp result = newInstance(this); + result.sign = (byte) - result.sign; + return result; + } + + /** Subtract x from this. + * @param x number to subtract + * @return difference of this and a + */ + public Dfp subtract(final Dfp x) { + return add(x.negate()); + } + + /** Round this given the next digit n using the current rounding mode. + * @param n ??? + * @return the IEEE flag if an exception occurred + */ + protected int round(int n) { + boolean inc = false; + switch (field.getRoundingMode()) { + case ROUND_DOWN: + inc = false; + break; + + case ROUND_UP: + inc = n != 0; // round up if n!=0 + break; + + case ROUND_HALF_UP: + inc = n >= 5000; // round half up + break; + + case ROUND_HALF_DOWN: + inc = n > 5000; // round half down + break; + + case ROUND_HALF_EVEN: + inc = n > 5000 || (n == 5000 && (mant[0] & 1) == 1); // round half-even + break; + + case ROUND_HALF_ODD: + inc = n > 5000 || (n == 5000 && (mant[0] & 1) == 0); // round half-odd + break; + + case ROUND_CEIL: + inc = sign == 1 && n != 0; // round ceil + break; + + case ROUND_FLOOR: + default: + inc = sign == -1 && n != 0; // round floor + break; + } + + if (inc) { + // increment if necessary + int rh = 1; + for (int i = 0; i < mant.length; i++) { + final int r = mant[i] + rh; + rh = r / RADIX; + mant[i] = r - rh * RADIX; + } + + if (rh != 0) { + shiftRight(); + mant[mant.length-1] = rh; + } + } + + // check for exceptional cases and raise signals if necessary + if (exp < MIN_EXP) { + // Gradual Underflow + field.setIEEEFlagsBits(DfpField.FLAG_UNDERFLOW); + return DfpField.FLAG_UNDERFLOW; + } + + if (exp > MAX_EXP) { + // Overflow + field.setIEEEFlagsBits(DfpField.FLAG_OVERFLOW); + return DfpField.FLAG_OVERFLOW; + } + + if (n != 0) { + // Inexact + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + return DfpField.FLAG_INEXACT; + } + + return 0; + + } + + /** Multiply this by x. + * @param x multiplicand + * @return product of this and x + */ + public Dfp multiply(final Dfp x) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != x.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, x, result); + } + + Dfp result = newInstance(getZero()); + + /* handle special cases */ + if (nans != FINITE || x.nans != FINITE) { + if (isNaN()) { + return this; + } + + if (x.isNaN()) { + return x; + } + + if (nans == INFINITE && x.nans == FINITE && x.mant[mant.length-1] != 0) { + result = newInstance(this); + result.sign = (byte) (sign * x.sign); + return result; + } + + if (x.nans == INFINITE && nans == FINITE && mant[mant.length-1] != 0) { + result = newInstance(x); + result.sign = (byte) (sign * x.sign); + return result; + } + + if (x.nans == INFINITE && nans == INFINITE) { + result = newInstance(this); + result.sign = (byte) (sign * x.sign); + return result; + } + + if ( (x.nans == INFINITE && nans == FINITE && mant[mant.length-1] == 0) || + (nans == INFINITE && x.nans == FINITE && x.mant[mant.length-1] == 0) ) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(getZero()); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, x, result); + return result; + } + } + + int[] product = new int[mant.length*2]; // Big enough to hold even the largest result + + for (int i = 0; i < mant.length; i++) { + int rh = 0; // acts as a carry + for (int j=0; j= 0; i--) { + if (product[i] != 0) { + md = i; + break; + } + } + + // Copy the digits into the result + for (int i = 0; i < mant.length; i++) { + result.mant[mant.length - i - 1] = product[md - i]; + } + + // Fixup the exponent. + result.exp = exp + x.exp + md - 2 * mant.length + 1; + result.sign = (byte)((sign == x.sign)?1:-1); + + if (result.mant[mant.length-1] == 0) { + // if result is zero, set exp to zero + result.exp = 0; + } + + final int excp; + if (md > (mant.length-1)) { + excp = result.round(product[md-mant.length]); + } else { + excp = result.round(0); // has no effect except to check status + } + + if (excp != 0) { + result = dotrap(excp, MULTIPLY_TRAP, x, result); + } + + return result; + + } + + /** Multiply this by a single digit x. + * @param x multiplicand + * @return product of this and x + */ + public Dfp multiply(final int x) { + if (x >= 0 && x < RADIX) { + return multiplyFast(x); + } else { + return multiply(newInstance(x)); + } + } + + /** Multiply this by a single digit 0<=x<radix. + * There are speed advantages in this special case. + * @param x multiplicand + * @return product of this and x + */ + private Dfp multiplyFast(final int x) { + Dfp result = newInstance(this); + + /* handle special cases */ + if (nans != FINITE) { + if (isNaN()) { + return this; + } + + if (nans == INFINITE && x != 0) { + result = newInstance(this); + return result; + } + + if (nans == INFINITE && x == 0) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(getZero()); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, newInstance(getZero()), result); + return result; + } + } + + /* range check x */ + if (x < 0 || x >= RADIX) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(getZero()); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, result, result); + return result; + } + + int rh = 0; + for (int i = 0; i < mant.length; i++) { + final int r = mant[i] * x + rh; + rh = r / RADIX; + result.mant[i] = r - rh * RADIX; + } + + int lostdigit = 0; + if (rh != 0) { + lostdigit = result.mant[0]; + result.shiftRight(); + result.mant[mant.length-1] = rh; + } + + if (result.mant[mant.length-1] == 0) { // if result is zero, set exp to zero + result.exp = 0; + } + + final int excp = result.round(lostdigit); + if (excp != 0) { + result = dotrap(excp, MULTIPLY_TRAP, result, result); + } + + return result; + } + + /** Divide this by divisor. + * @param divisor divisor + * @return quotient of this by divisor + */ + public Dfp divide(Dfp divisor) { + int dividend[]; // current status of the dividend + int quotient[]; // quotient + int remainder[];// remainder + int qd; // current quotient digit we're working with + int nsqd; // number of significant quotient digits we have + int trial=0; // trial quotient digit + int minadj; // minimum adjustment + boolean trialgood; // Flag to indicate a good trail digit + int md=0; // most sig digit in result + int excp; // exceptions + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != divisor.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, DIVIDE_TRAP, divisor, result); + } + + Dfp result = newInstance(getZero()); + + /* handle special cases */ + if (nans != FINITE || divisor.nans != FINITE) { + if (isNaN()) { + return this; + } + + if (divisor.isNaN()) { + return divisor; + } + + if (nans == INFINITE && divisor.nans == FINITE) { + result = newInstance(this); + result.sign = (byte) (sign * divisor.sign); + return result; + } + + if (divisor.nans == INFINITE && nans == FINITE) { + result = newInstance(getZero()); + result.sign = (byte) (sign * divisor.sign); + return result; + } + + if (divisor.nans == INFINITE && nans == INFINITE) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(getZero()); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, DIVIDE_TRAP, divisor, result); + return result; + } + } + + /* Test for divide by zero */ + if (divisor.mant[mant.length-1] == 0) { + field.setIEEEFlagsBits(DfpField.FLAG_DIV_ZERO); + result = newInstance(getZero()); + result.sign = (byte) (sign * divisor.sign); + result.nans = INFINITE; + result = dotrap(DfpField.FLAG_DIV_ZERO, DIVIDE_TRAP, divisor, result); + return result; + } + + dividend = new int[mant.length+1]; // one extra digit needed + quotient = new int[mant.length+2]; // two extra digits needed 1 for overflow, 1 for rounding + remainder = new int[mant.length+1]; // one extra digit needed + + /* Initialize our most significant digits to zero */ + + dividend[mant.length] = 0; + quotient[mant.length] = 0; + quotient[mant.length+1] = 0; + remainder[mant.length] = 0; + + /* copy our mantissa into the dividend, initialize the + quotient while we are at it */ + + for (int i = 0; i < mant.length; i++) { + dividend[i] = mant[i]; + quotient[i] = 0; + remainder[i] = 0; + } + + /* outer loop. Once per quotient digit */ + nsqd = 0; + for (qd = mant.length+1; qd >= 0; qd--) { + /* Determine outer limits of our quotient digit */ + + // r = most sig 2 digits of dividend + final int divMsb = dividend[mant.length]*RADIX+dividend[mant.length-1]; + int min = divMsb / (divisor.mant[mant.length-1]+1); + int max = (divMsb + 1) / divisor.mant[mant.length-1]; + + trialgood = false; + while (!trialgood) { + // try the mean + trial = (min+max)/2; + + /* Multiply by divisor and store as remainder */ + int rh = 0; + for (int i = 0; i < mant.length + 1; i++) { + int dm = (i= 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-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; + + } + + /** {@inheritDoc} */ + public Dfp reciprocal() { + return field.getOne().divide(this); + } + + /** Compute the square root. + * @return square root of the instance + * @since 3.2 + */ + public Dfp sqrt() { + + // check for unusual cases + if (nans == FINITE && mant[mant.length-1] == 0) { + // if zero + return newInstance(this); + } + + if (nans != FINITE) { + if (nans == INFINITE && sign == 1) { + // if positive infinity + return newInstance(this); + } + + if (nans == QNAN) { + return newInstance(this); + } + + if (nans == SNAN) { + Dfp result; + + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(this); + result = dotrap(DfpField.FLAG_INVALID, SQRT_TRAP, null, result); + return result; + } + } + + if (sign == -1) { + // if negative + Dfp result; + + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + result = newInstance(this); + result.nans = QNAN; + result = dotrap(DfpField.FLAG_INVALID, SQRT_TRAP, null, result); + return result; + } + + Dfp x = newInstance(this); + + /* Lets make a reasonable guess as to the size of the square root */ + if (x.exp < -1 || x.exp > 1) { + x.exp = this.exp / 2; + } + + /* Coarsely estimate the mantissa */ + switch (x.mant[mant.length-1] / 2000) { + case 0: + x.mant[mant.length-1] = x.mant[mant.length-1]/2+1; + break; + case 2: + x.mant[mant.length-1] = 1500; + break; + case 3: + x.mant[mant.length-1] = 2200; + break; + default: + x.mant[mant.length-1] = 3000; + } + + Dfp dx = newInstance(x); + + /* Now that we have the first pass estimate, compute the rest + by the formula dx = (y - x*x) / (2x); */ + + Dfp px = getZero(); + Dfp ppx = getZero(); + while (x.unequal(px)) { + dx = newInstance(x); + dx.sign = -1; + dx = dx.add(this.divide(x)); + dx = dx.divide(2); + ppx = px; + px = x; + x = x.add(dx); + + if (x.equals(ppx)) { + // alternating between two values + break; + } + + // if dx is zero, break. Note testing the most sig digit + // is a sufficient test since dx is normalized + if (dx.mant[mant.length-1] == 0) { + break; + } + } + + return x; + + } + + /** Get a string representation of the instance. + * @return string representation of the instance + */ + @Override + public String toString() { + if (nans != FINITE) { + // if non-finite exceptional cases + if (nans == INFINITE) { + return (sign < 0) ? NEG_INFINITY_STRING : POS_INFINITY_STRING; + } else { + return NAN_STRING; + } + } + + if (exp > mant.length || exp < -1) { + return dfp2sci(); + } + + return dfp2string(); + + } + + /** Convert an instance to a string using scientific notation. + * @return string representation of the instance in scientific notation + */ + protected String dfp2sci() { + char rawdigits[] = new char[mant.length * 4]; + char outputbuffer[] = new char[mant.length * 4 + 20]; + int p; + int q; + int e; + int ae; + int shf; + + // Get all the digits + p = 0; + for (int i = mant.length - 1; i >= 0; i--) { + rawdigits[p++] = (char) ((mant[i] / 1000) + '0'); + rawdigits[p++] = (char) (((mant[i] / 100) %10) + '0'); + rawdigits[p++] = (char) (((mant[i] / 10) % 10) + '0'); + rawdigits[p++] = (char) (((mant[i]) % 10) + '0'); + } + + // Find the first non-zero one + for (p = 0; p < rawdigits.length; p++) { + if (rawdigits[p] != '0') { + break; + } + } + shf = p; + + // Now do the conversion + q = 0; + if (sign == -1) { + outputbuffer[q++] = '-'; + } + + if (p != rawdigits.length) { + // there are non zero digits... + outputbuffer[q++] = rawdigits[p++]; + outputbuffer[q++] = '.'; + + while (p ae; p /= 10) { + // nothing to do + } + + if (e < 0) { + outputbuffer[q++] = '-'; + } + + while (p > 0) { + outputbuffer[q++] = (char)(ae / p + '0'); + ae %= p; + p /= 10; + } + + return new String(outputbuffer, 0, q); + + } + + /** Convert an instance to a string using normal notation. + * @return string representation of the instance in normal notation + */ + protected String dfp2string() { + char buffer[] = new char[mant.length*4 + 20]; + int p = 1; + int q; + int e = exp; + boolean pointInserted = false; + + buffer[0] = ' '; + + if (e <= 0) { + buffer[p++] = '0'; + buffer[p++] = '.'; + pointInserted = true; + } + + while (e < 0) { + buffer[p++] = '0'; + buffer[p++] = '0'; + buffer[p++] = '0'; + buffer[p++] = '0'; + e++; + } + + for (int i = mant.length - 1; i >= 0; i--) { + buffer[p++] = (char) ((mant[i] / 1000) + '0'); + buffer[p++] = (char) (((mant[i] / 100) % 10) + '0'); + buffer[p++] = (char) (((mant[i] / 10) % 10) + '0'); + buffer[p++] = (char) (((mant[i]) % 10) + '0'); + if (--e == 0) { + buffer[p++] = '.'; + pointInserted = true; + } + } + + while (e > 0) { + buffer[p++] = '0'; + buffer[p++] = '0'; + buffer[p++] = '0'; + buffer[p++] = '0'; + e--; + } + + if (!pointInserted) { + // Ensure we have a radix point! + buffer[p++] = '.'; + } + + // Suppress leading zeros + q = 1; + while (buffer[q] == '0') { + q++; + } + if (buffer[q] == '.') { + q--; + } + + // Suppress trailing zeros + while (buffer[p-1] == '0') { + p--; + } + + // Insert sign + if (sign < 0) { + buffer[--q] = '-'; + } + + return new String(buffer, q, p - q); + + } + + /** Raises a trap. This does not set the corresponding flag however. + * @param type the trap type + * @param what - name of routine trap occurred in + * @param oper - input operator to function + * @param result - the result computed prior to the trap + * @return The suggested return value from the trap handler + */ + public Dfp dotrap(int type, String what, Dfp oper, Dfp result) { + Dfp def = result; + + switch (type) { + case DfpField.FLAG_INVALID: + def = newInstance(getZero()); + def.sign = result.sign; + def.nans = QNAN; + break; + + case DfpField.FLAG_DIV_ZERO: + if (nans == FINITE && mant[mant.length-1] != 0) { + // normal case, we are finite, non-zero + def = newInstance(getZero()); + def.sign = (byte)(sign*oper.sign); + def.nans = INFINITE; + } + + if (nans == FINITE && mant[mant.length-1] == 0) { + // 0/0 + def = newInstance(getZero()); + def.nans = QNAN; + } + + if (nans == INFINITE || nans == QNAN) { + def = newInstance(getZero()); + def.nans = QNAN; + } + + if (nans == INFINITE || nans == SNAN) { + def = newInstance(getZero()); + def.nans = QNAN; + } + break; + + case DfpField.FLAG_UNDERFLOW: + if ( (result.exp+mant.length) < MIN_EXP) { + def = newInstance(getZero()); + def.sign = result.sign; + } else { + def = newInstance(result); // gradual underflow + } + result.exp += ERR_SCALE; + break; + + case DfpField.FLAG_OVERFLOW: + result.exp -= ERR_SCALE; + def = newInstance(getZero()); + def.sign = result.sign; + def.nans = INFINITE; + break; + + default: def = result; break; + } + + return trap(type, what, oper, def, result); + + } + + /** Trap handler. Subclasses may override this to provide trap + * functionality per IEEE 854-1987. + * + * @param type The exception type - e.g. FLAG_OVERFLOW + * @param what The name of the routine we were in e.g. divide() + * @param oper An operand to this function if any + * @param def The default return value if trap not enabled + * @param result The result that is specified to be delivered per + * IEEE 854, if any + * @return the value that should be return by the operation triggering the trap + */ + protected Dfp trap(int type, String what, Dfp oper, Dfp def, Dfp result) { + return def; + } + + /** Returns the type - one of FINITE, INFINITE, SNAN, QNAN. + * @return type of the number + */ + public int classify() { + return nans; + } + + /** Creates an instance that is the same as x except that it has the sign of y. + * abs(x) = dfp.copysign(x, dfp.one) + * @param x number to get the value from + * @param y number to get the sign from + * @return a number with the value of x and the sign of y + */ + public static Dfp copysign(final Dfp x, final Dfp y) { + Dfp result = x.newInstance(x); + result.sign = y.sign; + return result; + } + + /** Returns the next number greater than this one in the direction of x. + * If this==x then simply returns this. + * @param x direction where to look at + * @return closest number next to instance in the direction of x + */ + public Dfp nextAfter(final Dfp x) { + + // make sure we don't mix number with different precision + if (field.getRadixDigits() != x.field.getRadixDigits()) { + field.setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, NEXT_AFTER_TRAP, x, result); + } + + // if this is greater than x + boolean up = false; + if (this.lessThan(x)) { + up = true; + } + + if (compare(this, x) == 0) { + return newInstance(x); + } + + if (lessThan(getZero())) { + up = !up; + } + + final Dfp inc; + Dfp result; + if (up) { + inc = newInstance(getOne()); + inc.exp = this.exp-mant.length+1; + inc.sign = this.sign; + + if (this.equals(getZero())) { + inc.exp = MIN_EXP-mant.length; + } + + result = add(inc); + } else { + inc = newInstance(getOne()); + inc.exp = this.exp; + inc.sign = this.sign; + + if (this.equals(inc)) { + inc.exp = this.exp-mant.length; + } else { + inc.exp = this.exp-mant.length+1; + } + + if (this.equals(getZero())) { + inc.exp = MIN_EXP-mant.length; + } + + result = this.subtract(inc); + } + + if (result.classify() == INFINITE && this.classify() != INFINITE) { + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + result = dotrap(DfpField.FLAG_INEXACT, NEXT_AFTER_TRAP, x, result); + } + + if (result.equals(getZero()) && this.equals(getZero()) == false) { + field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); + result = dotrap(DfpField.FLAG_INEXACT, NEXT_AFTER_TRAP, x, result); + } + + return result; + + } + + /** Convert the instance into a double. + * @return a double approximating the instance + * @see #toSplitDouble() + */ + public double toDouble() { + + if (isInfinite()) { + if (lessThan(getZero())) { + return Double.NEGATIVE_INFINITY; + } else { + return Double.POSITIVE_INFINITY; + } + } + + if (isNaN()) { + return Double.NaN; + } + + Dfp y = this; + boolean negate = false; + int cmp0 = compare(this, getZero()); + if (cmp0 == 0) { + return sign < 0 ? -0.0 : +0.0; + } else if (cmp0 < 0) { + y = negate(); + negate = true; + } + + /* Find the exponent, first estimate by integer log10, then adjust. + Should be faster than doing a natural logarithm. */ + int exponent = (int)(y.intLog10() * 3.32); + if (exponent < 0) { + exponent--; + } + + Dfp tempDfp = DfpMath.pow(getTwo(), exponent); + while (tempDfp.lessThan(y) || tempDfp.equals(y)) { + tempDfp = tempDfp.multiply(2); + exponent++; + } + exponent--; + + /* We have the exponent, now work on the mantissa */ + + y = y.divide(DfpMath.pow(getTwo(), exponent)); + if (exponent > -1023) { + y = y.subtract(getOne()); + } + + if (exponent < -1074) { + return 0; + } + + if (exponent > 1023) { + return negate ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } + + + y = y.multiply(newInstance(4503599627370496l)).rint(); + String str = y.toString(); + str = str.substring(0, str.length()-1); + long mantissa = Long.parseLong(str); + + if (mantissa == 4503599627370496L) { + // Handle special case where we round up to next power of two + mantissa = 0; + exponent++; + } + + /* Its going to be subnormal, so make adjustments */ + if (exponent <= -1023) { + exponent--; + } + + while (exponent < -1023) { + exponent++; + mantissa >>>= 1; + } + + long bits = mantissa | ((exponent + 1023L) << 52); + double x = Double.longBitsToDouble(bits); + + if (negate) { + x = -x; + } + + return x; + + } + + /** Convert the instance into a split double. + * @return an array of two doubles which sum represent the instance + * @see #toDouble() + */ + public double[] toSplitDouble() { + double split[] = new double[2]; + long mask = 0xffffffffc0000000L; + + split[0] = Double.longBitsToDouble(Double.doubleToLongBits(toDouble()) & mask); + split[1] = subtract(newInstance(split[0])).toDouble(); + + return split; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public double getReal() { + return toDouble(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp add(final double a) { + return add(newInstance(a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp subtract(final double a) { + return subtract(newInstance(a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp multiply(final double a) { + return multiply(newInstance(a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp divide(final double a) { + return divide(newInstance(a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp remainder(final double a) { + return remainder(newInstance(a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public long round() { + return FastMath.round(toDouble()); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp signum() { + if (isNaN() || isZero()) { + return this; + } else { + return newInstance(sign > 0 ? +1 : -1); + } + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp copySign(final Dfp s) { + if ((sign >= 0 && s.sign >= 0) || (sign < 0 && s.sign < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp copySign(final double s) { + long sb = Double.doubleToLongBits(s); + if ((sign >= 0 && sb >= 0) || (sign < 0 && sb < 0)) { // Sign is currently OK + return this; + } + return negate(); // flip sign + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp scalb(final int n) { + return multiply(DfpMath.pow(getTwo(), n)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp hypot(final Dfp y) { + return multiply(this).add(y.multiply(y)).sqrt(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp cbrt() { + return rootN(3); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp rootN(final int n) { + return (sign >= 0) ? + DfpMath.pow(this, getOne().divide(n)) : + DfpMath.pow(negate(), getOne().divide(n)).negate(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp pow(final double p) { + return DfpMath.pow(this, newInstance(p)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp pow(final int n) { + return DfpMath.pow(this, n); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp pow(final Dfp e) { + return DfpMath.pow(this, e); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp exp() { + return DfpMath.exp(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp expm1() { + return DfpMath.exp(this).subtract(getOne()); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp log() { + return DfpMath.log(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp log1p() { + return DfpMath.log(this.add(getOne())); + } + +// TODO: deactivate this implementation (and return type) in 4.0 + /** Get the exponent of the greatest power of 10 that is less than or equal to abs(this). + * @return integer base 10 logarithm + * @deprecated as of 3.2, replaced by {@link #intLog10()}, in 4.0 the return type + * will be changed to Dfp + */ + @Deprecated + public int log10() { + return intLog10(); + } + +// TODO: activate this implementation (and return type) in 4.0 +// /** {@inheritDoc} +// * @since 3.2 +// */ +// public Dfp log10() { +// return DfpMath.log(this).divide(DfpMath.log(newInstance(10))); +// } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp cos() { + return DfpMath.cos(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp sin() { + return DfpMath.sin(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp tan() { + return DfpMath.tan(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp acos() { + return DfpMath.acos(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp asin() { + return DfpMath.asin(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp atan() { + return DfpMath.atan(this); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp atan2(final Dfp x) + throws DimensionMismatchException { + + // compute r = sqrt(x^2+y^2) + final Dfp r = x.multiply(x).add(multiply(this)).sqrt(); + + if (x.sign >= 0) { + + // compute atan2(y, x) = 2 atan(y / (r + x)) + return getTwo().multiply(divide(r.add(x)).atan()); + + } else { + + // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x)) + final Dfp tmp = getTwo().multiply(divide(r.subtract(x)).atan()); + final Dfp pmPi = newInstance((tmp.sign <= 0) ? -FastMath.PI : FastMath.PI); + return pmPi.subtract(tmp); + + } + + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp cosh() { + return DfpMath.exp(this).add(DfpMath.exp(negate())).divide(2); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp sinh() { + return DfpMath.exp(this).subtract(DfpMath.exp(negate())).divide(2); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp tanh() { + final Dfp ePlus = DfpMath.exp(this); + final Dfp eMinus = DfpMath.exp(negate()); + return ePlus.subtract(eMinus).divide(ePlus.add(eMinus)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp acosh() { + return multiply(this).subtract(getOne()).sqrt().add(this).log(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp asinh() { + return multiply(this).add(getOne()).sqrt().add(this).log(); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp atanh() { + return getOne().add(this).divide(getOne().subtract(this)).log().divide(2); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final Dfp[] a, final Dfp[] b) + throws DimensionMismatchException { + if (a.length != b.length) { + throw new DimensionMismatchException(a.length, b.length); + } + Dfp r = getZero(); + for (int i = 0; i < a.length; ++i) { + r = r.add(a[i].multiply(b[i])); + } + return r; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final double[] a, final Dfp[] b) + throws DimensionMismatchException { + if (a.length != b.length) { + throw new DimensionMismatchException(a.length, b.length); + } + Dfp r = getZero(); + for (int i = 0; i < a.length; ++i) { + r = r.add(b[i].multiply(a[i])); + } + return r; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final Dfp a1, final Dfp b1, final Dfp a2, final Dfp b2) { + return a1.multiply(b1).add(a2.multiply(b2)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final double a1, final Dfp b1, final double a2, final Dfp b2) { + return b1.multiply(a1).add(b2.multiply(a2)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final Dfp a1, final Dfp b1, + final Dfp a2, final Dfp b2, + final Dfp a3, final Dfp b3) { + return a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final double a1, final Dfp b1, + final double a2, final Dfp b2, + final double a3, final Dfp b3) { + return b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final Dfp a1, final Dfp b1, final Dfp a2, final Dfp b2, + final Dfp a3, final Dfp b3, final Dfp a4, final Dfp b4) { + return a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Dfp linearCombination(final double a1, final Dfp b1, final double a2, final Dfp b2, + final double a3, final Dfp b3, final double a4, final Dfp b4) { + return b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpDec.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpDec.java new file mode 100644 index 000000000..d3d7170f7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpDec.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.dfp; + +/** Subclass of {@link Dfp} which hides the radix-10000 artifacts of the superclass. + * This should give outward appearances of being a decimal number with DIGITS*4-3 + * decimal digits. This class can be subclassed to appear to be an arbitrary number + * of decimal digits less than DIGITS*4-3. + * @since 2.2 + */ +public class DfpDec extends Dfp { + + /** Makes an instance with a value of zero. + * @param factory factory linked to this instance + */ + protected DfpDec(final DfpField factory) { + super(factory); + } + + /** Create an instance from a byte value. + * @param factory factory linked to this instance + * @param x value to convert to an instance + */ + protected DfpDec(final DfpField factory, byte x) { + super(factory, x); + } + + /** Create an instance from an int value. + * @param factory factory linked to this instance + * @param x value to convert to an instance + */ + protected DfpDec(final DfpField factory, int x) { + super(factory, x); + } + + /** Create an instance from a long value. + * @param factory factory linked to this instance + * @param x value to convert to an instance + */ + protected DfpDec(final DfpField factory, long x) { + super(factory, x); + } + + /** Create an instance from a double value. + * @param factory factory linked to this instance + * @param x value to convert to an instance + */ + protected DfpDec(final DfpField factory, double x) { + super(factory, x); + round(0); + } + + /** Copy constructor. + * @param d instance to copy + */ + public DfpDec(final Dfp d) { + super(d); + round(0); + } + + /** Create an instance from a String representation. + * @param factory factory linked to this instance + * @param s string representation of the instance + */ + protected DfpDec(final DfpField factory, final String s) { + super(factory, s); + round(0); + } + + /** Creates an instance with a non-finite value. + * @param factory factory linked to this instance + * @param sign sign of the Dfp to create + * @param nans code of the value, must be one of {@link #INFINITE}, + * {@link #SNAN}, {@link #QNAN} + */ + protected DfpDec(final DfpField factory, final byte sign, final byte nans) { + super(factory, sign, nans); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance() { + return new DfpDec(getField()); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final byte x) { + return new DfpDec(getField(), x); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final int x) { + return new DfpDec(getField(), x); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final long x) { + return new DfpDec(getField(), x); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final double x) { + return new DfpDec(getField(), x); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final Dfp d) { + + // make sure we don't mix number with different precision + if (getField().getRadixDigits() != d.getField().getRadixDigits()) { + getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, "newInstance", d, result); + } + + return new DfpDec(d); + + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final String s) { + return new DfpDec(getField(), s); + } + + /** {@inheritDoc} */ + @Override + public Dfp newInstance(final byte sign, final byte nans) { + return new DfpDec(getField(), sign, nans); + } + + /** Get the number of decimal digits this class is going to represent. + * Default implementation returns {@link #getRadixDigits()}*4-3. Subclasses can + * override this to return something less. + * @return number of decimal digits this class is going to represent + */ + protected int getDecimalDigits() { + return getRadixDigits() * 4 - 3; + } + + /** {@inheritDoc} */ + @Override + protected int round(int in) { + + int msb = mant[mant.length-1]; + if (msb == 0) { + // special case -- this == zero + return 0; + } + + int cmaxdigits = mant.length * 4; + int lsbthreshold = 1000; + while (lsbthreshold > msb) { + lsbthreshold /= 10; + cmaxdigits --; + } + + + final int digits = getDecimalDigits(); + final int lsbshift = cmaxdigits - digits; + final int lsd = lsbshift / 4; + + lsbthreshold = 1; + for (int i = 0; i < lsbshift % 4; i++) { + lsbthreshold *= 10; + } + + final int lsb = mant[lsd]; + + if (lsbthreshold <= 1 && digits == 4 * mant.length - 3) { + return super.round(in); + } + + int discarded = in; // not looking at this after this point + final int n; + if (lsbthreshold == 1) { + // look to the next digit for rounding + n = (mant[lsd-1] / 1000) % 10; + mant[lsd-1] %= 1000; + discarded |= mant[lsd-1]; + } else { + n = (lsb * 10 / lsbthreshold) % 10; + discarded |= lsb % (lsbthreshold/10); + } + + for (int i = 0; i < lsd; i++) { + discarded |= mant[i]; // need to know if there are any discarded bits + mant[i] = 0; + } + + mant[lsd] = lsb / lsbthreshold * lsbthreshold; + + final boolean inc; + switch (getField().getRoundingMode()) { + case ROUND_DOWN: + inc = false; + break; + + case ROUND_UP: + inc = (n != 0) || (discarded != 0); // round up if n!=0 + break; + + case ROUND_HALF_UP: + inc = n >= 5; // round half up + break; + + case ROUND_HALF_DOWN: + inc = n > 5; // round half down + break; + + case ROUND_HALF_EVEN: + inc = (n > 5) || + (n == 5 && discarded != 0) || + (n == 5 && discarded == 0 && ((lsb / lsbthreshold) & 1) == 1); // round half-even + break; + + case ROUND_HALF_ODD: + inc = (n > 5) || + (n == 5 && discarded != 0) || + (n == 5 && discarded == 0 && ((lsb / lsbthreshold) & 1) == 0); // round half-odd + break; + + case ROUND_CEIL: + inc = (sign == 1) && (n != 0 || discarded != 0); // round ceil + break; + + case ROUND_FLOOR: + default: + inc = (sign == -1) && (n != 0 || discarded != 0); // round floor + break; + } + + if (inc) { + // increment if necessary + int rh = lsbthreshold; + for (int i = lsd; i < mant.length; i++) { + final int r = mant[i] + rh; + rh = r / RADIX; + mant[i] = r % RADIX; + } + + if (rh != 0) { + shiftRight(); + mant[mant.length-1]=rh; + } + } + + // Check for exceptional cases and raise signals if necessary + if (exp < MIN_EXP) { + // Gradual Underflow + getField().setIEEEFlagsBits(DfpField.FLAG_UNDERFLOW); + return DfpField.FLAG_UNDERFLOW; + } + + if (exp > MAX_EXP) { + // Overflow + getField().setIEEEFlagsBits(DfpField.FLAG_OVERFLOW); + return DfpField.FLAG_OVERFLOW; + } + + if (n != 0 || discarded != 0) { + // Inexact + getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT); + return DfpField.FLAG_INEXACT; + } + return 0; + } + + /** {@inheritDoc} */ + @Override + public Dfp nextAfter(Dfp x) { + + final String trapName = "nextAfter"; + + // make sure we don't mix number with different precision + if (getField().getRadixDigits() != x.getField().getRadixDigits()) { + getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = newInstance(getZero()); + result.nans = QNAN; + return dotrap(DfpField.FLAG_INVALID, trapName, x, result); + } + + boolean up = false; + Dfp result; + Dfp inc; + + // if this is greater than x + if (this.lessThan(x)) { + up = true; + } + + if (equals(x)) { + return newInstance(x); + } + + if (lessThan(getZero())) { + up = !up; + } + + if (up) { + inc = power10(intLog10() - getDecimalDigits() + 1); + inc = copysign(inc, this); + + if (this.equals(getZero())) { + inc = power10K(MIN_EXP-mant.length-1); + } + + if (inc.equals(getZero())) { + result = copysign(newInstance(getZero()), this); + } else { + result = add(inc); + } + } else { + inc = power10(intLog10()); + inc = copysign(inc, this); + + if (this.equals(inc)) { + inc = inc.divide(power10(getDecimalDigits())); + } else { + inc = inc.divide(power10(getDecimalDigits() - 1)); + } + + if (this.equals(getZero())) { + inc = power10K(MIN_EXP-mant.length-1); + } + + if (inc.equals(getZero())) { + result = copysign(newInstance(getZero()), this); + } else { + result = subtract(inc); + } + } + + if (result.classify() == INFINITE && this.classify() != INFINITE) { + getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT); + result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result); + } + + if (result.equals(getZero()) && this.equals(getZero()) == false) { + getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT); + result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result); + } + + return result; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpField.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpField.java new file mode 100644 index 000000000..baa8196f6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpField.java @@ -0,0 +1,757 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.dfp; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** Field for Decimal floating point instances. + * @since 2.2 + */ +public class DfpField implements Field { + + /** 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; + + // Note: the static strings are set up (once) by the ctor and @GuardedBy("DfpField.class") + + /** 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. + *

+ * Note that since the {@link Dfp} class uses 10000 as its radix, each radix + * digit is equivalent to 4 decimal digits. This implies that asking for + * 13, 14, 15 or 16 decimal digits will really lead to a 4 radix 10000 digits in + * all cases. + *

+ * @param decimalDigits minimal number of decimal digits. + */ + public DfpField(final int decimalDigits) { + this(decimalDigits, true); + } + + /** Create a factory for the specified number of radix digits. + *

+ * Note that since the {@link Dfp} class uses 10000 as its radix, each radix + * digit is equivalent to 4 decimal digits. This implies that asking for + * 13, 14, 15 or 16 decimal digits will really lead to a 4 radix 10000 digits in + * all cases. + *

+ * @param decimalDigits minimal number of decimal digits + * @param computeConstants if true, the transcendental constants for the given precision + * must be computed (setting this flag to false is RESERVED for the internal recursive call) + */ + private DfpField(final int decimalDigits, final boolean computeConstants) { + + this.radixDigits = (decimalDigits < 13) ? 4 : (decimalDigits + 3) / 4; + this.rMode = RoundingMode.ROUND_HALF_EVEN; + this.ieeeFlags = 0; + this.zero = new Dfp(this, 0); + this.one = new Dfp(this, 1); + this.two = new Dfp(this, 2); + + if (computeConstants) { + // set up transcendental constants + synchronized (DfpField.class) { + + // as a heuristic to circumvent Table-Maker's Dilemma, we set the string + // representation of the constants to be at least 3 times larger than the + // number of decimal digits, also as an attempt to really compute these + // constants only once, we set a minimum number of digits + computeStringConstants((decimalDigits < 67) ? 200 : (3 * decimalDigits)); + + // set up the constants at current field accuracy + sqr2 = new Dfp(this, sqr2String); + sqr2Split = split(sqr2String); + sqr2Reciprocal = new Dfp(this, sqr2ReciprocalString); + sqr3 = new Dfp(this, sqr3String); + sqr3Reciprocal = new Dfp(this, sqr3ReciprocalString); + pi = new Dfp(this, piString); + piSplit = split(piString); + e = new Dfp(this, eString); + eSplit = split(eString); + ln2 = new Dfp(this, ln2String); + ln2Split = split(ln2String); + ln5 = new Dfp(this, ln5String); + ln5Split = split(ln5String); + ln10 = new Dfp(this, ln10String); + + } + } else { + // dummy settings for unused constants + sqr2 = null; + sqr2Split = null; + sqr2Reciprocal = null; + sqr3 = null; + sqr3Reciprocal = null; + pi = null; + piSplit = null; + e = null; + eSplit = null; + ln2 = null; + ln2Split = null; + ln5 = null; + ln5Split = null; + ln10 = null; + } + + } + + /** Get the number of radix digits of the {@link Dfp} instances built by this factory. + * @return number of radix digits + */ + public int getRadixDigits() { + return radixDigits; + } + + /** Set the rounding mode. + * If not set, the default value is {@link RoundingMode#ROUND_HALF_EVEN}. + * @param mode desired rounding mode + * Note that the rounding mode is common to all {@link Dfp} instances + * belonging to the current {@link DfpField} in the system and will + * affect all future calculations. + */ + public void setRoundingMode(final RoundingMode mode) { + rMode = mode; + } + + /** Get the current rounding mode. + * @return current rounding mode + */ + public RoundingMode getRoundingMode() { + return rMode; + } + + /** Get the IEEE 854 status flags. + * @return IEEE 854 status flags + * @see #clearIEEEFlags() + * @see #setIEEEFlags(int) + * @see #setIEEEFlagsBits(int) + * @see #FLAG_INVALID + * @see #FLAG_DIV_ZERO + * @see #FLAG_OVERFLOW + * @see #FLAG_UNDERFLOW + * @see #FLAG_INEXACT + */ + public int getIEEEFlags() { + return ieeeFlags; + } + + /** Clears the IEEE 854 status flags. + * @see #getIEEEFlags() + * @see #setIEEEFlags(int) + * @see #setIEEEFlagsBits(int) + * @see #FLAG_INVALID + * @see #FLAG_DIV_ZERO + * @see #FLAG_OVERFLOW + * @see #FLAG_UNDERFLOW + * @see #FLAG_INEXACT + */ + public void clearIEEEFlags() { + ieeeFlags = 0; + } + + /** Sets the IEEE 854 status flags. + * @param flags desired value for the flags + * @see #getIEEEFlags() + * @see #clearIEEEFlags() + * @see #setIEEEFlagsBits(int) + * @see #FLAG_INVALID + * @see #FLAG_DIV_ZERO + * @see #FLAG_OVERFLOW + * @see #FLAG_UNDERFLOW + * @see #FLAG_INEXACT + */ + public void setIEEEFlags(final int flags) { + ieeeFlags = flags & (FLAG_INVALID | FLAG_DIV_ZERO | FLAG_OVERFLOW | FLAG_UNDERFLOW | FLAG_INEXACT); + } + + /** Sets some bits in the IEEE 854 status flags, without changing the already set bits. + *

+ * Calling this method is equivalent to call {@code setIEEEFlags(getIEEEFlags() | bits)} + *

+ * @param bits bits to set + * @see #getIEEEFlags() + * @see #clearIEEEFlags() + * @see #setIEEEFlags(int) + * @see #FLAG_INVALID + * @see #FLAG_DIV_ZERO + * @see #FLAG_OVERFLOW + * @see #FLAG_UNDERFLOW + * @see #FLAG_INEXACT + */ + public void setIEEEFlagsBits(final int bits) { + ieeeFlags |= bits & (FLAG_INVALID | FLAG_DIV_ZERO | FLAG_OVERFLOW | FLAG_UNDERFLOW | FLAG_INEXACT); + } + + /** Makes a {@link Dfp} with a value of 0. + * @return a new {@link Dfp} with a value of 0 + */ + public Dfp newDfp() { + return new Dfp(this); + } + + /** Create an instance from a byte value. + * @param x value to convert to an instance + * @return a new {@link Dfp} with the same value as x + */ + public Dfp newDfp(final byte x) { + return new Dfp(this, x); + } + + /** Create an instance from an int value. + * @param x value to convert to an instance + * @return a new {@link Dfp} with the same value as x + */ + public Dfp newDfp(final int x) { + return new Dfp(this, x); + } + + /** Create an instance from a long value. + * @param x value to convert to an instance + * @return a new {@link Dfp} with the same value as x + */ + public Dfp newDfp(final long x) { + return new Dfp(this, x); + } + + /** Create an instance from a double value. + * @param x value to convert to an instance + * @return a new {@link Dfp} with the same value as x + */ + public Dfp newDfp(final double x) { + return new Dfp(this, x); + } + + /** Copy constructor. + * @param d instance to copy + * @return a new {@link Dfp} with the same value as d + */ + public Dfp newDfp(Dfp d) { + return new Dfp(d); + } + + /** Create a {@link Dfp} given a String representation. + * @param s string representation of the instance + * @return a new {@link Dfp} parsed from specified string + */ + public Dfp newDfp(final String s) { + return new Dfp(this, s); + } + + /** Creates a {@link Dfp} with a non-finite value. + * @param sign sign of the Dfp to create + * @param nans code of the value, must be one of {@link Dfp#INFINITE}, + * {@link Dfp#SNAN}, {@link Dfp#QNAN} + * @return a new {@link Dfp} with a non-finite value + */ + public Dfp newDfp(final byte sign, final byte nans) { + return new Dfp(this, sign, nans); + } + + /** Get the constant 0. + * @return a {@link Dfp} with value 0 + */ + public Dfp getZero() { + return zero; + } + + /** Get the constant 1. + * @return a {@link Dfp} with value 1 + */ + public Dfp getOne() { + return one; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return Dfp.class; + } + + /** 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 += 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpMath.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpMath.java new file mode 100644 index 000000000..c33db4bfb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/DfpMath.java @@ -0,0 +1,962 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.dfp; + +/** Mathematical routines for use with {@link Dfp}. + * The constants are defined in {@link DfpField} + * @since 2.2 + */ +public class DfpMath { + + /** Name for traps triggered by pow. */ + private static final String POW_TRAP = "pow"; + + /** + * Private Constructor. + */ + private DfpMath() { + } + + /** Breaks a string representation up into two dfp's. + *

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 basea + */ + protected static Dfp splitPow(final Dfp[] base, int a) { + boolean invert = false; + + Dfp[] r = new Dfp[2]; + + Dfp[] result = new Dfp[2]; + result[0] = base[0].getOne(); + result[1] = base[0].getZero(); + + if (a == 0) { + // Special case a = 0 + return result[0].add(result[1]); + } + + if (a < 0) { + // If a is less than zero + invert = true; + a = -a; + } + + // Exponentiate by successive squaring + do { + r[0] = new Dfp(base[0]); + r[1] = new Dfp(base[1]); + int trial = 1; + + int prevtrial; + while (true) { + prevtrial = trial; + trial *= 2; + if (trial > a) { + break; + } + r = splitMult(r, r); + } + + trial = prevtrial; + + a -= trial; + result = splitMult(result, r); + + } while (a >= 1); + + result[0] = result[0].add(result[1]); + + if (invert) { + result[0] = base[0].getOne().divide(result[0]); + } + + return result[0]; + + } + + /** Raises base to the power a by successive squaring. + * @param base number to raise + * @param a power + * @return basea + */ + public static Dfp pow(Dfp base, int a) + { + boolean invert = false; + + Dfp result = base.getOne(); + + if (a == 0) { + // Special case + return result; + } + + if (a < 0) { + invert = true; + a = -a; + } + + // Exponentiate by successive squaring + do { + Dfp r = new Dfp(base); + Dfp prevr; + int trial = 1; + int prevtrial; + + do { + prevr = new Dfp(r); + prevtrial = trial; + r = r.multiply(r); + trial *= 2; + } while (a>trial); + + r = prevr; + trial = prevtrial; + + a -= trial; + result = result.multiply(r); + + } while (a >= 1); + + if (invert) { + result = base.getOne().divide(result); + } + + return base.newInstance(result); + + } + + /** Computes e to the given power. + * a is broken into two parts, such that a = n+m where n is an integer. + * We use pow() to compute en and a Taylor series to compute + * em. We return e*n × em + * @param a power at which e should be raised + * @return ea + */ + 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 ea + */ + 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 += 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.

+ * + * Uses the following method:

+ * + *

    + *
  1. Set u = rint(y), v = y-u + *
  2. Compute a = v * ln(x) + *
  3. Compute b = rint( a/ln(2) ) + *
  4. Compute c = a - b*ln(2) + *
  5. xy = xu * 2b * ec + *
+ * if |y| > 1e8, then we compute by exp(y*ln(x))

+ * + * Special Cases

+ *

    + *
  • if y is 0.0 or -0.0 then result is 1.0 + *
  • if y is 1.0 then result is x + *
  • if y is NaN then result is NaN + *
  • if x is NaN and y is not zero then result is NaN + *
  • if |x| > 1.0 and y is +Infinity then result is +Infinity + *
  • if |x| < 1.0 and y is -Infinity then result is +Infinity + *
  • if |x| > 1.0 and y is -Infinity then result is +0 + *
  • if |x| < 1.0 and y is +Infinity then result is +0 + *
  • if |x| = 1.0 and y is +/-Infinity then result is NaN + *
  • if x = +0 and y > 0 then result is +0 + *
  • if x = +Inf and y < 0 then result is +0 + *
  • if x = +0 and y < 0 then result is +Inf + *
  • if x = +Inf and y > 0 then result is +Inf + *
  • if x = -0 and y > 0, finite, not odd integer then result is +0 + *
  • if x = -0 and y < 0, finite, and odd integer then result is -Inf + *
  • if x = -Inf and y > 0, finite, and odd integer then result is -Inf + *
  • if x = -0 and y < 0, not finite odd integer then result is +Inf + *
  • if x = -Inf and y > 0, not finite odd integer then result is +Inf + *
  • if x < 0 and y > 0, finite, and odd integer then result is -(|x|y) + *
  • if x < 0 and y > 0, finite, and not integer then result is NaN + *
+ * @param x base to be raised + * @param y power to which base should be raised + * @return xy + */ + public static Dfp pow(Dfp x, final Dfp y) { + + // make sure we don't mix number with different precision + if (x.getField().getRadixDigits() != y.getField().getRadixDigits()) { + x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + final Dfp result = x.newInstance(x.getZero()); + result.nans = Dfp.QNAN; + return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, result); + } + + final Dfp zero = x.getZero(); + final Dfp one = x.getOne(); + final Dfp two = x.getTwo(); + boolean invert = false; + int ui; + + /* Check for special cases */ + if (y.equals(zero)) { + return x.newInstance(one); + } + + if (y.equals(one)) { + if (x.isNaN()) { + // Test for NaNs + x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x); + } + return x; + } + + if (x.isNaN() || y.isNaN()) { + // Test for NaNs + x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte)1, Dfp.QNAN)); + } + + // X == 0 + if (x.equals(zero)) { + if (Dfp.copysign(one, x).greaterThan(zero)) { + // X == +0 + if (y.greaterThan(zero)) { + return x.newInstance(zero); + } else { + return x.newInstance(x.newInstance((byte)1, Dfp.INFINITE)); + } + } else { + // X == -0 + if (y.classify() == Dfp.FINITE && y.rint().equals(y) && !y.remainder(two).equals(zero)) { + // If y is odd integer + if (y.greaterThan(zero)) { + return x.newInstance(zero.negate()); + } else { + return x.newInstance(x.newInstance((byte)-1, Dfp.INFINITE)); + } + } else { + // Y is not odd integer + if (y.greaterThan(zero)) { + return x.newInstance(zero); + } else { + return x.newInstance(x.newInstance((byte)1, Dfp.INFINITE)); + } + } + } + } + + if (x.lessThan(zero)) { + // Make x positive, but keep track of it + x = x.negate(); + invert = true; + } + + if (x.greaterThan(one) && y.classify() == Dfp.INFINITE) { + if (y.greaterThan(zero)) { + return y; + } else { + return x.newInstance(zero); + } + } + + if (x.lessThan(one) && y.classify() == Dfp.INFINITE) { + if (y.greaterThan(zero)) { + return x.newInstance(zero); + } else { + return x.newInstance(Dfp.copysign(y, one)); + } + } + + if (x.equals(one) && y.classify() == Dfp.INFINITE) { + x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte)1, Dfp.QNAN)); + } + + if (x.classify() == Dfp.INFINITE) { + // x = +/- inf + if (invert) { + // negative infinity + if (y.classify() == Dfp.FINITE && y.rint().equals(y) && !y.remainder(two).equals(zero)) { + // If y is odd integer + if (y.greaterThan(zero)) { + return x.newInstance(x.newInstance((byte)-1, Dfp.INFINITE)); + } else { + return x.newInstance(zero.negate()); + } + } else { + // Y is not odd integer + if (y.greaterThan(zero)) { + return x.newInstance(x.newInstance((byte)1, Dfp.INFINITE)); + } else { + return x.newInstance(zero); + } + } + } else { + // positive infinity + if (y.greaterThan(zero)) { + return x; + } else { + return x.newInstance(zero); + } + } + } + + if (invert && !y.rint().equals(y)) { + x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID); + return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte)1, Dfp.QNAN)); + } + + // End special cases + + Dfp r; + if (y.lessThan(x.newInstance(100000000)) && y.greaterThan(x.newInstance(-100000000))) { + final Dfp u = y.rint(); + ui = u.intValue(); + + final Dfp v = y.subtract(u); + + if (v.unequal(zero)) { + final Dfp a = v.multiply(log(x)); + final Dfp b = a.divide(x.getField().getLn2()).rint(); + + final Dfp c = a.subtract(b.multiply(x.getField().getLn2())); + r = splitPow(split(x), ui); + r = r.multiply(pow(two, b.intValue())); + r = r.multiply(exp(c)); + } else { + r = splitPow(split(x), ui); + } + } else { + // very large exponent. |y| > 1e8 + r = exp(log(x).multiply(y)); + } + + if (invert && y.rint().equals(y) && !y.remainder(two).equals(zero)) { + // if y is odd integer + r = r.negate(); + } + + return x.newInstance(r); + + } + + /** Computes sin(a) Used when 0 < a < pi/4. + * Uses the classic Taylor series. x - x**3/3! + x**5/5! ... + * @param a number from which sine is desired, in split form + * @return sin(a) + */ + protected static Dfp sinInternal(Dfp a[]) { + + Dfp c = a[0].add(a[1]); + Dfp y = c; + c = c.multiply(c); + Dfp x = y; + Dfp fact = a[0].getOne(); + Dfp py = new Dfp(y); + + for (int i = 3; i < 90; i += 2) { + x = x.multiply(c); + x = x.negate(); + + fact = fact.divide((i-1)*i); // 1 over fact + y = y.add(x.multiply(fact)); + if (y.equals(py)) { + break; + } + py = new Dfp(y); + } + + return y; + + } + + /** Computes cos(a) Used when 0 < a < pi/4. + * Uses the classic Taylor series for cosine. 1 - x**2/2! + x**4/4! ... + * @param a number from which cosine is desired, in split form + * @return cos(a) + */ + protected static Dfp cosInternal(Dfp a[]) { + final Dfp one = a[0].getOne(); + + + Dfp x = one; + Dfp y = one; + Dfp c = a[0].add(a[1]); + c = c.multiply(c); + + Dfp fact = one; + Dfp py = new Dfp(y); + + for (int i = 2; i < 90; i += 2) { + x = x.multiply(c); + x = x.negate(); + + fact = fact.divide((i - 1) * i); // 1 over fact + + y = y.add(x.multiply(fact)); + if (y.equals(py)) { + break; + } + py = new Dfp(y); + } + + return y; + + } + + /** computes the sine of the argument. + * @param a number from which sine is desired + * @return sin(a) + */ + public static Dfp sin(final Dfp a) { + final Dfp pi = a.getField().getPi(); + final Dfp zero = a.getField().getZero(); + boolean neg = false; + + /* First reduce the argument to the range of +/- PI */ + Dfp x = a.remainder(pi.multiply(2)); + + /* if x < 0 then apply identity sin(-x) = -sin(x) */ + /* This puts x in the range 0 < x < PI */ + if (x.lessThan(zero)) { + x = x.negate(); + neg = true; + } + + /* Since sine(x) = sine(pi - x) we can reduce the range to + * 0 < x < pi/2 + */ + + if (x.greaterThan(pi.divide(2))) { + x = pi.subtract(x); + } + + Dfp y; + if (x.lessThan(pi.divide(4))) { + y = sinInternal(split(x)); + } else { + final Dfp c[] = new Dfp[2]; + final Dfp[] piSplit = a.getField().getPiSplit(); + c[0] = piSplit[0].divide(2).subtract(x); + c[1] = piSplit[1].divide(2); + y = cosInternal(c); + } + + if (neg) { + y = y.negate(); + } + + return a.newInstance(y); + + } + + /** computes the cosine of the argument. + * @param a number from which cosine is desired + * @return cos(a) + */ + public static Dfp cos(Dfp a) { + final Dfp pi = a.getField().getPi(); + final Dfp zero = a.getField().getZero(); + boolean neg = false; + + /* First reduce the argument to the range of +/- PI */ + Dfp x = a.remainder(pi.multiply(2)); + + /* if x < 0 then apply identity cos(-x) = cos(x) */ + /* This puts x in the range 0 < x < PI */ + if (x.lessThan(zero)) { + x = x.negate(); + } + + /* Since cos(x) = -cos(pi - x) we can reduce the range to + * 0 < x < pi/2 + */ + + if (x.greaterThan(pi.divide(2))) { + x = pi.subtract(x); + neg = true; + } + + Dfp y; + if (x.lessThan(pi.divide(4))) { + Dfp c[] = new Dfp[2]; + c[0] = x; + c[1] = zero; + + y = cosInternal(c); + } else { + final Dfp c[] = new Dfp[2]; + final Dfp[] piSplit = a.getField().getPiSplit(); + c[0] = piSplit[0].divide(2).subtract(x); + c[1] = piSplit[1].divide(2); + y = sinInternal(c); + } + + if (neg) { + y = y.negate(); + } + + return a.newInstance(y); + + } + + /** computes the tangent of the argument. + * @param a number from which tangent is desired + * @return tan(a) + */ + public static Dfp tan(final Dfp a) { + return sin(a).divide(cos(a)); + } + + /** computes the arc-tangent of the argument. + * @param a number from which arc-tangent is desired + * @return atan(a) + */ + protected static Dfp atanInternal(final Dfp a) { + + Dfp y = new Dfp(a); + Dfp x = new Dfp(y); + Dfp py = new Dfp(y); + + for (int i = 3; i < 90; i += 2) { + x = x.multiply(a); + x = x.multiply(a); + x = x.negate(); + y = y.add(x.divide(i)); + if (y.equals(py)) { + break; + } + py = new Dfp(y); + } + + return y; + + } + + /** computes the arc tangent of the argument + * + * 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/UnivariateDfpFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/UnivariateDfpFunction.java new file mode 100644 index 000000000..b68528ff0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/UnivariateDfpFunction.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 com.fr.third.org.apache.commons.math3.dfp; + +import com.fr.third.org.apache.commons.math3.analysis.RealFieldUnivariateFunction; + +/** + * An interface representing a univariate {@link Dfp} function. + * @deprecated as of 3.6, replaced with {@link RealFieldUnivariateFunction} + */ +@Deprecated +public interface UnivariateDfpFunction { + + /** + * Compute the value of the function. + * + * @param x Point at which the function value should be computed. + * @return the value. + * @throws IllegalArgumentException when the activated method itself can + * ascertain that preconditions, specified in the API expressed at the + * level of the activated method, have been violated. In the vast + * majority of cases where Commons-Math throws IllegalArgumentException, + * it is the result of argument checking of actual parameters immediately + * passed to a method. + */ + Dfp value(Dfp x); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/package-info.java new file mode 100644 index 000000000..7c80e3447 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/dfp/package-info.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. + */ +/** + * + * Decimal floating point library for Java + * + *

Another floating point class. This one is built using radix 10000 + * which is 104, so its almost decimal.

+ * + *

The design goals here are: + *

    + *
  1. Decimal math, or close to it
  2. + *
  3. Settable precision (but no mix between numbers using different settings)
  4. + *
  5. Portability. Code should be keep as portable as possible.
  6. + *
  7. Performance
  8. + *
  9. Accuracy - Results should always be +/- 1 ULP for basic + * algebraic operation
  10. + *
  11. Comply with IEEE 854-1987 as much as possible. + * (See IEEE 854-1987 notes below)
  12. + *

+ * + *

Trade offs: + *

    + *
  1. Memory foot print. I'm using more memory than necessary to + * represent numbers to get better performance.
  2. + *
  3. 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.
  4. + *

+ * + *

Numbers are represented in the following form: + *

+ * n  =  sign × mant × (radix)exp;

+ *
+ * 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

+ * + *

IEEE 854-1987 Notes and differences

+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + *

IEEE 854 defines that the implied radix point lies just to the right + * of the most significant digit and to the left of the remaining digits. + * This implementation puts the implied radix point to the left of all + * digits including the most significant one. The most significant digit + * here is the one just to the right of the radix point. This is a fine + * detail and is really only a matter of definition. Any side effects of + * this can be rendered invisible by a subclass.

+ * + */ +package com.fr.third.org.apache.commons.math3.dfp; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java new file mode 100644 index 000000000..4b8806fae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomDataImpl; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.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. + * + */ +public abstract class AbstractIntegerDistribution implements IntegerDistribution, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1146319659338487221L; + + /** + * RandomData instance used to generate samples from the distribution. + * @deprecated As of 3.1, to be removed in 4.0. Please use the + * {@link #random} instance variable instead. + */ + @Deprecated + protected final RandomDataImpl randomData = + new RandomDataImpl(); + + /** + * RNG instance used to generate samples from the distribution. + * @since 3.1 + */ + protected final RandomGenerator random; + + /** + * @deprecated As of 3.1, to be removed in 4.0. Please use + * {@link #AbstractIntegerDistribution(RandomGenerator)} instead. + */ + @Deprecated + protected AbstractIntegerDistribution() { + // Legacy users are only allowed to access the deprecated "randomData". + // New users are forbidden to use this constructor. + random = null; + } + + /** + * @param rng Random number generator. + * @since 3.1 + */ + protected AbstractIntegerDistribution(RandomGenerator rng) { + random = rng; + } + + /** + * {@inheritDoc} + * + * The default implementation uses the identity + *

{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}

+ */ + public double cumulativeProbability(int x0, int x1) throws NumberIsTooLargeException { + if (x1 < x0) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, + x0, x1, true); + } + return cumulativeProbability(x1) - cumulativeProbability(x0); + } + + /** + * {@inheritDoc} + * + * The default implementation returns + *
    + *
  • {@link #getSupportLowerBound()} for {@code p = 0},
  • + *
  • {@link #getSupportUpperBound()} for {@code p = 1}, and
  • + *
  • {@link #solveInverseCumulativeProbability(double, int, int)} for + * {@code 0 < p < 1}.
  • + *
+ */ + public int inverseCumulativeProbability(final double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + + int lower = getSupportLowerBound(); + if (p == 0.0) { + return lower; + } + if (lower == Integer.MIN_VALUE) { + if (checkedCumulativeProbability(lower) >= p) { + return lower; + } + } else { + lower -= 1; // this ensures cumulativeProbability(lower) < p, which + // is important for the solving step + } + + int upper = getSupportUpperBound(); + if (p == 1.0) { + return upper; + } + + // use the one-sided Chebyshev inequality to narrow the bracket + // cf. AbstractRealDistribution.inverseCumulativeProbability(double) + final double mu = getNumericalMean(); + final double sigma = FastMath.sqrt(getNumericalVariance()); + final boolean chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) || + Double.isInfinite(sigma) || Double.isNaN(sigma) || sigma == 0.0); + if (chebyshevApplies) { + double k = FastMath.sqrt((1.0 - p) / p); + double tmp = mu - k * sigma; + if (tmp > lower) { + lower = ((int) FastMath.ceil(tmp)) - 1; + } + k = 1.0 / k; + tmp = mu + k * sigma; + if (tmp < upper) { + upper = ((int) FastMath.ceil(tmp)) - 1; + } + } + + return solveInverseCumulativeProbability(p, lower, upper); + } + + /** + * This is a utility function used by {@link + * #inverseCumulativeProbability(double)}. It assumes {@code 0 < p < 1} and + * that the inverse cumulative probability lies in the bracket {@code + * (lower, upper]}. The implementation does simple bisection to find the + * smallest {@code p}-quantile inf{x in Z | P(X<=x) >= p}. + * + * @param p the cumulative probability + * @param lower a value satisfying {@code cumulativeProbability(lower) < p} + * @param upper a value satisfying {@code p <= cumulativeProbability(upper)} + * @return the smallest {@code p}-quantile of this distribution + */ + protected int solveInverseCumulativeProbability(final double p, int lower, int upper) { + while (lower + 1 < upper) { + int xm = (lower + upper) / 2; + if (xm < lower || xm > upper) { + /* + * Overflow. + * There will never be an overflow in both calculation methods + * for xm at the same time + */ + xm = lower + (upper - lower) / 2; + } + + double pm = checkedCumulativeProbability(xm); + if (pm >= p) { + upper = xm; + } else { + lower = xm; + } + } + return upper; + } + + /** {@inheritDoc} */ + public void reseedRandomGenerator(long seed) { + random.setSeed(seed); + randomData.reSeed(seed); + } + + /** + * {@inheritDoc} + * + * The default implementation uses the + * + * inversion method. + */ + public int sample() { + return inverseCumulativeProbability(random.nextDouble()); + } + + /** + * {@inheritDoc} + * + * The default implementation generates the sample by calling + * {@link #sample()} in a loop. + */ + public int[] sample(int sampleSize) { + if (sampleSize <= 0) { + throw new NotStrictlyPositiveException( + LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize); + } + int[] out = new int[sampleSize]; + for (int i = 0; i < sampleSize; i++) { + out[i] = sample(); + } + return out; + } + + /** + * Computes the cumulative probability function and checks for {@code NaN} + * values returned. Throws {@code MathInternalError} if the value is + * {@code NaN}. Rethrows any exception encountered evaluating the cumulative + * probability function. Throws {@code MathInternalError} if the cumulative + * probability function returns {@code NaN}. + * + * @param argument input value + * @return the cumulative probability + * @throws MathInternalError if the cumulative probability is {@code NaN} + */ + private double checkedCumulativeProbability(int argument) + throws MathInternalError { + double result = Double.NaN; + result = cumulativeProbability(argument); + if (Double.isNaN(result)) { + throw new MathInternalError(LocalizedFormats + .DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN, argument); + } + return result; + } + + /** + * For a random variable {@code X} whose values are distributed according to + * this distribution, this method returns {@code log(P(X = x))}, where + * {@code log} is the natural logarithm. In other words, this method + * represents the logarithm of the probability mass function (PMF) for the + * distribution. Note that due to the floating point precision and + * under/overflow issues, this method will for some distributions be more + * precise and faster than computing the logarithm of + * {@link #probability(int)}. + *

+ * The default implementation simply computes the logarithm of {@code probability(x)}.

+ * + * @param x the point at which the PMF is evaluated + * @return the logarithm of the value of the probability mass function at {@code x} + */ + public double logProbability(int x) { + return FastMath.log(probability(x)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.java new file mode 100644 index 000000000..8d9598df9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; + +/** + * Base class for multivariate probability distributions. + * + * @since 3.1 + */ +public abstract class AbstractMultivariateRealDistribution + implements MultivariateRealDistribution { + /** RNG instance used to generate samples from the distribution. */ + protected final RandomGenerator random; + /** The number of dimensions or columns in the multivariate distribution. */ + private final int dimension; + + /** + * @param rng Random number generator. + * @param n Number of dimensions. + */ + protected AbstractMultivariateRealDistribution(RandomGenerator rng, + int n) { + random = rng; + dimension = n; + } + + /** {@inheritDoc} */ + public void reseedRandomGenerator(long seed) { + random.setSeed(seed); + } + + /** {@inheritDoc} */ + public int getDimension() { + return dimension; + } + + /** {@inheritDoc} */ + public abstract double[] sample(); + + /** {@inheritDoc} */ + public double[][] sample(final int sampleSize) { + if (sampleSize <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, + sampleSize); + } + final double[][] out = new double[sampleSize][dimension]; + for (int i = 0; i < sampleSize; i++) { + out[i] = sample(); + } + return out; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractRealDistribution.java new file mode 100644 index 000000000..0bfcc4c39 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/AbstractRealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomDataImpl; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Base class for probability distributions on the reals. + * Default implementations are provided for some of the methods + * that do not vary from distribution to distribution. + * + * @since 3.0 + */ +public abstract class AbstractRealDistribution +implements RealDistribution, Serializable { + /** Default accuracy. */ + public static final double SOLVER_DEFAULT_ABSOLUTE_ACCURACY = 1e-6; + /** Serializable version identifier */ + private static final long serialVersionUID = -38038050983108802L; + /** + * RandomData instance used to generate samples from the distribution. + * @deprecated As of 3.1, to be removed in 4.0. Please use the + * {@link #random} instance variable instead. + */ + @Deprecated + protected RandomDataImpl randomData = + new RandomDataImpl(); + + /** + * RNG instance used to generate samples from the distribution. + * @since 3.1 + */ + protected final RandomGenerator random; + + /** Solver absolute accuracy for inverse cumulative computation */ + private double solverAbsoluteAccuracy = SOLVER_DEFAULT_ABSOLUTE_ACCURACY; + + /** + * @deprecated As of 3.1, to be removed in 4.0. Please use + * {@link #AbstractRealDistribution(RandomGenerator)} instead. + */ + @Deprecated + protected AbstractRealDistribution() { + // Legacy users are only allowed to access the deprecated "randomData". + // New users are forbidden to use this constructor. + random = null; + } + /** + * @param rng Random number generator. + * @since 3.1 + */ + protected AbstractRealDistribution(RandomGenerator rng) { + random = rng; + } + + /** + * {@inheritDoc} + * + * The default implementation uses the identity + *

{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}

+ * + * @deprecated As of 3.1 (to be removed in 4.0). Please use + * {@link #probability(double,double)} instead. + */ + @Deprecated + public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException { + return probability(x0, x1); + } + + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(x0 < X <= x1)}. + * + * @param x0 Lower bound (excluded). + * @param x1 Upper bound (included). + * @return the probability that a random variable with this distribution + * takes a value between {@code x0} and {@code x1}, excluding the lower + * and including the upper endpoint. + * @throws NumberIsTooLargeException if {@code x0 > x1}. + * + * The default implementation uses the identity + * {@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)} + * + * @since 3.1 + */ + public double probability(double x0, + double x1) { + if (x0 > x1) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, + x0, x1, true); + } + return cumulativeProbability(x1) - cumulativeProbability(x0); + } + + /** + * {@inheritDoc} + * + * The default implementation returns + *
    + *
  • {@link #getSupportLowerBound()} for {@code p = 0},
  • + *
  • {@link #getSupportUpperBound()} for {@code p = 1}.
  • + *
+ */ + public double inverseCumulativeProbability(final double p) throws OutOfRangeException { + /* + * IMPLEMENTATION NOTES + * -------------------- + * Where applicable, use is made of the one-sided Chebyshev inequality + * to bracket the root. This inequality states that + * P(X - mu >= k * sig) <= 1 / (1 + k^2), + * mu: mean, sig: standard deviation. Equivalently + * 1 - P(X < mu + k * sig) <= 1 / (1 + k^2), + * F(mu + k * sig) >= k^2 / (1 + k^2). + * + * For k = sqrt(p / (1 - p)), we find + * F(mu + k * sig) >= p, + * and (mu + k * sig) is an upper-bound for the root. + * + * Then, introducing Y = -X, mean(Y) = -mu, sd(Y) = sig, and + * P(Y >= -mu + k * sig) <= 1 / (1 + k^2), + * P(-X >= -mu + k * sig) <= 1 / (1 + k^2), + * P(X <= mu - k * sig) <= 1 / (1 + k^2), + * F(mu - k * sig) <= 1 / (1 + k^2). + * + * For k = sqrt((1 - p) / p), we find + * F(mu - k * sig) <= p, + * and (mu - k * sig) is a lower-bound for the root. + * + * In cases where the Chebyshev inequality does not apply, geometric + * progressions 1, 2, 4, ... and -1, -2, -4, ... are used to bracket + * the root. + */ + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + + double lowerBound = getSupportLowerBound(); + if (p == 0.0) { + return lowerBound; + } + + double upperBound = getSupportUpperBound(); + if (p == 1.0) { + return upperBound; + } + + final double mu = getNumericalMean(); + final double sig = FastMath.sqrt(getNumericalVariance()); + final boolean chebyshevApplies; + chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) || + Double.isInfinite(sig) || Double.isNaN(sig)); + + if (lowerBound == Double.NEGATIVE_INFINITY) { + if (chebyshevApplies) { + lowerBound = mu - sig * FastMath.sqrt((1. - p) / p); + } else { + lowerBound = -1.0; + while (cumulativeProbability(lowerBound) >= p) { + lowerBound *= 2.0; + } + } + } + + if (upperBound == Double.POSITIVE_INFINITY) { + if (chebyshevApplies) { + upperBound = mu + sig * FastMath.sqrt(p / (1. - p)); + } else { + upperBound = 1.0; + while (cumulativeProbability(upperBound) < p) { + upperBound *= 2.0; + } + } + } + + final UnivariateFunction toSolve = new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(final double x) { + return cumulativeProbability(x) - p; + } + }; + + double x = UnivariateSolverUtils.solve(toSolve, + lowerBound, + upperBound, + getSolverAbsoluteAccuracy()); + + if (!isSupportConnected()) { + /* Test for plateau. */ + final double dx = getSolverAbsoluteAccuracy(); + if (x - dx >= getSupportLowerBound()) { + double px = cumulativeProbability(x); + if (cumulativeProbability(x - dx) == px) { + upperBound = x; + while (upperBound - lowerBound > dx) { + final double midPoint = 0.5 * (lowerBound + upperBound); + if (cumulativeProbability(midPoint) < px) { + lowerBound = midPoint; + } else { + upperBound = midPoint; + } + } + return upperBound; + } + } + } + return x; + } + + /** + * Returns the solver absolute accuracy for inverse cumulative computation. + * You can override this method in order to use a Brent solver with an + * absolute accuracy different from the default. + * + * @return the maximum absolute error in inverse cumulative probability estimates + */ + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** {@inheritDoc} */ + public void reseedRandomGenerator(long seed) { + random.setSeed(seed); + randomData.reSeed(seed); + } + + /** + * {@inheritDoc} + * + * The default implementation uses the + * + * inversion method. + * + */ + public double sample() { + return inverseCumulativeProbability(random.nextDouble()); + } + + /** + * {@inheritDoc} + * + * The default implementation generates the sample by calling + * {@link #sample()} in a loop. + */ + public double[] sample(int sampleSize) { + if (sampleSize <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, + sampleSize); + } + double[] out = new double[sampleSize]; + for (int i = 0; i < sampleSize; i++) { + out[i] = sample(); + } + return out; + } + + /** + * {@inheritDoc} + * + * @return zero. + * @since 3.1 + */ + public double probability(double x) { + return 0d; + } + + /** + * Returns the natural logarithm of the probability density function (PDF) of this distribution + * evaluated at the specified point {@code x}. In general, the PDF is the derivative of the + * {@link #cumulativeProbability(double) CDF}. If the derivative does not exist at {@code x}, + * then an appropriate replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY}, + * {@code Double.NaN}, or the limit inferior or limit superior of the difference quotient. Note + * that due to the floating point precision and under/overflow issues, this method will for some + * distributions be more precise and faster than computing the logarithm of + * {@link #density(double)}. The default implementation simply computes the logarithm of + * {@code density(x)}. + * + * @param x the point at which the PDF is evaluated + * @return the logarithm of the value of the probability density function at point {@code x} + */ + public double logDensity(double x) { + return FastMath.log(density(x)); + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BetaDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BetaDistribution.java new file mode 100644 index 000000000..d8fe00803 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BetaDistribution.java @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Beta; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Implements the Beta distribution. + * + * @see Beta distribution + * @since 2.0 (changed to concrete class in 3.0) + */ +public class BetaDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = -1221965979403477668L; + /** First shape parameter. */ + private final double alpha; + /** Second shape parameter. */ + private final double beta; + /** Normalizing factor used in density computations. + * updated whenever alpha or beta are changed. + */ + private double z; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param alpha First shape parameter (must be positive). + * @param beta Second shape parameter (must be positive). + */ + public BetaDistribution(double alpha, double beta) { + this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param alpha First shape parameter (must be positive). + * @param beta Second shape parameter (must be positive). + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @since 2.1 + */ + public BetaDistribution(double alpha, double beta, double inverseCumAccuracy) { + this(new Well19937c(), alpha, beta, inverseCumAccuracy); + } + + /** + * Creates a β distribution. + * + * @param rng Random number generator. + * @param alpha First shape parameter (must be positive). + * @param beta Second shape parameter (must be positive). + * @since 3.3 + */ + public BetaDistribution(RandomGenerator rng, double alpha, double beta) { + this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a β distribution. + * + * @param rng Random number generator. + * @param alpha First shape parameter (must be positive). + * @param beta Second shape parameter (must be positive). + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @since 3.1 + */ + public BetaDistribution(RandomGenerator rng, + double alpha, + double beta, + double inverseCumAccuracy) { + super(rng); + + this.alpha = alpha; + this.beta = beta; + z = Double.NaN; + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Access the first shape parameter, {@code alpha}. + * + * @return the first shape parameter. + */ + public double getAlpha() { + return alpha; + } + + /** + * Access the second shape parameter, {@code beta}. + * + * @return the second shape parameter. + */ + public double getBeta() { + return beta; + } + + /** Recompute the normalization factor. */ + private void recomputeZ() { + if (Double.isNaN(z)) { + z = Gamma.logGamma(alpha) + Gamma.logGamma(beta) - Gamma.logGamma(alpha + beta); + } + } + + /** {@inheritDoc} */ + public double density(double x) { + final double logDensity = logDensity(x); + return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity); + } + + /** {@inheritDoc} **/ + @Override + public double logDensity(double x) { + recomputeZ(); + if (x < 0 || x > 1) { + return Double.NEGATIVE_INFINITY; + } else if (x == 0) { + if (alpha < 1) { + throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA, alpha, 1, false); + } + return Double.NEGATIVE_INFINITY; + } else if (x == 1) { + if (beta < 1) { + throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA, beta, 1, false); + } + return Double.NEGATIVE_INFINITY; + } else { + double logX = FastMath.log(x); + double log1mX = FastMath.log1p(-x); + return (alpha - 1) * logX + (beta - 1) * log1mX - z; + } + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + if (x <= 0) { + return 0; + } else if (x >= 1) { + return 1; + } else { + return Beta.regularizedBeta(x, alpha, beta); + } + } + + /** + * Return the absolute accuracy setting of the solver used to estimate + * inverse cumulative probabilities. + * + * @return the solver absolute accuracy. + * @since 2.1 + */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For first shape parameter {@code alpha} and second shape parameter + * {@code beta}, the mean is {@code alpha / (alpha + beta)}. + */ + public double getNumericalMean() { + final double a = getAlpha(); + return a / (a + getBeta()); + } + + /** + * {@inheritDoc} + * + * For first shape parameter {@code alpha} and second shape parameter + * {@code beta}, the variance is + * {@code (alpha * beta) / [(alpha + beta)^2 * (alpha + beta + 1)]}. + */ + public double getNumericalVariance() { + final double a = getAlpha(); + final double b = getBeta(); + final double alphabetasum = a + b; + return (a * b) / ((alphabetasum * alphabetasum) * (alphabetasum + 1)); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always 1 no matter the parameters. + * + * @return upper bound of the support (always 1) + */ + public double getSupportUpperBound() { + return 1; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + + /** {@inheritDoc} + *

+ * Sampling is performed using Cheng algorithms: + *

+ *

+ * R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.". + * Communications of the ACM, 21, 317–322, 1978. + *

+ */ + @Override + public double sample() { + return ChengBetaSampler.sample(random, alpha, beta); + } + + /** Utility class implementing Cheng's algorithms for beta distribution sampling. + *

+ * R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.". + * Communications of the ACM, 21, 317–322, 1978. + *

+ * @since 3.6 + */ + private static final class ChengBetaSampler { + + /** + * Returns one sample using Cheng's sampling algorithm. + * @param random random generator to use + * @param alpha distribution first shape parameter + * @param beta distribution second shape parameter + * @return sampled value + */ + static double sample(RandomGenerator random, final double alpha, final double beta) { + final double a = FastMath.min(alpha, beta); + final double b = FastMath.max(alpha, beta); + + if (a > 1) { + return algorithmBB(random, alpha, a, b); + } else { + return algorithmBC(random, alpha, b, a); + } + } + + /** + * Returns one sample using Cheng's BB algorithm, when both α and β are greater than 1. + * @param random random generator to use + * @param a0 distribution first shape parameter (α) + * @param a min(α, β) where α, β are the two distribution shape parameters + * @param b max(α, β) where α, β are the two distribution shape parameters + * @return sampled value + */ + private static double algorithmBB(RandomGenerator random, + final double a0, + final double a, + final double b) { + final double alpha = a + b; + final double beta = FastMath.sqrt((alpha - 2.) / (2. * a * b - alpha)); + final double gamma = a + 1. / beta; + + double r; + double w; + double t; + do { + final double u1 = random.nextDouble(); + final double u2 = random.nextDouble(); + final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1)); + w = a * FastMath.exp(v); + final double z = u1 * u1 * u2; + r = gamma * v - 1.3862944; + final double s = a + r - w; + if (s + 2.609438 >= 5 * z) { + break; + } + + t = FastMath.log(z); + if (s >= t) { + break; + } + } while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t); + + w = FastMath.min(w, Double.MAX_VALUE); + return Precision.equals(a, a0) ? w / (b + w) : b / (b + w); + } + + /** + * Returns one sample using Cheng's BC algorithm, when at least one of α and β is smaller than 1. + * @param random random generator to use + * @param a0 distribution first shape parameter (α) + * @param a max(α, β) where α, β are the two distribution shape parameters + * @param b min(α, β) where α, β are the two distribution shape parameters + * @return sampled value + */ + private static double algorithmBC(RandomGenerator random, + final double a0, + final double a, + final double b) { + final double alpha = a + b; + final double beta = 1. / b; + final double delta = 1. + a - b; + final double k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 0.777778); + final double k2 = 0.25 + (0.5 + 0.25 / delta) * b; + + double w; + for (;;) { + final double u1 = random.nextDouble(); + final double u2 = random.nextDouble(); + final double y = u1 * u2; + final double z = u1 * y; + if (u1 < 0.5) { + if (0.25 * u2 + z - y >= k1) { + continue; + } + } else { + if (z <= 0.25) { + final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1)); + w = a * FastMath.exp(v); + break; + } + + if (z >= k2) { + continue; + } + } + + final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1)); + w = a * FastMath.exp(v); + if (alpha * (FastMath.log(alpha) - FastMath.log(b + w) + v) - 1.3862944 >= FastMath.log(z)) { + break; + } + } + + w = FastMath.min(w, Double.MAX_VALUE); + return Precision.equals(a, a0) ? w / (b + w) : b / (b + w); + } + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BinomialDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BinomialDistribution.java new file mode 100644 index 000000000..9749bdf45 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/BinomialDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Beta; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the binomial distribution. + * + * @see Binomial distribution (Wikipedia) + * @see Binomial Distribution (MathWorld) + */ +public class BinomialDistribution extends AbstractIntegerDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = 6751309484392813623L; + /** The number of trials. */ + private final int numberOfTrials; + /** The probability of success. */ + private final double probabilityOfSuccess; + + /** + * Create a binomial distribution with the given number of trials and + * probability of success. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param trials Number of trials. + * @param p Probability of success. + * @throws NotPositiveException if {@code trials < 0}. + * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}. + */ + public BinomialDistribution(int trials, double p) { + this(new Well19937c(), trials, p); + } + + /** + * Creates a binomial distribution. + * + * @param rng Random number generator. + * @param trials Number of trials. + * @param p Probability of success. + * @throws NotPositiveException if {@code trials < 0}. + * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}. + * @since 3.1 + */ + public BinomialDistribution(RandomGenerator rng, + int trials, + double p) { + super(rng); + + if (trials < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_TRIALS, + trials); + } + if (p < 0 || p > 1) { + throw new OutOfRangeException(p, 0, 1); + } + + probabilityOfSuccess = p; + numberOfTrials = trials; + } + + /** + * Access the number of trials for this distribution. + * + * @return the number of trials. + */ + public int getNumberOfTrials() { + return numberOfTrials; + } + + /** + * Access the probability of success for this distribution. + * + * @return the probability of success. + */ + public double getProbabilityOfSuccess() { + return probabilityOfSuccess; + } + + /** {@inheritDoc} */ + public double probability(int x) { + final double logProbability = logProbability(x); + return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability); + } + + /** {@inheritDoc} **/ + @Override + public double logProbability(int x) { + if (numberOfTrials == 0) { + return (x == 0) ? 0. : Double.NEGATIVE_INFINITY; + } + double ret; + if (x < 0 || x > numberOfTrials) { + ret = Double.NEGATIVE_INFINITY; + } else { + ret = SaddlePointExpansion.logBinomialProbability(x, + numberOfTrials, probabilityOfSuccess, + 1.0 - probabilityOfSuccess); + } + return ret; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + double ret; + if (x < 0) { + ret = 0.0; + } else if (x >= numberOfTrials) { + ret = 1.0; + } else { + ret = 1.0 - Beta.regularizedBeta(probabilityOfSuccess, + x + 1.0, numberOfTrials - x); + } + return ret; + } + + /** + * {@inheritDoc} + * + * For {@code n} trials and probability parameter {@code p}, the mean is + * {@code n * p}. + */ + public double getNumericalMean() { + return numberOfTrials * probabilityOfSuccess; + } + + /** + * {@inheritDoc} + * + * For {@code n} trials and probability parameter {@code p}, the variance is + * {@code n * p * (1 - p)}. + */ + public double getNumericalVariance() { + final double p = probabilityOfSuccess; + return numberOfTrials * p * (1 - p); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 except for the probability + * parameter {@code p = 1}. + * + * @return lower bound of the support (0 or the number of trials) + */ + public int getSupportLowerBound() { + return probabilityOfSuccess < 1.0 ? 0 : numberOfTrials; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is the number of trials except for the + * probability parameter {@code p = 0}. + * + * @return upper bound of the support (number of trials or 0) + */ + public int getSupportUpperBound() { + return probabilityOfSuccess > 0.0 ? numberOfTrials : 0; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/CauchyDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/CauchyDistribution.java new file mode 100644 index 000000000..c3251313f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/CauchyDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Cauchy distribution. + * + * @see Cauchy distribution (Wikipedia) + * @see Cauchy Distribution (MathWorld) + * @since 1.1 (changed to concrete class in 3.0) + */ +public class CauchyDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier */ + private static final long serialVersionUID = 8589540077390120676L; + /** The median of this distribution. */ + private final double median; + /** The scale of this distribution. */ + private final double scale; + /** Inverse cumulative probability accuracy */ + private final double solverAbsoluteAccuracy; + + /** + * Creates a Cauchy distribution with the median equal to zero and scale + * equal to one. + */ + public CauchyDistribution() { + this(0, 1); + } + + /** + * Creates a Cauchy distribution using the given median and scale. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param median Median for this distribution. + * @param scale Scale parameter for this distribution. + */ + public CauchyDistribution(double median, double scale) { + this(median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a Cauchy distribution using the given median and scale. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param median Median for this distribution. + * @param scale Scale parameter for this distribution. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code scale <= 0}. + * @since 2.1 + */ + public CauchyDistribution(double median, double scale, + double inverseCumAccuracy) { + this(new Well19937c(), median, scale, inverseCumAccuracy); + } + + /** + * Creates a Cauchy distribution. + * + * @param rng Random number generator. + * @param median Median for this distribution. + * @param scale Scale parameter for this distribution. + * @throws NotStrictlyPositiveException if {@code scale <= 0}. + * @since 3.3 + */ + public CauchyDistribution(RandomGenerator rng, double median, double scale) { + this(rng, median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a Cauchy distribution. + * + * @param rng Random number generator. + * @param median Median for this distribution. + * @param scale Scale parameter for this distribution. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code scale <= 0}. + * @since 3.1 + */ + public CauchyDistribution(RandomGenerator rng, + double median, + double scale, + double inverseCumAccuracy) { + super(rng); + if (scale <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale); + } + this.scale = scale; + this.median = median; + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + return 0.5 + (FastMath.atan((x - median) / scale) / FastMath.PI); + } + + /** + * Access the median. + * + * @return the median for this distribution. + */ + public double getMedian() { + return median; + } + + /** + * Access the scale parameter. + * + * @return the scale parameter for this distribution. + */ + public double getScale() { + return scale; + } + + /** {@inheritDoc} */ + public double density(double x) { + final double dev = x - median; + return (1 / FastMath.PI) * (scale / (dev * dev + scale * scale)); + } + + /** + * {@inheritDoc} + * + * Returns {@code Double.NEGATIVE_INFINITY} when {@code p == 0} + * and {@code Double.POSITIVE_INFINITY} when {@code p == 1}. + */ + @Override + public double inverseCumulativeProbability(double p) throws OutOfRangeException { + double ret; + if (p < 0 || p > 1) { + throw new OutOfRangeException(p, 0, 1); + } else if (p == 0) { + ret = Double.NEGATIVE_INFINITY; + } else if (p == 1) { + ret = Double.POSITIVE_INFINITY; + } else { + ret = median + scale * FastMath.tan(FastMath.PI * (p - .5)); + } + return ret; + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * The mean is always undefined no matter the parameters. + * + * @return mean (always Double.NaN) + */ + public double getNumericalMean() { + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * The variance is always undefined no matter the parameters. + * + * @return variance (always Double.NaN) + */ + public double getNumericalVariance() { + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always negative infinity no matter + * the parameters. + * + * @return lower bound of the support (always Double.NEGATIVE_INFINITY) + */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity no matter + * the parameters. + * + * @return upper bound of the support (always Double.POSITIVE_INFINITY) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ChiSquaredDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ChiSquaredDistribution.java new file mode 100644 index 000000000..dad721539 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ChiSquaredDistribution.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; + +/** + * Implementation of the chi-squared distribution. + * + * @see Chi-squared distribution (Wikipedia) + * @see Chi-squared Distribution (MathWorld) + */ +public class ChiSquaredDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier */ + private static final long serialVersionUID = -8352658048349159782L; + /** Internal Gamma distribution. */ + private final GammaDistribution gamma; + /** Inverse cumulative probability accuracy */ + private final double solverAbsoluteAccuracy; + + /** + * Create a Chi-Squared distribution with the given degrees of freedom. + * + * @param degreesOfFreedom Degrees of freedom. + */ + public ChiSquaredDistribution(double degreesOfFreedom) { + this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a Chi-Squared distribution with the given degrees of freedom and + * inverse cumulative probability accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param degreesOfFreedom Degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @since 2.1 + */ + public ChiSquaredDistribution(double degreesOfFreedom, + double inverseCumAccuracy) { + this(new Well19937c(), degreesOfFreedom, inverseCumAccuracy); + } + + /** + * Create a Chi-Squared distribution with the given degrees of freedom. + * + * @param rng Random number generator. + * @param degreesOfFreedom Degrees of freedom. + * @since 3.3 + */ + public ChiSquaredDistribution(RandomGenerator rng, double degreesOfFreedom) { + this(rng, degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a Chi-Squared distribution with the given degrees of freedom and + * inverse cumulative probability accuracy. + * + * @param rng Random number generator. + * @param degreesOfFreedom Degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @since 3.1 + */ + public ChiSquaredDistribution(RandomGenerator rng, + double degreesOfFreedom, + double inverseCumAccuracy) { + super(rng); + + gamma = new GammaDistribution(degreesOfFreedom / 2, 2); + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Access the number of degrees of freedom. + * + * @return the degrees of freedom. + */ + public double getDegreesOfFreedom() { + return gamma.getShape() * 2.0; + } + + /** {@inheritDoc} */ + public double density(double x) { + return gamma.density(x); + } + + /** {@inheritDoc} **/ + @Override + public double logDensity(double x) { + return gamma.logDensity(x); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + return gamma.cumulativeProbability(x); + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For {@code k} degrees of freedom, the mean is {@code k}. + */ + public double getNumericalMean() { + return getDegreesOfFreedom(); + } + + /** + * {@inheritDoc} + * + * @return {@code 2 * k}, where {@code k} is the number of degrees of freedom. + */ + public double getNumericalVariance() { + return 2 * getDegreesOfFreedom(); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the + * degrees of freedom. + * + * @return zero. + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity no matter the + * degrees of freedom. + * + * @return {@code Double.POSITIVE_INFINITY}. + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ConstantRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ConstantRealDistribution.java new file mode 100644 index 000000000..2fc5fc838 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ConstantRealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Implementation of the constant real distribution. + * + * @since 3.4 + */ +public class ConstantRealDistribution extends AbstractRealDistribution { + + /** Serialization ID */ + private static final long serialVersionUID = -4157745166772046273L; + + /** Constant value of the distribution */ + private final double value; + + /** + * Create a constant real distribution with the given value. + * + * @param value the constant value of this distribution + */ + public ConstantRealDistribution(double value) { + super(null); // Avoid creating RandomGenerator + this.value = value; + } + + /** {@inheritDoc} */ + public double density(double x) { + return x == value ? 1 : 0; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + return x < value ? 0 : 1; + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(final double p) + throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + return value; + } + + /** + * {@inheritDoc} + */ + public double getNumericalMean() { + return value; + } + + /** + * {@inheritDoc} + */ + public double getNumericalVariance() { + return 0; + } + + /** + * {@inheritDoc} + */ + public double getSupportLowerBound() { + return value; + } + + /** + * {@inheritDoc} + */ + public double getSupportUpperBound() { + return value; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double sample() { + return value; + } + + /** + * Override with no-op (there is no generator). + * @param seed (ignored) + */ + @Override + public void reseedRandomGenerator(long seed) {} +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedDistribution.java new file mode 100644 index 000000000..f63c554e7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + *

A generic implementation of a + * + * discrete probability distribution (Wikipedia) over a finite sample space, + * based on an enumerated list of <value, probability> pairs. Input probabilities must all be non-negative, + * but zero values are allowed and their sum does not have to equal one. Constructors will normalize input + * probabilities to make them sum to one.

+ * + *

The list of pairs does not, strictly speaking, have to be a function and it can + * contain null values. The pmf created by the constructor will combine probabilities of equal values and + * will treat null values as equal. For example, if the list of pairs <"dog", 0.2>, <null, 0.1>, + * <"pig", 0.2>, <"dog", 0.1>, <null, 0.4> is provided to the constructor, the resulting + * pmf will assign mass of 0.5 to null, 0.3 to "dog" and 0.2 to null.

+ * + * @param type of the elements in the sample space. + * @since 3.2 + */ +public class EnumeratedDistribution implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20123308L; + + /** + * RNG instance used to generate samples from the distribution. + */ + protected final RandomGenerator random; + + /** + * List of random variable values. + */ + private final List singletons; + + /** + * Probabilities of respective random variable values. For i = 0, ..., singletons.size() - 1, + * probability[i] is the probability that a random variable following this distribution takes + * the value singletons[i]. + */ + private final double[] probabilities; + + /** + * Cumulative probabilities, cached to speed up sampling. + */ + private final double[] cumulativeProbabilities; + + /** + * Create an enumerated distribution using the given probability mass function + * enumeration. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param pmf probability mass function enumerated as a list of + * pairs. + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedDistribution(final List> pmf) + throws NotPositiveException, MathArithmeticException, NotFiniteNumberException, NotANumberException { + this(new Well19937c(), pmf); + } + + /** + * Create an enumerated distribution using the given random number generator + * and probability mass function enumeration. + * + * @param rng random number generator. + * @param pmf probability mass function enumerated as a list of + * pairs. + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedDistribution(final RandomGenerator rng, final List> pmf) + throws NotPositiveException, MathArithmeticException, NotFiniteNumberException, NotANumberException { + random = rng; + + singletons = new ArrayList(pmf.size()); + final double[] probs = new double[pmf.size()]; + + for (int i = 0; i < pmf.size(); i++) { + final Pair sample = pmf.get(i); + singletons.add(sample.getKey()); + final double p = sample.getValue(); + if (p < 0) { + throw new NotPositiveException(sample.getValue()); + } + if (Double.isInfinite(p)) { + throw new NotFiniteNumberException(p); + } + if (Double.isNaN(p)) { + throw new NotANumberException(); + } + probs[i] = p; + } + + probabilities = MathArrays.normalizeArray(probs, 1.0); + + cumulativeProbabilities = new double[probabilities.length]; + double sum = 0; + for (int i = 0; i < probabilities.length; i++) { + sum += probabilities[i]; + cumulativeProbabilities[i] = sum; + } + } + + /** + * Reseed the random generator used to generate samples. + * + * @param seed the new seed + */ + public void reseedRandomGenerator(long seed) { + random.setSeed(seed); + } + + /** + *

For a random variable {@code X} whose values are distributed according to + * this distribution, this method returns {@code P(X = x)}. In other words, + * this method represents the probability mass function (PMF) for the + * distribution.

+ * + *

Note that if {@code x1} and {@code x2} satisfy {@code x1.equals(x2)}, + * or both are null, then {@code probability(x1) = probability(x2)}.

+ * + * @param x the point at which the PMF is evaluated + * @return the value of the probability mass function at {@code x} + */ + double probability(final T x) { + double probability = 0; + + for (int i = 0; i < probabilities.length; i++) { + if ((x == null && singletons.get(i) == null) || + (x != null && x.equals(singletons.get(i)))) { + probability += probabilities[i]; + } + } + + return probability; + } + + /** + *

Return the probability mass function as a list of pairs.

+ * + *

Note that if duplicate and / or null values were provided to the constructor + * when creating this EnumeratedDistribution, the returned list will contain these + * values. If duplicates values exist, what is returned will not represent + * a pmf (i.e., it is up to the caller to consolidate duplicate mass points).

+ * + * @return the probability mass function. + */ + public List> getPmf() { + final List> samples = new ArrayList>(probabilities.length); + + for (int i = 0; i < probabilities.length; i++) { + samples.add(new Pair(singletons.get(i), probabilities[i])); + } + + return samples; + } + + /** + * Generate a random value sampled from this distribution. + * + * @return a random value. + */ + public T sample() { + final double randomValue = random.nextDouble(); + + int index = Arrays.binarySearch(cumulativeProbabilities, randomValue); + if (index < 0) { + index = -index-1; + } + + if (index >= 0 && + index < probabilities.length && + randomValue < cumulativeProbabilities[index]) { + return singletons.get(index); + } + + /* This should never happen, but it ensures we will return a correct + * object in case there is some floating point inequality problem + * wrt the cumulative probabilities. */ + return singletons.get(singletons.size() - 1); + } + + /** + * Generate a random sample from the distribution. + * + * @param sampleSize the number of random values to generate. + * @return an array representing the random sample. + * @throws NotStrictlyPositiveException if {@code sampleSize} is not + * positive. + */ + public Object[] sample(int sampleSize) throws NotStrictlyPositiveException { + if (sampleSize <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, + sampleSize); + } + + final Object[] out = new Object[sampleSize]; + + for (int i = 0; i < sampleSize; i++) { + out[i] = sample(); + } + + return out; + + } + + /** + * Generate a random sample from the distribution. + *

+ * If the requested samples fit in the specified array, it is returned + * therein. Otherwise, a new array is allocated with the runtime type of + * the specified array and the size of this collection. + * + * @param sampleSize the number of random values to generate. + * @param array the array to populate. + * @return an array representing the random sample. + * @throws NotStrictlyPositiveException if {@code sampleSize} is not positive. + * @throws NullArgumentException if {@code array} is null + */ + public T[] sample(int sampleSize, final T[] array) throws NotStrictlyPositiveException { + if (sampleSize <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize); + } + + if (array == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + + T[] out; + if (array.length < sampleSize) { + @SuppressWarnings("unchecked") // safe as both are of type T + final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), sampleSize); + out = unchecked; + } else { + out = array; + } + + for (int i = 0; i < sampleSize; i++) { + out[i] = sample(); + } + + return out; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.java new file mode 100644 index 000000000..246139dd3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + *

Implementation of an integer-valued {@link EnumeratedDistribution}.

+ * + *

Values with zero-probability are allowed but they do not extend the + * support.
+ * Duplicate values are allowed. Probabilities of duplicate values are combined + * when computing cumulative probabilities and statistics.

+ * + * @since 3.2 + */ +public class EnumeratedIntegerDistribution extends AbstractIntegerDistribution { + + /** Serializable UID. */ + private static final long serialVersionUID = 20130308L; + + /** + * {@link EnumeratedDistribution} instance (using the {@link Integer} wrapper) + * used to generate the pmf. + */ + protected final EnumeratedDistribution innerDistribution; + + /** + * Create a discrete distribution using the given probability mass function + * definition. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param singletons array of random variable values. + * @param probabilities array of probabilities. + * @throws DimensionMismatchException if + * {@code singletons.length != probabilities.length} + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedIntegerDistribution(final int[] singletons, final double[] probabilities) + throws DimensionMismatchException, NotPositiveException, MathArithmeticException, + NotFiniteNumberException, NotANumberException{ + this(new Well19937c(), singletons, probabilities); + } + + /** + * Create a discrete distribution using the given random number generator + * and probability mass function definition. + * + * @param rng random number generator. + * @param singletons array of random variable values. + * @param probabilities array of probabilities. + * @throws DimensionMismatchException if + * {@code singletons.length != probabilities.length} + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedIntegerDistribution(final RandomGenerator rng, + final int[] singletons, final double[] probabilities) + throws DimensionMismatchException, NotPositiveException, MathArithmeticException, + NotFiniteNumberException, NotANumberException { + super(rng); + innerDistribution = new EnumeratedDistribution( + rng, createDistribution(singletons, probabilities)); + } + + /** + * Create a discrete integer-valued distribution from the input data. Values are assigned + * mass based on their frequency. + * + * @param rng random number generator used for sampling + * @param data input dataset + * @since 3.6 + */ + public EnumeratedIntegerDistribution(final RandomGenerator rng, final int[] data) { + super(rng); + final Map dataMap = new HashMap(); + for (int value : data) { + Integer count = dataMap.get(value); + if (count == null) { + count = 0; + } + dataMap.put(value, ++count); + } + final int massPoints = dataMap.size(); + final double denom = data.length; + final int[] values = new int[massPoints]; + final double[] probabilities = new double[massPoints]; + int index = 0; + for (Entry entry : dataMap.entrySet()) { + values[index] = entry.getKey(); + probabilities[index] = entry.getValue().intValue() / denom; + index++; + } + innerDistribution = new EnumeratedDistribution(rng, createDistribution(values, probabilities)); + } + + /** + * Create a discrete integer-valued distribution from the input data. Values are assigned + * mass based on their frequency. For example, [0,1,1,2] as input creates a distribution + * with values 0, 1 and 2 having probability masses 0.25, 0.5 and 0.25 respectively, + * + * @param data input dataset + * @since 3.6 + */ + public EnumeratedIntegerDistribution(final int[] data) { + this(new Well19937c(), data); + } + + /** + * Create the list of Pairs representing the distribution from singletons and probabilities. + * + * @param singletons values + * @param probabilities probabilities + * @return list of value/probability pairs + */ + private static List> createDistribution(int[] singletons, double[] probabilities) { + if (singletons.length != probabilities.length) { + throw new DimensionMismatchException(probabilities.length, singletons.length); + } + + final List> samples = new ArrayList>(singletons.length); + + for (int i = 0; i < singletons.length; i++) { + samples.add(new Pair(singletons[i], probabilities[i])); + } + return samples; + + } + + /** + * {@inheritDoc} + */ + public double probability(final int x) { + return innerDistribution.probability(x); + } + + /** + * {@inheritDoc} + */ + public double cumulativeProbability(final int x) { + double probability = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() <= x) { + probability += sample.getValue(); + } + } + + return probability; + } + + /** + * {@inheritDoc} + * + * @return {@code sum(singletons[i] * probabilities[i])} + */ + public double getNumericalMean() { + double mean = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + mean += sample.getValue() * sample.getKey(); + } + + return mean; + } + + /** + * {@inheritDoc} + * + * @return {@code sum((singletons[i] - mean) ^ 2 * probabilities[i])} + */ + public double getNumericalVariance() { + double mean = 0; + double meanOfSquares = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + mean += sample.getValue() * sample.getKey(); + meanOfSquares += sample.getValue() * sample.getKey() * sample.getKey(); + } + + return meanOfSquares - mean * mean; + } + + /** + * {@inheritDoc} + * + * Returns the lowest value with non-zero probability. + * + * @return the lowest value with non-zero probability. + */ + public int getSupportLowerBound() { + int min = Integer.MAX_VALUE; + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() < min && sample.getValue() > 0) { + min = sample.getKey(); + } + } + + return min; + } + + /** + * {@inheritDoc} + * + * Returns the highest value with non-zero probability. + * + * @return the highest value with non-zero probability. + */ + public int getSupportUpperBound() { + int max = Integer.MIN_VALUE; + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() > max && sample.getValue() > 0) { + max = sample.getKey(); + } + } + + return max; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int sample() { + return innerDistribution.sample(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java new file mode 100644 index 000000000..002796b22 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + *

Implementation of a real-valued {@link EnumeratedDistribution}. + * + *

Values with zero-probability are allowed but they do not extend the + * support.
+ * Duplicate values are allowed. Probabilities of duplicate values are combined + * when computing cumulative probabilities and statistics.

+ * + * @since 3.2 + */ +public class EnumeratedRealDistribution extends AbstractRealDistribution { + + /** Serializable UID. */ + private static final long serialVersionUID = 20130308L; + + /** + * {@link EnumeratedDistribution} (using the {@link Double} wrapper) + * used to generate the pmf. + */ + protected final EnumeratedDistribution innerDistribution; + + /** + * Create a discrete real-valued distribution using the given probability mass function + * enumeration. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param singletons array of random variable values. + * @param probabilities array of probabilities. + * @throws DimensionMismatchException if + * {@code singletons.length != probabilities.length} + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedRealDistribution(final double[] singletons, final double[] probabilities) + throws DimensionMismatchException, NotPositiveException, MathArithmeticException, + NotFiniteNumberException, NotANumberException { + this(new Well19937c(), singletons, probabilities); + } + + /** + * Create a discrete real-valued distribution using the given random number generator + * and probability mass function enumeration. + * + * @param rng random number generator. + * @param singletons array of random variable values. + * @param probabilities array of probabilities. + * @throws DimensionMismatchException if + * {@code singletons.length != probabilities.length} + * @throws NotPositiveException if any of the probabilities are negative. + * @throws NotFiniteNumberException if any of the probabilities are infinite. + * @throws NotANumberException if any of the probabilities are NaN. + * @throws MathArithmeticException all of the probabilities are 0. + */ + public EnumeratedRealDistribution(final RandomGenerator rng, + final double[] singletons, final double[] probabilities) + throws DimensionMismatchException, NotPositiveException, MathArithmeticException, + NotFiniteNumberException, NotANumberException { + super(rng); + + innerDistribution = new EnumeratedDistribution( + rng, createDistribution(singletons, probabilities)); + } + + /** + * Create a discrete real-valued distribution from the input data. Values are assigned + * mass based on their frequency. + * + * @param rng random number generator used for sampling + * @param data input dataset + * @since 3.6 + */ + public EnumeratedRealDistribution(final RandomGenerator rng, final double[] data) { + super(rng); + final Map dataMap = new HashMap(); + for (double value : data) { + Integer count = dataMap.get(value); + if (count == null) { + count = 0; + } + dataMap.put(value, ++count); + } + final int massPoints = dataMap.size(); + final double denom = data.length; + final double[] values = new double[massPoints]; + final double[] probabilities = new double[massPoints]; + int index = 0; + for (Entry entry : dataMap.entrySet()) { + values[index] = entry.getKey(); + probabilities[index] = entry.getValue().intValue() / denom; + index++; + } + innerDistribution = new EnumeratedDistribution(rng, createDistribution(values, probabilities)); + } + + /** + * Create a discrete real-valued distribution from the input data. Values are assigned + * mass based on their frequency. For example, [0,1,1,2] as input creates a distribution + * with values 0, 1 and 2 having probability masses 0.25, 0.5 and 0.25 respectively, + * + * @param data input dataset + * @since 3.6 + */ + public EnumeratedRealDistribution(final double[] data) { + this(new Well19937c(), data); + } + /** + * Create the list of Pairs representing the distribution from singletons and probabilities. + * + * @param singletons values + * @param probabilities probabilities + * @return list of value/probability pairs + */ + private static List> createDistribution(double[] singletons, double[] probabilities) { + if (singletons.length != probabilities.length) { + throw new DimensionMismatchException(probabilities.length, singletons.length); + } + + final List> samples = new ArrayList>(singletons.length); + + for (int i = 0; i < singletons.length; i++) { + samples.add(new Pair(singletons[i], probabilities[i])); + } + return samples; + + } + + /** + * {@inheritDoc} + */ + @Override + public double probability(final double x) { + return innerDistribution.probability(x); + } + + /** + * For a random variable {@code X} whose values are distributed according to + * this distribution, this method returns {@code P(X = x)}. In other words, + * this method represents the probability mass function (PMF) for the + * distribution. + * + * @param x the point at which the PMF is evaluated + * @return the value of the probability mass function at point {@code x} + */ + public double density(final double x) { + return probability(x); + } + + /** + * {@inheritDoc} + */ + public double cumulativeProbability(final double x) { + double probability = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() <= x) { + probability += sample.getValue(); + } + } + + return probability; + } + + /** + * {@inheritDoc} + */ + @Override + public double inverseCumulativeProbability(final double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + + double probability = 0; + double x = getSupportLowerBound(); + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getValue() == 0.0) { + continue; + } + + probability += sample.getValue(); + x = sample.getKey(); + + if (probability >= p) { + break; + } + } + + return x; + } + + /** + * {@inheritDoc} + * + * @return {@code sum(singletons[i] * probabilities[i])} + */ + public double getNumericalMean() { + double mean = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + mean += sample.getValue() * sample.getKey(); + } + + return mean; + } + + /** + * {@inheritDoc} + * + * @return {@code sum((singletons[i] - mean) ^ 2 * probabilities[i])} + */ + public double getNumericalVariance() { + double mean = 0; + double meanOfSquares = 0; + + for (final Pair sample : innerDistribution.getPmf()) { + mean += sample.getValue() * sample.getKey(); + meanOfSquares += sample.getValue() * sample.getKey() * sample.getKey(); + } + + return meanOfSquares - mean * mean; + } + + /** + * {@inheritDoc} + * + * Returns the lowest value with non-zero probability. + * + * @return the lowest value with non-zero probability. + */ + public double getSupportLowerBound() { + double min = Double.POSITIVE_INFINITY; + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() < min && sample.getValue() > 0) { + min = sample.getKey(); + } + } + + return min; + } + + /** + * {@inheritDoc} + * + * Returns the highest value with non-zero probability. + * + * @return the highest value with non-zero probability. + */ + public double getSupportUpperBound() { + double max = Double.NEGATIVE_INFINITY; + for (final Pair sample : innerDistribution.getPmf()) { + if (sample.getKey() > max && sample.getValue() > 0) { + max = sample.getKey(); + } + } + + return max; + } + + /** + * {@inheritDoc} + * + * The support of this distribution includes the lower bound. + * + * @return {@code true} + */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * + * The support of this distribution includes the upper bound. + * + * @return {@code true} + */ + public boolean isSupportUpperBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public double sample() { + return innerDistribution.sample(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ExponentialDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ExponentialDistribution.java new file mode 100644 index 000000000..494f3bbe4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ExponentialDistribution.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.ResizableDoubleArray; + +/** + * Implementation of the exponential distribution. + * + * @see Exponential distribution (Wikipedia) + * @see Exponential distribution (MathWorld) + */ +public class ExponentialDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier */ + private static final long serialVersionUID = 2401296428283614780L; + /** + * Used when generating Exponential samples. + * Table containing the constants + * q_i = sum_{j=1}^i (ln 2)^j/j! = ln 2 + (ln 2)^2/2 + ... + (ln 2)^i/i! + * until the largest representable fraction below 1 is exceeded. + * + * Note that + * 1 = 2 - 1 = exp(ln 2) - 1 = sum_{n=1}^infty (ln 2)^n / n! + * thus q_i -> 1 as i -> +inf, + * so the higher i, the closer to one we get (the series is not alternating). + * + * By trying, n = 16 in Java is enough to reach 1.0. + */ + private static final double[] EXPONENTIAL_SA_QI; + /** The mean of this distribution. */ + private final double mean; + /** The logarithm of the mean, stored to reduce computing time. **/ + private final double logMean; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Initialize tables. + */ + static { + /** + * Filling EXPONENTIAL_SA_QI table. + * Note that we don't want qi = 0 in the table. + */ + final double LN2 = FastMath.log(2); + double qi = 0; + int i = 1; + + /** + * ArithmeticUtils provides factorials up to 20, so let's use that + * limit together with Precision.EPSILON to generate the following + * code (a priori, we know that there will be 16 elements, but it is + * better to not hardcode it). + */ + final ResizableDoubleArray ra = new ResizableDoubleArray(20); + + while (qi < 1) { + qi += FastMath.pow(LN2, i) / CombinatoricsUtils.factorial(i); + ra.addElement(qi); + ++i; + } + + EXPONENTIAL_SA_QI = ra.getElements(); + } + + /** + * Create an exponential distribution with the given mean. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mean mean of this distribution. + */ + public ExponentialDistribution(double mean) { + this(mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create an exponential distribution with the given mean. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mean Mean of this distribution. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code mean <= 0}. + * @since 2.1 + */ + public ExponentialDistribution(double mean, double inverseCumAccuracy) { + this(new Well19937c(), mean, inverseCumAccuracy); + } + + /** + * Creates an exponential distribution. + * + * @param rng Random number generator. + * @param mean Mean of this distribution. + * @throws NotStrictlyPositiveException if {@code mean <= 0}. + * @since 3.3 + */ + public ExponentialDistribution(RandomGenerator rng, double mean) + throws NotStrictlyPositiveException { + this(rng, mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates an exponential distribution. + * + * @param rng Random number generator. + * @param mean Mean of this distribution. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code mean <= 0}. + * @since 3.1 + */ + public ExponentialDistribution(RandomGenerator rng, + double mean, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (mean <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, mean); + } + this.mean = mean; + logMean = FastMath.log(mean); + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Access the mean. + * + * @return the mean. + */ + public double getMean() { + return mean; + } + + /** {@inheritDoc} */ + public double density(double x) { + final double logDensity = logDensity(x); + return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity); + } + + /** {@inheritDoc} **/ + @Override + public double logDensity(double x) { + if (x < 0) { + return Double.NEGATIVE_INFINITY; + } + return -x / mean - logMean; + } + + /** + * {@inheritDoc} + * + * The implementation of this method is based on: + *

+ */ + public double cumulativeProbability(double x) { + double ret; + if (x <= 0.0) { + ret = 0.0; + } else { + ret = 1.0 - FastMath.exp(-x / mean); + } + return ret; + } + + /** + * {@inheritDoc} + * + * Returns {@code 0} when {@code p= = 0} and + * {@code Double.POSITIVE_INFINITY} when {@code p == 1}. + */ + @Override + public double inverseCumulativeProbability(double p) throws OutOfRangeException { + double ret; + + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0.0, 1.0); + } else if (p == 1.0) { + ret = Double.POSITIVE_INFINITY; + } else { + ret = -mean * FastMath.log(1.0 - p); + } + + return ret; + } + + /** + * {@inheritDoc} + * + *

Algorithm Description: this implementation uses the + * + * Inversion Method to generate exponentially distributed random values + * from uniform deviates.

+ * + * @return a random value. + * @since 2.2 + */ + @Override + public double sample() { + // Step 1: + double a = 0; + double u = random.nextDouble(); + + // Step 2 and 3: + while (u < 0.5) { + a += EXPONENTIAL_SA_QI[0]; + u *= 2; + } + + // Step 4 (now u >= 0.5): + u += u - 1; + + // Step 5: + if (u <= EXPONENTIAL_SA_QI[0]) { + return mean * (a + u); + } + + // Step 6: + int i = 0; // Should be 1, be we iterate before it in while using 0 + double u2 = random.nextDouble(); + double umin = u2; + + // Step 7 and 8: + do { + ++i; + u2 = random.nextDouble(); + + if (u2 < umin) { + umin = u2; + } + + // Step 8: + } while (u > EXPONENTIAL_SA_QI[i]); // Ensured to exit since EXPONENTIAL_SA_QI[MAX] = 1 + + return mean * (a + umin * EXPONENTIAL_SA_QI[0]); + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For mean parameter {@code k}, the mean is {@code k}. + */ + public double getNumericalMean() { + return getMean(); + } + + /** + * {@inheritDoc} + * + * For mean parameter {@code k}, the variance is {@code k^2}. + */ + public double getNumericalVariance() { + final double m = getMean(); + return m * m; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the mean parameter. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the mean parameter. + * + * @return upper bound of the support (always Double.POSITIVE_INFINITY) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/FDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/FDistribution.java new file mode 100644 index 000000000..c53c6195f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/FDistribution.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Beta; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the F-distribution. + * + * @see F-distribution (Wikipedia) + * @see F-distribution (MathWorld) + */ +public class FDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = -8516354193418641566L; + /** The numerator degrees of freedom. */ + private final double numeratorDegreesOfFreedom; + /** The numerator degrees of freedom. */ + private final double denominatorDegreesOfFreedom; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + /** Cached numerical variance */ + private double numericalVariance = Double.NaN; + /** Whether or not the numerical variance has been calculated */ + private boolean numericalVarianceIsCalculated = false; + + /** + * Creates an F distribution using the given degrees of freedom. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param numeratorDegreesOfFreedom Numerator degrees of freedom. + * @param denominatorDegreesOfFreedom Denominator degrees of freedom. + * @throws NotStrictlyPositiveException if + * {@code numeratorDegreesOfFreedom <= 0} or + * {@code denominatorDegreesOfFreedom <= 0}. + */ + public FDistribution(double numeratorDegreesOfFreedom, + double denominatorDegreesOfFreedom) + throws NotStrictlyPositiveException { + this(numeratorDegreesOfFreedom, denominatorDegreesOfFreedom, + DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates an F distribution using the given degrees of freedom + * and inverse cumulative probability accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param numeratorDegreesOfFreedom Numerator degrees of freedom. + * @param denominatorDegreesOfFreedom Denominator degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates. + * @throws NotStrictlyPositiveException if + * {@code numeratorDegreesOfFreedom <= 0} or + * {@code denominatorDegreesOfFreedom <= 0}. + * @since 2.1 + */ + public FDistribution(double numeratorDegreesOfFreedom, + double denominatorDegreesOfFreedom, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), numeratorDegreesOfFreedom, + denominatorDegreesOfFreedom, inverseCumAccuracy); + } + + /** + * Creates an F distribution. + * + * @param rng Random number generator. + * @param numeratorDegreesOfFreedom Numerator degrees of freedom. + * @param denominatorDegreesOfFreedom Denominator degrees of freedom. + * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or + * {@code denominatorDegreesOfFreedom <= 0}. + * @since 3.3 + */ + public FDistribution(RandomGenerator rng, + double numeratorDegreesOfFreedom, + double denominatorDegreesOfFreedom) + throws NotStrictlyPositiveException { + this(rng, numeratorDegreesOfFreedom, denominatorDegreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates an F distribution. + * + * @param rng Random number generator. + * @param numeratorDegreesOfFreedom Numerator degrees of freedom. + * @param denominatorDegreesOfFreedom Denominator degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates. + * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or + * {@code denominatorDegreesOfFreedom <= 0}. + * @since 3.1 + */ + public FDistribution(RandomGenerator rng, + double numeratorDegreesOfFreedom, + double denominatorDegreesOfFreedom, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (numeratorDegreesOfFreedom <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DEGREES_OF_FREEDOM, + numeratorDegreesOfFreedom); + } + if (denominatorDegreesOfFreedom <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DEGREES_OF_FREEDOM, + denominatorDegreesOfFreedom); + } + this.numeratorDegreesOfFreedom = numeratorDegreesOfFreedom; + this.denominatorDegreesOfFreedom = denominatorDegreesOfFreedom; + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * {@inheritDoc} + * + * @since 2.1 + */ + public double density(double x) { + return FastMath.exp(logDensity(x)); + } + + /** {@inheritDoc} **/ + @Override + public double logDensity(double x) { + final double nhalf = numeratorDegreesOfFreedom / 2; + final double mhalf = denominatorDegreesOfFreedom / 2; + final double logx = FastMath.log(x); + final double logn = FastMath.log(numeratorDegreesOfFreedom); + final double logm = FastMath.log(denominatorDegreesOfFreedom); + final double lognxm = FastMath.log(numeratorDegreesOfFreedom * x + + denominatorDegreesOfFreedom); + return nhalf * logn + nhalf * logx - logx + + mhalf * logm - nhalf * lognxm - mhalf * lognxm - + Beta.logBeta(nhalf, mhalf); + } + + /** + * {@inheritDoc} + * + * The implementation of this method is based on + *

+ */ + public double cumulativeProbability(double x) { + double ret; + if (x <= 0) { + ret = 0; + } else { + double n = numeratorDegreesOfFreedom; + double m = denominatorDegreesOfFreedom; + + ret = Beta.regularizedBeta((n * x) / (m + n * x), + 0.5 * n, + 0.5 * m); + } + return ret; + } + + /** + * Access the numerator degrees of freedom. + * + * @return the numerator degrees of freedom. + */ + public double getNumeratorDegreesOfFreedom() { + return numeratorDegreesOfFreedom; + } + + /** + * Access the denominator degrees of freedom. + * + * @return the denominator degrees of freedom. + */ + public double getDenominatorDegreesOfFreedom() { + return denominatorDegreesOfFreedom; + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For denominator degrees of freedom parameter {@code b}, the mean is + *
    + *
  • if {@code b > 2} then {@code b / (b - 2)},
  • + *
  • else undefined ({@code Double.NaN}). + *
+ */ + public double getNumericalMean() { + final double denominatorDF = getDenominatorDegreesOfFreedom(); + + if (denominatorDF > 2) { + return denominatorDF / (denominatorDF - 2); + } + + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * For numerator degrees of freedom parameter {@code a} and denominator + * degrees of freedom parameter {@code b}, the variance is + *
    + *
  • + * if {@code b > 4} then + * {@code [2 * b^2 * (a + b - 2)] / [a * (b - 2)^2 * (b - 4)]}, + *
  • + *
  • else undefined ({@code Double.NaN}). + *
+ */ + public double getNumericalVariance() { + if (!numericalVarianceIsCalculated) { + numericalVariance = calculateNumericalVariance(); + numericalVarianceIsCalculated = true; + } + return numericalVariance; + } + + /** + * used by {@link #getNumericalVariance()} + * + * @return the variance of this distribution + */ + protected double calculateNumericalVariance() { + final double denominatorDF = getDenominatorDegreesOfFreedom(); + + if (denominatorDF > 4) { + final double numeratorDF = getNumeratorDegreesOfFreedom(); + final double denomDFMinusTwo = denominatorDF - 2; + + return ( 2 * (denominatorDF * denominatorDF) * (numeratorDF + denominatorDF - 2) ) / + ( (numeratorDF * (denomDFMinusTwo * denomDFMinusTwo) * (denominatorDF - 4)) ); + } + + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the parameters. + * + * @return upper bound of the support (always Double.POSITIVE_INFINITY) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GammaDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GammaDistribution.java new file mode 100644 index 000000000..fcc7a1ac2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GammaDistribution.java @@ -0,0 +1,513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Gamma distribution. + * + * @see Gamma distribution (Wikipedia) + * @see Gamma distribution (MathWorld) + */ +public class GammaDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = 20120524L; + /** The shape parameter. */ + private final double shape; + /** The scale parameter. */ + private final double scale; + /** + * The constant value of {@code shape + g + 0.5}, where {@code g} is the + * Lanczos constant {@link Gamma#LANCZOS_G}. + */ + private final double shiftedShape; + /** + * The constant value of + * {@code shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape)}, + * where {@code L(shape)} is the Lanczos approximation returned by + * {@link Gamma#lanczos(double)}. This prefactor is used in + * {@link #density(double)}, when no overflow occurs with the natural + * calculation. + */ + private final double densityPrefactor1; + /** + * The constant value of + * {@code log(shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape))}, + * where {@code L(shape)} is the Lanczos approximation returned by + * {@link Gamma#lanczos(double)}. This prefactor is used in + * {@link #logDensity(double)}, when no overflow occurs with the natural + * calculation. + */ + private final double logDensityPrefactor1; + /** + * The constant value of + * {@code shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape)}, + * where {@code L(shape)} is the Lanczos approximation returned by + * {@link Gamma#lanczos(double)}. This prefactor is used in + * {@link #density(double)}, when overflow occurs with the natural + * calculation. + */ + private final double densityPrefactor2; + /** + * The constant value of + * {@code log(shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape))}, + * where {@code L(shape)} is the Lanczos approximation returned by + * {@link Gamma#lanczos(double)}. This prefactor is used in + * {@link #logDensity(double)}, when overflow occurs with the natural + * calculation. + */ + private final double logDensityPrefactor2; + /** + * Lower bound on {@code y = x / scale} for the selection of the computation + * method in {@link #density(double)}. For {@code y <= minY}, the natural + * calculation overflows. + */ + private final double minY; + /** + * Upper bound on {@code log(y)} ({@code y = x / scale}) for the selection + * of the computation method in {@link #density(double)}. For + * {@code log(y) >= maxLogY}, the natural calculation overflows. + */ + private final double maxLogY; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Creates a new gamma distribution with specified values of the shape and + * scale parameters. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param shape the shape parameter + * @param scale the scale parameter + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + */ + public GammaDistribution(double shape, double scale) throws NotStrictlyPositiveException { + this(shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a new gamma distribution with specified values of the shape and + * scale parameters. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param shape the shape parameter + * @param scale the scale parameter + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + * @since 2.1 + */ + public GammaDistribution(double shape, double scale, double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), shape, scale, inverseCumAccuracy); + } + + /** + * Creates a Gamma distribution. + * + * @param rng Random number generator. + * @param shape the shape parameter + * @param scale the scale parameter + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + * @since 3.3 + */ + public GammaDistribution(RandomGenerator rng, double shape, double scale) + throws NotStrictlyPositiveException { + this(rng, shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a Gamma distribution. + * + * @param rng Random number generator. + * @param shape the shape parameter + * @param scale the scale parameter + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to + * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + * @since 3.1 + */ + public GammaDistribution(RandomGenerator rng, + double shape, + double scale, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (shape <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape); + } + if (scale <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale); + } + + this.shape = shape; + this.scale = scale; + this.solverAbsoluteAccuracy = inverseCumAccuracy; + this.shiftedShape = shape + Gamma.LANCZOS_G + 0.5; + final double aux = FastMath.E / (2.0 * FastMath.PI * shiftedShape); + this.densityPrefactor2 = shape * FastMath.sqrt(aux) / Gamma.lanczos(shape); + this.logDensityPrefactor2 = FastMath.log(shape) + 0.5 * FastMath.log(aux) - + FastMath.log(Gamma.lanczos(shape)); + this.densityPrefactor1 = this.densityPrefactor2 / scale * + FastMath.pow(shiftedShape, -shape) * + FastMath.exp(shape + Gamma.LANCZOS_G); + this.logDensityPrefactor1 = this.logDensityPrefactor2 - FastMath.log(scale) - + FastMath.log(shiftedShape) * shape + + shape + Gamma.LANCZOS_G; + this.minY = shape + Gamma.LANCZOS_G - FastMath.log(Double.MAX_VALUE); + this.maxLogY = FastMath.log(Double.MAX_VALUE) / (shape - 1.0); + } + + /** + * Returns the shape parameter of {@code this} distribution. + * + * @return the shape parameter + * @deprecated as of version 3.1, {@link #getShape()} should be preferred. + * This method will be removed in version 4.0. + */ + @Deprecated + public double getAlpha() { + return shape; + } + + /** + * Returns the shape parameter of {@code this} distribution. + * + * @return the shape parameter + * @since 3.1 + */ + public double getShape() { + return shape; + } + + /** + * Returns the scale parameter of {@code this} distribution. + * + * @return the scale parameter + * @deprecated as of version 3.1, {@link #getScale()} should be preferred. + * This method will be removed in version 4.0. + */ + @Deprecated + public double getBeta() { + return scale; + } + + /** + * Returns the scale parameter of {@code this} distribution. + * + * @return the scale parameter + * @since 3.1 + */ + public double getScale() { + return scale; + } + + /** {@inheritDoc} */ + public double density(double x) { + /* The present method must return the value of + * + * 1 x a - x + * ---------- (-) exp(---) + * x Gamma(a) b b + * + * where a is the shape parameter, and b the scale parameter. + * Substituting the Lanczos approximation of Gamma(a) leads to the + * following expression of the density + * + * a e 1 y a + * - sqrt(------------------) ---- (-----------) exp(a - y + g), + * x 2 pi (a + g + 0.5) L(a) a + g + 0.5 + * + * where y = x / b. The above formula is the "natural" computation, which + * is implemented when no overflow is likely to occur. If overflow occurs + * with the natural computation, the following identity is used. It is + * based on the BOOST library + * http://www.boost.org/doc/libs/1_35_0/libs/math/doc/sf_and_dist/html/math_toolkit/special/sf_gamma/igamma.html + * Formula (15) needs adaptations, which are detailed below. + * + * y a + * (-----------) exp(a - y + g) + * a + g + 0.5 + * y - a - g - 0.5 y (g + 0.5) + * = exp(a log1pm(---------------) - ----------- + g), + * a + g + 0.5 a + g + 0.5 + * + * where log1pm(z) = log(1 + z) - z. Therefore, the value to be + * returned is + * + * a e 1 + * - sqrt(------------------) ---- + * x 2 pi (a + g + 0.5) L(a) + * y - a - g - 0.5 y (g + 0.5) + * * exp(a log1pm(---------------) - ----------- + g). + * a + g + 0.5 a + g + 0.5 + */ + if (x < 0) { + return 0; + } + final double y = x / scale; + if ((y <= minY) || (FastMath.log(y) >= maxLogY)) { + /* + * Overflow. + */ + final double aux1 = (y - shiftedShape) / shiftedShape; + final double aux2 = shape * (FastMath.log1p(aux1) - aux1); + final double aux3 = -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape + + Gamma.LANCZOS_G + aux2; + return densityPrefactor2 / x * FastMath.exp(aux3); + } + /* + * Natural calculation. + */ + return densityPrefactor1 * FastMath.exp(-y) * FastMath.pow(y, shape - 1); + } + + /** {@inheritDoc} **/ + @Override + public double logDensity(double x) { + /* + * see the comment in {@link #density(double)} for computation details + */ + if (x < 0) { + return Double.NEGATIVE_INFINITY; + } + final double y = x / scale; + if ((y <= minY) || (FastMath.log(y) >= maxLogY)) { + /* + * Overflow. + */ + final double aux1 = (y - shiftedShape) / shiftedShape; + final double aux2 = shape * (FastMath.log1p(aux1) - aux1); + final double aux3 = -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape + + Gamma.LANCZOS_G + aux2; + return logDensityPrefactor2 - FastMath.log(x) + aux3; + } + /* + * Natural calculation. + */ + return logDensityPrefactor1 - y + FastMath.log(y) * (shape - 1); + } + + /** + * {@inheritDoc} + * + * The implementation of this method is based on: + *

    + *
  • + * + * Chi-Squared Distribution, equation (9). + *
  • + *
  • Casella, G., & Berger, R. (1990). Statistical Inference. + * Belmont, CA: Duxbury Press. + *
  • + *
+ */ + public double cumulativeProbability(double x) { + double ret; + + if (x <= 0) { + ret = 0; + } else { + ret = Gamma.regularizedGammaP(shape, x / scale); + } + + return ret; + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For shape parameter {@code alpha} and scale parameter {@code beta}, the + * mean is {@code alpha * beta}. + */ + public double getNumericalMean() { + return shape * scale; + } + + /** + * {@inheritDoc} + * + * For shape parameter {@code alpha} and scale parameter {@code beta}, the + * variance is {@code alpha * beta^2}. + * + * @return {@inheritDoc} + */ + public double getNumericalVariance() { + return shape * scale * scale; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the parameters. + * + * @return upper bound of the support (always Double.POSITIVE_INFINITY) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + *

This implementation uses the following algorithms:

+ * + *

For 0 < shape < 1:
+ * Ahrens, J. H. and Dieter, U., Computer methods for + * sampling from gamma, beta, Poisson and binomial distributions. + * Computing, 12, 223-246, 1974.

+ * + *

For shape >= 1:
+ * Marsaglia and Tsang, A Simple Method for Generating + * Gamma Variables. ACM Transactions on Mathematical Software, + * Volume 26 Issue 3, September, 2000.

+ * + * @return random value sampled from the Gamma(shape, scale) distribution + */ + @Override + public double sample() { + if (shape < 1) { + // [1]: p. 228, Algorithm GS + + while (true) { + // Step 1: + final double u = random.nextDouble(); + final double bGS = 1 + shape / FastMath.E; + final double p = bGS * u; + + if (p <= 1) { + // Step 2: + + final double x = FastMath.pow(p, 1 / shape); + final double u2 = random.nextDouble(); + + if (u2 > FastMath.exp(-x)) { + // Reject + continue; + } else { + return scale * x; + } + } else { + // Step 3: + + final double x = -1 * FastMath.log((bGS - p) / shape); + final double u2 = random.nextDouble(); + + if (u2 > FastMath.pow(x, shape - 1)) { + // Reject + continue; + } else { + return scale * x; + } + } + } + } + + // Now shape >= 1 + + final double d = shape - 0.333333333333333333; + final double c = 1 / (3 * FastMath.sqrt(d)); + + while (true) { + final double x = random.nextGaussian(); + final double v = (1 + c * x) * (1 + c * x) * (1 + c * x); + + if (v <= 0) { + continue; + } + + final double x2 = x * x; + final double u = random.nextDouble(); + + // Squeeze + if (u < 1 - 0.0331 * x2 * x2) { + return scale * d * v; + } + + if (FastMath.log(u) < 0.5 * x2 + d * (1 - v + FastMath.log(v))) { + return scale * d * v; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GeometricDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GeometricDistribution.java new file mode 100644 index 000000000..340cba0b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GeometricDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the geometric distribution. + * + * @see Geometric distribution (Wikipedia) + * @see Geometric Distribution (MathWorld) + * @since 3.3 + */ +public class GeometricDistribution extends AbstractIntegerDistribution { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20130507L; + /** The probability of success. */ + private final double probabilityOfSuccess; + /** {@code log(p)} where p is the probability of success. */ + private final double logProbabilityOfSuccess; + /** {@code log(1 - p)} where p is the probability of success. */ + private final double log1mProbabilityOfSuccess; + + /** + * Create a geometric distribution with the given probability of success. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param p probability of success. + * @throws OutOfRangeException if {@code p <= 0} or {@code p > 1}. + */ + public GeometricDistribution(double p) { + this(new Well19937c(), p); + } + + /** + * Creates a geometric distribution. + * + * @param rng Random number generator. + * @param p Probability of success. + * @throws OutOfRangeException if {@code p <= 0} or {@code p > 1}. + */ + public GeometricDistribution(RandomGenerator rng, double p) { + super(rng); + + if (p <= 0 || p > 1) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_LEFT, p, 0, 1); + } + + probabilityOfSuccess = p; + logProbabilityOfSuccess = FastMath.log(p); + log1mProbabilityOfSuccess = FastMath.log1p(-p); + } + + /** + * Access the probability of success for this distribution. + * + * @return the probability of success. + */ + public double getProbabilityOfSuccess() { + return probabilityOfSuccess; + } + + /** {@inheritDoc} */ + public double probability(int x) { + if (x < 0) { + return 0.0; + } else { + return FastMath.exp(log1mProbabilityOfSuccess * x) * probabilityOfSuccess; + } + } + + /** {@inheritDoc} */ + @Override + public double logProbability(int x) { + if (x < 0) { + return Double.NEGATIVE_INFINITY; + } else { + return x * log1mProbabilityOfSuccess + logProbabilityOfSuccess; + } + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + if (x < 0) { + return 0.0; + } else { + return -FastMath.expm1(log1mProbabilityOfSuccess * (x + 1)); + } + } + + /** + * {@inheritDoc} + * + * For probability parameter {@code p}, the mean is {@code (1 - p) / p}. + */ + public double getNumericalMean() { + return (1 - probabilityOfSuccess) / probabilityOfSuccess; + } + + /** + * {@inheritDoc} + * + * For probability parameter {@code p}, the variance is + * {@code (1 - p) / (p * p)}. + */ + public double getNumericalVariance() { + return (1 - probabilityOfSuccess) / (probabilityOfSuccess * probabilityOfSuccess); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0. + * + * @return lower bound of the support (always 0) + */ + public int getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is infinite (which we approximate as + * {@code Integer.MAX_VALUE}). + * + * @return upper bound of the support (always Integer.MAX_VALUE) + */ + public int getSupportUpperBound() { + return Integer.MAX_VALUE; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int inverseCumulativeProbability(double p) throws OutOfRangeException { + if (p < 0 || p > 1) { + throw new OutOfRangeException(p, 0, 1); + } + if (p == 1) { + return Integer.MAX_VALUE; + } + if (p == 0) { + return 0; + } + return Math.max(0, (int) Math.ceil(FastMath.log1p(-p)/log1mProbabilityOfSuccess-1)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GumbelDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GumbelDistribution.java new file mode 100644 index 000000000..88aae8bbc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/GumbelDistribution.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This class implements the Gumbel distribution. + * + * @see Gumbel Distribution (Wikipedia) + * @see Gumbel Distribution (Mathworld) + * + * @since 3.4 + */ +public class GumbelDistribution extends AbstractRealDistribution { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20141003; + + /** + * Approximation of Euler's constant + * see http://mathworld.wolfram.com/Euler-MascheroniConstantApproximations.html + */ + private static final double EULER = FastMath.PI / (2 * FastMath.E); + + /** The location parameter. */ + private final double mu; + /** The scale parameter. */ + private final double beta; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu location parameter + * @param beta scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public GumbelDistribution(double mu, double beta) { + this(new Well19937c(), mu, beta); + } + + /** + * Build a new instance. + * + * @param rng Random number generator + * @param mu location parameter + * @param beta scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public GumbelDistribution(RandomGenerator rng, double mu, double beta) { + super(rng); + + if (beta <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, beta); + } + + this.beta = beta; + this.mu = mu; + } + + /** + * Access the location parameter, {@code mu}. + * + * @return the location parameter. + */ + public double getLocation() { + return mu; + } + + /** + * Access the scale parameter, {@code beta}. + * + * @return the scale parameter. + */ + public double getScale() { + return beta; + } + + /** {@inheritDoc} */ + public double density(double x) { + final double z = (x - mu) / beta; + final double t = FastMath.exp(-z); + return FastMath.exp(-z - t) / beta; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + final double z = (x - mu) / beta; + return FastMath.exp(-FastMath.exp(-z)); + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0.0, 1.0); + } else if (p == 0) { + return Double.NEGATIVE_INFINITY; + } else if (p == 1) { + return Double.POSITIVE_INFINITY; + } + return mu - FastMath.log(-FastMath.log(p)) * beta; + } + + /** {@inheritDoc} */ + public double getNumericalMean() { + return mu + EULER * beta; + } + + /** {@inheritDoc} */ + public double getNumericalVariance() { + return (MathUtils.PI_SQUARED) / 6.0 * (beta * beta); + } + + /** {@inheritDoc} */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** {@inheritDoc} */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportConnected() { + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/HypergeometricDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/HypergeometricDistribution.java new file mode 100644 index 000000000..f28e81639 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/HypergeometricDistribution.java @@ -0,0 +1,347 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the hypergeometric distribution. + * + * @see Hypergeometric distribution (Wikipedia) + * @see Hypergeometric distribution (MathWorld) + */ +public class HypergeometricDistribution extends AbstractIntegerDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = -436928820673516179L; + /** The number of successes in the population. */ + private final int numberOfSuccesses; + /** The population size. */ + private final int populationSize; + /** The sample size. */ + private final int sampleSize; + /** Cached numerical variance */ + private double numericalVariance = Double.NaN; + /** Whether or not the numerical variance has been calculated */ + private boolean numericalVarianceIsCalculated = false; + + /** + * Construct a new hypergeometric distribution with the specified population + * size, number of successes in the population, and sample size. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param populationSize Population size. + * @param numberOfSuccesses Number of successes in the population. + * @param sampleSize Sample size. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NotStrictlyPositiveException if {@code populationSize <= 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, + * or {@code sampleSize > populationSize}. + */ + public HypergeometricDistribution(int populationSize, int numberOfSuccesses, int sampleSize) + throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException { + this(new Well19937c(), populationSize, numberOfSuccesses, sampleSize); + } + + /** + * Creates a new hypergeometric distribution. + * + * @param rng Random number generator. + * @param populationSize Population size. + * @param numberOfSuccesses Number of successes in the population. + * @param sampleSize Sample size. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NotStrictlyPositiveException if {@code populationSize <= 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, + * or {@code sampleSize > populationSize}. + * @since 3.1 + */ + public HypergeometricDistribution(RandomGenerator rng, + int populationSize, + int numberOfSuccesses, + int sampleSize) + throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException { + super(rng); + + if (populationSize <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.POPULATION_SIZE, + populationSize); + } + if (numberOfSuccesses < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_SUCCESSES, + numberOfSuccesses); + } + if (sampleSize < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, + sampleSize); + } + + if (numberOfSuccesses > populationSize) { + throw new NumberIsTooLargeException(LocalizedFormats.NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE, + numberOfSuccesses, populationSize, true); + } + if (sampleSize > populationSize) { + throw new NumberIsTooLargeException(LocalizedFormats.SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE, + sampleSize, populationSize, true); + } + + this.numberOfSuccesses = numberOfSuccesses; + this.populationSize = populationSize; + this.sampleSize = sampleSize; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + double ret; + + int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize); + if (x < domain[0]) { + ret = 0.0; + } else if (x >= domain[1]) { + ret = 1.0; + } else { + ret = innerCumulativeProbability(domain[0], x, 1); + } + + return ret; + } + + /** + * Return the domain for the given hypergeometric distribution parameters. + * + * @param n Population size. + * @param m Number of successes in the population. + * @param k Sample size. + * @return a two element array containing the lower and upper bounds of the + * hypergeometric distribution. + */ + private int[] getDomain(int n, int m, int k) { + return new int[] { getLowerDomain(n, m, k), getUpperDomain(m, k) }; + } + + /** + * Return the lowest domain value for the given hypergeometric distribution + * parameters. + * + * @param n Population size. + * @param m Number of successes in the population. + * @param k Sample size. + * @return the lowest domain value of the hypergeometric distribution. + */ + private int getLowerDomain(int n, int m, int k) { + return FastMath.max(0, m - (n - k)); + } + + /** + * Access the number of successes. + * + * @return the number of successes. + */ + public int getNumberOfSuccesses() { + return numberOfSuccesses; + } + + /** + * Access the population size. + * + * @return the population size. + */ + public int getPopulationSize() { + return populationSize; + } + + /** + * Access the sample size. + * + * @return the sample size. + */ + public int getSampleSize() { + return sampleSize; + } + + /** + * Return the highest domain value for the given hypergeometric distribution + * parameters. + * + * @param m Number of successes in the population. + * @param k Sample size. + * @return the highest domain value of the hypergeometric distribution. + */ + private int getUpperDomain(int m, int k) { + return FastMath.min(k, m); + } + + /** {@inheritDoc} */ + public double probability(int x) { + final double logProbability = logProbability(x); + return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability); + } + + /** {@inheritDoc} */ + @Override + public double logProbability(int x) { + double ret; + + int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize); + if (x < domain[0] || x > domain[1]) { + ret = Double.NEGATIVE_INFINITY; + } else { + double p = (double) sampleSize / (double) populationSize; + double q = (double) (populationSize - sampleSize) / (double) populationSize; + double p1 = SaddlePointExpansion.logBinomialProbability(x, + numberOfSuccesses, p, q); + double p2 = + SaddlePointExpansion.logBinomialProbability(sampleSize - x, + populationSize - numberOfSuccesses, p, q); + double p3 = + SaddlePointExpansion.logBinomialProbability(sampleSize, populationSize, p, q); + ret = p1 + p2 - p3; + } + + return ret; + } + + /** + * For this distribution, {@code X}, this method returns {@code P(X >= x)}. + * + * @param x Value at which the CDF is evaluated. + * @return the upper tail CDF for this distribution. + * @since 1.1 + */ + public double upperCumulativeProbability(int x) { + double ret; + + final int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize); + if (x <= domain[0]) { + ret = 1.0; + } else if (x > domain[1]) { + ret = 0.0; + } else { + ret = innerCumulativeProbability(domain[1], x, -1); + } + + return ret; + } + + /** + * For this distribution, {@code X}, this method returns + * {@code P(x0 <= X <= x1)}. + * This probability is computed by summing the point probabilities for the + * values {@code x0, x0 + 1, x0 + 2, ..., x1}, in the order directed by + * {@code dx}. + * + * @param x0 Inclusive lower bound. + * @param x1 Inclusive upper bound. + * @param dx Direction of summation (1 indicates summing from x0 to x1, and + * 0 indicates summing from x1 to x0). + * @return {@code P(x0 <= X <= x1)}. + */ + private double innerCumulativeProbability(int x0, int x1, int dx) { + double ret = probability(x0); + while (x0 != x1) { + x0 += dx; + ret += probability(x0); + } + return ret; + } + + /** + * {@inheritDoc} + * + * For population size {@code N}, number of successes {@code m}, and sample + * size {@code n}, the mean is {@code n * m / N}. + */ + public double getNumericalMean() { + return getSampleSize() * (getNumberOfSuccesses() / (double) getPopulationSize()); + } + + /** + * {@inheritDoc} + * + * For population size {@code N}, number of successes {@code m}, and sample + * size {@code n}, the variance is + * {@code [n * m * (N - n) * (N - m)] / [N^2 * (N - 1)]}. + */ + public double getNumericalVariance() { + if (!numericalVarianceIsCalculated) { + numericalVariance = calculateNumericalVariance(); + numericalVarianceIsCalculated = true; + } + return numericalVariance; + } + + /** + * Used by {@link #getNumericalVariance()}. + * + * @return the variance of this distribution + */ + protected double calculateNumericalVariance() { + final double N = getPopulationSize(); + final double m = getNumberOfSuccesses(); + final double n = getSampleSize(); + return (n * m * (N - n) * (N - m)) / (N * N * (N - 1)); + } + + /** + * {@inheritDoc} + * + * For population size {@code N}, number of successes {@code m}, and sample + * size {@code n}, the lower bound of the support is + * {@code max(0, n + m - N)}. + * + * @return lower bound of the support + */ + public int getSupportLowerBound() { + return FastMath.max(0, + getSampleSize() + getNumberOfSuccesses() - getPopulationSize()); + } + + /** + * {@inheritDoc} + * + * For number of successes {@code m} and sample size {@code n}, the upper + * bound of the support is {@code min(m, n)}. + * + * @return upper bound of the support + */ + public int getSupportUpperBound() { + return FastMath.min(getNumberOfSuccesses(), getSampleSize()); + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/IntegerDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/IntegerDistribution.java new file mode 100644 index 000000000..4c206542c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/IntegerDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Interface for distributions on the integers. + * + */ +public interface IntegerDistribution { + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(X = x)}. In other + * words, this method represents the probability mass function (PMF) + * for the distribution. + * + * @param x the point at which the PMF is evaluated + * @return the value of the probability mass function at {@code x} + */ + double probability(int x); + + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(X <= x)}. In other + * words, this method represents the (cumulative) distribution function + * (CDF) for this distribution. + * + * @param x the point at which the CDF is evaluated + * @return the probability that a random variable with this + * distribution takes a value less than or equal to {@code x} + */ + double cumulativeProbability(int x); + + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(x0 < X <= x1)}. + * + * @param x0 the exclusive lower bound + * @param x1 the inclusive upper bound + * @return the probability that a random variable with this distribution + * will take a value between {@code x0} and {@code x1}, + * excluding the lower and including the upper endpoint + * @throws NumberIsTooLargeException if {@code x0 > x1} + */ + double cumulativeProbability(int x0, int x1) throws NumberIsTooLargeException; + + /** + * Computes the quantile function of this distribution. + * For a random variable {@code X} distributed according to this distribution, + * the returned value is + *

    + *
  • inf{x in Z | P(X<=x) >= p} for {@code 0 < p <= 1},
  • + *
  • inf{x in Z | P(X<=x) > 0} for {@code p = 0}.
  • + *
+ * If the result exceeds the range of the data type {@code int}, + * then {@code Integer.MIN_VALUE} or {@code Integer.MAX_VALUE} is returned. + * + * @param p the cumulative probability + * @return the smallest {@code p}-quantile of this distribution + * (largest 0-quantile for {@code p = 0}) + * @throws OutOfRangeException if {@code p < 0} or {@code p > 1} + */ + int inverseCumulativeProbability(double p) throws OutOfRangeException; + + /** + * Use this method to get the numerical value of the mean of this + * distribution. + * + * @return the mean or {@code Double.NaN} if it is not defined + */ + double getNumericalMean(); + + /** + * Use this method to get the numerical value of the variance of this + * distribution. + * + * @return the variance (possibly {@code Double.POSITIVE_INFINITY} or + * {@code Double.NaN} if it is not defined) + */ + double getNumericalVariance(); + + /** + * Access the lower bound of the support. This method must return the same + * value as {@code inverseCumulativeProbability(0)}. In other words, this + * method must return + *

inf {x in Z | P(X <= x) > 0}.

+ * + * @return lower bound of the support ({@code Integer.MIN_VALUE} + * for negative infinity) + */ + int getSupportLowerBound(); + + /** + * Access the upper bound of the support. This method must return the same + * value as {@code inverseCumulativeProbability(1)}. In other words, this + * method must return + *

inf {x in R | P(X <= x) = 1}.

+ * + * @return upper bound of the support ({@code Integer.MAX_VALUE} + * for positive infinity) + */ + int getSupportUpperBound(); + + /** + * Use this method to get information about whether the support is + * connected, i.e. whether all integers between the lower and upper bound of + * the support are included in the support. + * + * @return whether the support is connected or not + */ + boolean isSupportConnected(); + + /** + * Reseed the random generator used to generate samples. + * + * @param seed the new seed + * @since 3.0 + */ + void reseedRandomGenerator(long seed); + + /** + * Generate a random value sampled from this distribution. + * + * @return a random value + * @since 3.0 + */ + int sample(); + + /** + * Generate a random sample from the distribution. + * + * @param sampleSize the number of random values to generate + * @return an array representing the random sample + * @throws NotStrictlyPositiveException + * if {@code sampleSize} is not positive + * @since 3.0 + */ + int[] sample(int sampleSize); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.java new file mode 100644 index 000000000..81551f02a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import java.io.Serializable; +import java.math.BigDecimal; + +import com.fr.third.org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.fraction.BigFractionField; +import com.fr.third.org.apache.commons.math3.fraction.FractionConversionException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Kolmogorov-Smirnov distribution. + * + *

+ * Treats the distribution of the two-sided {@code P(D_n < d)} where + * {@code D_n = sup_x |G(x) - G_n (x)|} for the theoretical cdf {@code G} and + * the empirical cdf {@code G_n}. + *

+ *

+ * This implementation is based on [1] with certain quick decisions for extreme + * values given in [2]. + *

+ *

+ * In short, when wanting to evaluate {@code P(D_n < d)}, the method in [1] is + * to write {@code d = (k - h) / n} for positive integer {@code k} and + * {@code 0 <= h < 1}. Then {@code P(D_n < d) = (n! / n^n) * t_kk}, where + * {@code t_kk} is the {@code (k, k)}'th entry in the special matrix + * {@code H^n}, i.e. {@code H} to the {@code n}'th power. + *

+ *

+ * References: + *

+ * Note that [1] contains an error in computing h, refer to + * MATH-437 for details. + *

+ * + * @see + * Kolmogorov-Smirnov test (Wikipedia) + * @deprecated to be removed in version 4.0 - + * use {@link KolmogorovSmirnovTest} + */ +public class KolmogorovSmirnovDistribution implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -4670676796862967187L; + + /** Number of observations. */ + private int n; + + /** + * @param n Number of observations + * @throws NotStrictlyPositiveException if {@code n <= 0} + */ + public KolmogorovSmirnovDistribution(int n) + throws NotStrictlyPositiveException { + if (n <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES, n); + } + + this.n = n; + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] with quick + * decisions for extreme values given in [2] (see above). The result is not + * exact as with + * {@link KolmogorovSmirnovDistribution#cdfExact(double)} because + * calculations are based on {@code double} rather than + * {@link BigFraction}. + * + * @param d statistic + * @return the two-sided probability of {@code P(D_n < d)} + * @throws MathArithmeticException if algorithm fails to convert {@code h} + * to a {@link BigFraction} in expressing + * {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + public double cdf(double d) throws MathArithmeticException { + return this.cdf(d, false); + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] with quick + * decisions for extreme values given in [2] (see above). The result is + * exact in the sense that BigFraction/BigReal is used everywhere at the + * expense of very slow execution time. Almost never choose this in real + * applications unless you are very sure; this is almost solely for + * verification purposes. Normally, you would choose + * {@link KolmogorovSmirnovDistribution#cdf(double)} + * + * @param d statistic + * @return the two-sided probability of {@code P(D_n < d)} + * @throws MathArithmeticException if algorithm fails to convert {@code h} + * to a {@link BigFraction} in expressing + * {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + public double cdfExact(double d) throws MathArithmeticException { + return this.cdf(d, true); + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] with quick + * decisions for extreme values given in [2] (see above). + * + * @param d statistic + * @param exact whether the probability should be calculated exact using + * {@link BigFraction} everywhere at the + * expense of very slow execution time, or if {@code double} should be used + * convenient places to gain speed. Almost never choose {@code true} in real + * applications unless you are very sure; {@code true} is almost solely for + * verification purposes. + * @return the two-sided probability of {@code P(D_n < d)} + * @throws MathArithmeticException if algorithm fails to convert {@code h} + * to a {@link BigFraction} in expressing + * {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + public double cdf(double d, boolean exact) throws MathArithmeticException { + + final double ninv = 1 / ((double) n); + final double ninvhalf = 0.5 * ninv; + + if (d <= ninvhalf) { + + return 0; + + } else if (ninvhalf < d && d <= ninv) { + + double res = 1; + double f = 2 * d - ninv; + + // n! f^n = n*f * (n-1)*f * ... * 1*x + for (int i = 1; i <= n; ++i) { + res *= i * f; + } + + return res; + + } else if (1 - ninv <= d && d < 1) { + + return 1 - 2 * FastMath.pow(1 - d, n); + + } else if (1 <= d) { + + return 1; + } + + return exact ? exactK(d) : roundedK(d); + } + + /** + * Calculates the exact value of {@code P(D_n < d)} using method described + * in [1] and {@link BigFraction} (see + * above). + * + * @param d statistic + * @return the two-sided probability of {@code P(D_n < d)} + * @throws MathArithmeticException if algorithm fails to convert {@code h} + * to a {@link BigFraction} in expressing + * {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + private double exactK(double d) throws MathArithmeticException { + + final int k = (int) FastMath.ceil(n * d); + + final FieldMatrix H = this.createH(d); + final FieldMatrix Hpower = H.power(n); + + BigFraction pFrac = Hpower.getEntry(k - 1, k - 1); + + for (int i = 1; i <= n; ++i) { + pFrac = pFrac.multiply(i).divide(n); + } + + /* + * BigFraction.doubleValue converts numerator to double and the + * denominator to double and divides afterwards. That gives NaN quite + * easy. This does not (scale is the number of digits): + */ + return pFrac.bigDecimalValue(20, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] and doubles + * (see above). + * + * @param d statistic + * @return the two-sided probability of {@code P(D_n < d)} + * @throws MathArithmeticException if algorithm fails to convert {@code h} + * to a {@link BigFraction} in expressing + * {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + private double roundedK(double d) throws MathArithmeticException { + + final int k = (int) FastMath.ceil(n * d); + final FieldMatrix HBigFraction = this.createH(d); + final int m = HBigFraction.getRowDimension(); + + /* + * Here the rounding part comes into play: use + * RealMatrix instead of FieldMatrix + */ + final RealMatrix H = new Array2DRowRealMatrix(m, m); + + for (int i = 0; i < m; ++i) { + for (int j = 0; j < m; ++j) { + H.setEntry(i, j, HBigFraction.getEntry(i, j).doubleValue()); + } + } + + final RealMatrix Hpower = H.power(n); + + double pFrac = Hpower.getEntry(k - 1, k - 1); + + for (int i = 1; i <= n; ++i) { + pFrac *= (double) i / (double) n; + } + + return pFrac; + } + + /*** + * Creates {@code H} of size {@code m x m} as described in [1] (see above). + * + * @param d statistic + * @return H matrix + * @throws NumberIsTooLargeException if fractional part is greater than 1 + * @throws FractionConversionException if algorithm fails to convert + * {@code h} to a {@link BigFraction} in + * expressing {@code d} as {@code (k - h) / m} for integer {@code k, m} and + * {@code 0 <= h < 1}. + */ + private FieldMatrix createH(double d) + throws NumberIsTooLargeException, FractionConversionException { + + int k = (int) FastMath.ceil(n * d); + + int m = 2 * k - 1; + double hDouble = k - n * d; + + if (hDouble >= 1) { + throw new NumberIsTooLargeException(hDouble, 1.0, false); + } + + BigFraction h = null; + + try { + h = new BigFraction(hDouble, 1.0e-20, 10000); + } catch (FractionConversionException e1) { + try { + h = new BigFraction(hDouble, 1.0e-10, 10000); + } catch (FractionConversionException e2) { + h = new BigFraction(hDouble, 1.0e-5, 10000); + } + } + + final BigFraction[][] Hdata = new BigFraction[m][m]; + + /* + * Start by filling everything with either 0 or 1. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < m; ++j) { + if (i - j + 1 < 0) { + Hdata[i][j] = BigFraction.ZERO; + } else { + Hdata[i][j] = BigFraction.ONE; + } + } + } + + /* + * Setting up power-array to avoid calculating the same value twice: + * hPowers[0] = h^1 ... hPowers[m-1] = h^m + */ + final BigFraction[] hPowers = new BigFraction[m]; + hPowers[0] = h; + for (int i = 1; i < m; ++i) { + hPowers[i] = h.multiply(hPowers[i - 1]); + } + + /* + * First column and last row has special values (each other reversed). + */ + for (int i = 0; i < m; ++i) { + Hdata[i][0] = Hdata[i][0].subtract(hPowers[i]); + Hdata[m - 1][i] = Hdata[m - 1][i].subtract(hPowers[m - i - 1]); + } + + /* + * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix + * should be (1 - 2*h^m + (2h - 1)^m )/m!" Since 0 <= h < 1, then if h > + * 1/2 is sufficient to check: + */ + if (h.compareTo(BigFraction.ONE_HALF) == 1) { + Hdata[m - 1][0] = Hdata[m - 1][0].add(h.multiply(2).subtract(1).pow(m)); + } + + /* + * Aside from the first column and last row, the (i, j)-th element is + * 1/(i - j + 1)! if i - j + 1 >= 0, else 0. 1's and 0's are already + * put, so only division with (i - j + 1)! is needed in the elements + * that have 1's. There is no need to calculate (i - j + 1)! and then + * divide - small steps avoid overflows. + * + * Note that i - j + 1 > 0 <=> i + 1 > j instead of j'ing all the way to + * m. Also note that it is started at g = 2 because dividing by 1 isn't + * really necessary. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < i + 1; ++j) { + if (i - j + 1 > 0) { + for (int g = 2; g <= i - j + 1; ++g) { + Hdata[i][j] = Hdata[i][j].divide(g); + } + } + } + } + + return new Array2DRowFieldMatrix(BigFractionField.getInstance(), Hdata); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LaplaceDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LaplaceDistribution.java new file mode 100644 index 000000000..c232d9443 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LaplaceDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the Laplace distribution. + * + * @see Laplace distribution (Wikipedia) + * + * @since 3.4 + */ +public class LaplaceDistribution extends AbstractRealDistribution { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20141003; + + /** The location parameter. */ + private final double mu; + /** The scale parameter. */ + private final double beta; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu location parameter + * @param beta scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public LaplaceDistribution(double mu, double beta) { + this(new Well19937c(), mu, beta); + } + + /** + * Build a new instance. + * + * @param rng Random number generator + * @param mu location parameter + * @param beta scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public LaplaceDistribution(RandomGenerator rng, double mu, double beta) { + super(rng); + + if (beta <= 0.0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, beta); + } + + this.mu = mu; + this.beta = beta; + } + + /** + * Access the location parameter, {@code mu}. + * + * @return the location parameter. + */ + public double getLocation() { + return mu; + } + + /** + * Access the scale parameter, {@code beta}. + * + * @return the scale parameter. + */ + public double getScale() { + return beta; + } + + /** {@inheritDoc} */ + public double density(double x) { + return FastMath.exp(-FastMath.abs(x - mu) / beta) / (2.0 * beta); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + if (x <= mu) { + return FastMath.exp((x - mu) / beta) / 2.0; + } else { + return 1.0 - FastMath.exp((mu - x) / beta) / 2.0; + } + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0.0, 1.0); + } else if (p == 0) { + return Double.NEGATIVE_INFINITY; + } else if (p == 1) { + return Double.POSITIVE_INFINITY; + } + double x = (p > 0.5) ? -Math.log(2.0 - 2.0 * p) : Math.log(2.0 * p); + return mu + beta * x; + } + + /** {@inheritDoc} */ + public double getNumericalMean() { + return mu; + } + + /** {@inheritDoc} */ + public double getNumericalVariance() { + return 2.0 * beta * beta; + } + + /** {@inheritDoc} */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** {@inheritDoc} */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportConnected() { + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LevyDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LevyDistribution.java new file mode 100644 index 000000000..e2a23a471 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LevyDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Erf; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the + * Lévy distribution. + * + * @since 3.2 + */ +public class LevyDistribution extends AbstractRealDistribution { + + /** Serializable UID. */ + private static final long serialVersionUID = 20130314L; + + /** Location parameter. */ + private final double mu; + + /** Scale parameter. */ + private final double c; // Setting this to 1 returns a cumProb of 1.0 + + /** Half of c (for calculations). */ + private final double halfC; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu location parameter + * @param c scale parameter + * @since 3.4 + */ + public LevyDistribution(final double mu, final double c) { + this(new Well19937c(), mu, c); + } + + /** + * Creates a LevyDistribution. + * @param rng random generator to be used for sampling + * @param mu location + * @param c scale parameter + */ + public LevyDistribution(final RandomGenerator rng, final double mu, final double c) { + super(rng); + this.mu = mu; + this.c = c; + this.halfC = 0.5 * c; + } + + /** {@inheritDoc} + *

+ * From Wikipedia: The probability density function of the Lévy distribution + * over the domain is + *

+ *
+    * f(x; μ, c) = √(c / 2π) * e-c / 2 (x - μ) / (x - μ)3/2
+    * 
+ *

+ * For this distribution, {@code X}, this method returns {@code P(X < x)}. + * If {@code x} is less than location parameter μ, {@code Double.NaN} is + * returned, as in these cases the distribution is not defined. + *

+ */ + public double density(final double x) { + if (x < mu) { + return Double.NaN; + } + + final double delta = x - mu; + final double f = halfC / delta; + return FastMath.sqrt(f / FastMath.PI) * FastMath.exp(-f) /delta; + } + + /** {@inheritDoc} + * + * See documentation of {@link #density(double)} for computation details. + */ + @Override + public double logDensity(double x) { + if (x < mu) { + return Double.NaN; + } + + final double delta = x - mu; + final double f = halfC / delta; + return 0.5 * FastMath.log(f / FastMath.PI) - f - FastMath.log(delta); + } + + /** {@inheritDoc} + *

+ * From Wikipedia: the cumulative distribution function is + *

+ *
+     * f(x; u, c) = erfc (√ (c / 2 (x - u )))
+     * 
+ */ + public double cumulativeProbability(final double x) { + if (x < mu) { + return Double.NaN; + } + return Erf.erfc(FastMath.sqrt(halfC / (x - mu))); + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(final double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + final double t = Erf.erfcInv(p); + return mu + halfC / (t * t); + } + + /** Get the scale parameter of the distribution. + * @return scale parameter of the distribution + */ + public double getScale() { + return c; + } + + /** Get the location parameter of the distribution. + * @return location parameter of the distribution + */ + public double getLocation() { + return mu; + } + + /** {@inheritDoc} */ + public double getNumericalMean() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public double getNumericalVariance() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public double getSupportLowerBound() { + return mu; + } + + /** {@inheritDoc} */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + // there is a division by x-mu in the computation, so density + // is not finite at lower bound, bound must be excluded + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + // upper bound is infinite, so it must be excluded + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportConnected() { + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogNormalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogNormalDistribution.java new file mode 100644 index 000000000..6f429ae63 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogNormalDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Erf; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the log-normal (gaussian) distribution. + * + *

+ * Parameters: + * {@code X} is log-normally distributed if its natural logarithm {@code log(X)} + * is normally distributed. The probability distribution function of {@code X} + * is given by (for {@code x > 0}) + *

+ *

+ * {@code exp(-0.5 * ((ln(x) - m) / s)^2) / (s * sqrt(2 * pi) * x)} + *

+ *
    + *
  • {@code m} is the scale parameter: this is the mean of the + * normally distributed natural logarithm of this distribution,
  • + *
  • {@code s} is the shape parameter: this is the standard + * deviation of the normally distributed natural logarithm of this + * distribution. + *
+ * + * @see + * Log-normal distribution (Wikipedia) + * @see + * Log Normal distribution (MathWorld) + * + * @since 3.0 + */ +public class LogNormalDistribution extends AbstractRealDistribution { + /** Default inverse cumulative probability accuracy. */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20120112; + + /** √(2 π) */ + private static final double SQRT2PI = FastMath.sqrt(2 * FastMath.PI); + + /** √(2) */ + private static final double SQRT2 = FastMath.sqrt(2.0); + + /** The scale parameter of this distribution. */ + private final double scale; + + /** The shape parameter of this distribution. */ + private final double shape; + /** The value of {@code log(shape) + 0.5 * log(2*PI)} stored for faster computation. */ + private final double logShapePlusHalfLog2Pi; + + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Create a log-normal distribution, where the mean and standard deviation + * of the {@link NormalDistribution normally distributed} natural + * logarithm of the log-normal distribution are equal to zero and one + * respectively. In other words, the scale of the returned distribution is + * {@code 0}, while its shape is {@code 1}. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + */ + public LogNormalDistribution() { + this(0, 1); + } + + /** + * Create a log-normal distribution using the specified scale and shape. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param scale the scale parameter of this distribution + * @param shape the shape parameter of this distribution + * @throws NotStrictlyPositiveException if {@code shape <= 0}. + */ + public LogNormalDistribution(double scale, double shape) + throws NotStrictlyPositiveException { + this(scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a log-normal distribution using the specified scale, shape and + * inverse cumulative distribution accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param scale the scale parameter of this distribution + * @param shape the shape parameter of this distribution + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code shape <= 0}. + */ + public LogNormalDistribution(double scale, double shape, double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), scale, shape, inverseCumAccuracy); + } + + /** + * Creates a log-normal distribution. + * + * @param rng Random number generator. + * @param scale Scale parameter of this distribution. + * @param shape Shape parameter of this distribution. + * @throws NotStrictlyPositiveException if {@code shape <= 0}. + * @since 3.3 + */ + public LogNormalDistribution(RandomGenerator rng, double scale, double shape) + throws NotStrictlyPositiveException { + this(rng, scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a log-normal distribution. + * + * @param rng Random number generator. + * @param scale Scale parameter of this distribution. + * @param shape Shape parameter of this distribution. + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code shape <= 0}. + * @since 3.1 + */ + public LogNormalDistribution(RandomGenerator rng, + double scale, + double shape, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (shape <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape); + } + + this.scale = scale; + this.shape = shape; + this.logShapePlusHalfLog2Pi = FastMath.log(shape) + 0.5 * FastMath.log(2 * FastMath.PI); + this.solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Returns the scale parameter of this distribution. + * + * @return the scale parameter + */ + public double getScale() { + return scale; + } + + /** + * Returns the shape parameter of this distribution. + * + * @return the shape parameter + */ + public double getShape() { + return shape; + } + + /** + * {@inheritDoc} + * + * For scale {@code m}, and shape {@code s} of this distribution, the PDF + * is given by + *

    + *
  • {@code 0} if {@code x <= 0},
  • + *
  • {@code exp(-0.5 * ((ln(x) - m) / s)^2) / (s * sqrt(2 * pi) * x)} + * otherwise.
  • + *
+ */ + public double density(double x) { + if (x <= 0) { + return 0; + } + final double x0 = FastMath.log(x) - scale; + final double x1 = x0 / shape; + return FastMath.exp(-0.5 * x1 * x1) / (shape * SQRT2PI * x); + } + + /** {@inheritDoc} + * + * See documentation of {@link #density(double)} for computation details. + */ + @Override + public double logDensity(double x) { + if (x <= 0) { + return Double.NEGATIVE_INFINITY; + } + final double logX = FastMath.log(x); + final double x0 = logX - scale; + final double x1 = x0 / shape; + return -0.5 * x1 * x1 - (logShapePlusHalfLog2Pi + logX); + } + + /** + * {@inheritDoc} + * + * For scale {@code m}, and shape {@code s} of this distribution, the CDF + * is given by + *
    + *
  • {@code 0} if {@code x <= 0},
  • + *
  • {@code 0} if {@code ln(x) - m < 0} and {@code m - ln(x) > 40 * s}, as + * in these cases the actual value is within {@code Double.MIN_VALUE} of 0, + *
  • {@code 1} if {@code ln(x) - m >= 0} and {@code ln(x) - m > 40 * s}, + * as in these cases the actual value is within {@code Double.MIN_VALUE} of + * 1,
  • + *
  • {@code 0.5 + 0.5 * erf((ln(x) - m) / (s * sqrt(2))} otherwise.
  • + *
+ */ + public double cumulativeProbability(double x) { + if (x <= 0) { + return 0; + } + final double dev = FastMath.log(x) - scale; + if (FastMath.abs(dev) > 40 * shape) { + return dev < 0 ? 0.0d : 1.0d; + } + return 0.5 + 0.5 * Erf.erf(dev / (shape * SQRT2)); + } + + /** + * {@inheritDoc} + * + * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)} + */ + @Override@Deprecated + public double cumulativeProbability(double x0, double x1) + throws NumberIsTooLargeException { + return probability(x0, x1); + } + + /** {@inheritDoc} */ + @Override + public double probability(double x0, + double x1) + throws NumberIsTooLargeException { + if (x0 > x1) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, + x0, x1, true); + } + if (x0 <= 0 || x1 <= 0) { + return super.probability(x0, x1); + } + final double denom = shape * SQRT2; + final double v0 = (FastMath.log(x0) - scale) / denom; + final double v1 = (FastMath.log(x1) - scale) / denom; + return 0.5 * Erf.erf(v0, v1); + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For scale {@code m} and shape {@code s}, the mean is + * {@code exp(m + s^2 / 2)}. + */ + public double getNumericalMean() { + double s = shape; + return FastMath.exp(scale + (s * s / 2)); + } + + /** + * {@inheritDoc} + * + * For scale {@code m} and shape {@code s}, the variance is + * {@code (exp(s^2) - 1) * exp(2 * m + s^2)}. + */ + public double getNumericalVariance() { + final double s = shape; + final double ss = s * s; + return (FastMath.expm1(ss)) * FastMath.exp(2 * scale + ss); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the parameters. + * + * @return upper bound of the support (always + * {@code Double.POSITIVE_INFINITY}) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double sample() { + final double n = random.nextGaussian(); + return FastMath.exp(scale + shape * n); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogisticDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogisticDistribution.java new file mode 100644 index 000000000..996c513cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/LogisticDistribution.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This class implements the Logistic distribution. + * + * @see Logistic Distribution (Wikipedia) + * @see Logistic Distribution (Mathworld) + * + * @since 3.4 + */ +public class LogisticDistribution extends AbstractRealDistribution { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20141003; + + /** The location parameter. */ + private final double mu; + /** The scale parameter. */ + private final double s; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu location parameter + * @param s scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public LogisticDistribution(double mu, double s) { + this(new Well19937c(), mu, s); + } + + /** + * Build a new instance. + * + * @param rng Random number generator + * @param mu location parameter + * @param s scale parameter (must be positive) + * @throws NotStrictlyPositiveException if {@code beta <= 0} + */ + public LogisticDistribution(RandomGenerator rng, double mu, double s) { + super(rng); + + if (s <= 0.0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, s); + } + + this.mu = mu; + this.s = s; + } + + /** + * Access the location parameter, {@code mu}. + * + * @return the location parameter. + */ + public double getLocation() { + return mu; + } + + /** + * Access the scale parameter, {@code s}. + * + * @return the scale parameter. + */ + public double getScale() { + return s; + } + + /** {@inheritDoc} */ + public double density(double x) { + double z = (x - mu) / s; + double v = FastMath.exp(-z); + return 1 / s * v / ((1.0 + v) * (1.0 + v)); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + double z = 1 / s * (x - mu); + return 1.0 / (1.0 + FastMath.exp(-z)); + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0.0, 1.0); + } else if (p == 0) { + return 0.0; + } else if (p == 1) { + return Double.POSITIVE_INFINITY; + } + return s * Math.log(p / (1.0 - p)) + mu; + } + + /** {@inheritDoc} */ + public double getNumericalMean() { + return mu; + } + + /** {@inheritDoc} */ + public double getNumericalVariance() { + return (MathUtils.PI_SQUARED / 3.0) * (1.0 / (s * s)); + } + + /** {@inheritDoc} */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** {@inheritDoc} */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportConnected() { + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.java new file mode 100644 index 000000000..a0aea1213 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Multivariate normal mixture distribution. + * This class is mainly syntactic sugar. + * + * @see MixtureMultivariateRealDistribution + * @since 3.2 + */ +public class MixtureMultivariateNormalDistribution + extends MixtureMultivariateRealDistribution { + + /** + * Creates a multivariate normal mixture distribution. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c Well19937c} as random + * generator to be used for sampling only (see {@link #sample()} and + * {@link #sample(int)}). In case no sampling is needed for the created + * distribution, it is advised to pass {@code null} as random generator via + * the appropriate constructors to avoid the additional initialisation + * overhead. + * + * @param weights Weights of each component. + * @param means Mean vector for each component. + * @param covariances Covariance matrix for each component. + */ + public MixtureMultivariateNormalDistribution(double[] weights, + double[][] means, + double[][][] covariances) { + super(createComponents(weights, means, covariances)); + } + + /** + * Creates a mixture model from a list of distributions and their + * associated weights. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c Well19937c} as random + * generator to be used for sampling only (see {@link #sample()} and + * {@link #sample(int)}). In case no sampling is needed for the created + * distribution, it is advised to pass {@code null} as random generator via + * the appropriate constructors to avoid the additional initialisation + * overhead. + * + * @param components List of (weight, distribution) pairs from which to sample. + */ + public MixtureMultivariateNormalDistribution(List> components) { + super(components); + } + + /** + * Creates a mixture model from a list of distributions and their + * associated weights. + * + * @param rng Random number generator. + * @param components Distributions from which to sample. + * @throws NotPositiveException if any of the weights is negative. + * @throws DimensionMismatchException if not all components have the same + * number of variables. + */ + public MixtureMultivariateNormalDistribution(RandomGenerator rng, + List> components) + throws NotPositiveException, DimensionMismatchException { + super(rng, components); + } + + /** + * @param weights Weights of each component. + * @param means Mean vector for each component. + * @param covariances Covariance matrix for each component. + * @return the list of components. + */ + private static List> createComponents(double[] weights, + double[][] means, + double[][][] covariances) { + final List> mvns + = new ArrayList>(weights.length); + + for (int i = 0; i < weights.length; i++) { + final MultivariateNormalDistribution dist + = new MultivariateNormalDistribution(means[i], covariances[i]); + + mvns.add(new Pair(weights[i], dist)); + } + + return mvns; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java new file mode 100644 index 000000000..60045b604 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Class for representing + * mixture model distributions. + * + * @param Type of the mixture components. + * + * @since 3.1 + */ +public class MixtureMultivariateRealDistribution + extends AbstractMultivariateRealDistribution { + /** Normalized weight of each mixture component. */ + private final double[] weight; + /** Mixture components. */ + private final List distribution; + + /** + * Creates a mixture model from a list of distributions and their + * associated weights. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param components List of (weight, distribution) pairs from which to sample. + */ + public MixtureMultivariateRealDistribution(List> components) { + this(new Well19937c(), components); + } + + /** + * Creates a mixture model from a list of distributions and their + * associated weights. + * + * @param rng Random number generator. + * @param components Distributions from which to sample. + * @throws NotPositiveException if any of the weights is negative. + * @throws DimensionMismatchException if not all components have the same + * number of variables. + */ + public MixtureMultivariateRealDistribution(RandomGenerator rng, + List> components) { + super(rng, components.get(0).getSecond().getDimension()); + + final int numComp = components.size(); + final int dim = getDimension(); + double weightSum = 0; + for (int i = 0; i < numComp; i++) { + final Pair comp = components.get(i); + if (comp.getSecond().getDimension() != dim) { + throw new DimensionMismatchException(comp.getSecond().getDimension(), dim); + } + if (comp.getFirst() < 0) { + throw new NotPositiveException(comp.getFirst()); + } + weightSum += comp.getFirst(); + } + + // Check for overflow. + if (Double.isInfinite(weightSum)) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } + + // Store each distribution and its normalized weight. + distribution = new ArrayList(); + weight = new double[numComp]; + for (int i = 0; i < numComp; i++) { + final Pair comp = components.get(i); + weight[i] = comp.getFirst() / weightSum; + distribution.add(comp.getSecond()); + } + } + + /** {@inheritDoc} */ + public double density(final double[] values) { + double p = 0; + for (int i = 0; i < weight.length; i++) { + p += weight[i] * distribution.get(i).density(values); + } + return p; + } + + /** {@inheritDoc} */ + @Override + public double[] sample() { + // Sampled values. + double[] vals = null; + + // Determine which component to sample from. + final double randomValue = random.nextDouble(); + double sum = 0; + + for (int i = 0; i < weight.length; i++) { + sum += weight[i]; + if (randomValue <= sum) { + // pick model i + vals = distribution.get(i).sample(); + break; + } + } + + if (vals == null) { + // This should never happen, but it ensures we won't return a null in + // case the loop above has some floating point inequality problem on + // the final iteration. + vals = distribution.get(weight.length - 1).sample(); + } + + return vals; + } + + /** {@inheritDoc} */ + @Override + public void reseedRandomGenerator(long seed) { + // Seed needs to be propagated to underlying components + // in order to maintain consistency between runs. + super.reseedRandomGenerator(seed); + + for (int i = 0; i < distribution.size(); i++) { + // Make each component's seed different in order to avoid + // using the same sequence of random numbers. + distribution.get(i).reseedRandomGenerator(i + 1 + seed); + } + } + + /** + * Gets the distributions that make up the mixture model. + * + * @return the component distributions and associated weights. + */ + public List> getComponents() { + final List> list = new ArrayList>(weight.length); + + for (int i = 0; i < weight.length; i++) { + list.add(new Pair(weight[i], distribution.get(i))); + } + + return list; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateNormalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateNormalDistribution.java new file mode 100644 index 000000000..0faad3420 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateNormalDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implementation of the multivariate normal (Gaussian) distribution. + * + * @see + * Multivariate normal distribution (Wikipedia) + * @see + * Multivariate normal distribution (MathWorld) + * + * @since 3.1 + */ +public class MultivariateNormalDistribution + extends AbstractMultivariateRealDistribution { + /** Vector of means. */ + private final double[] means; + /** Covariance matrix. */ + private final RealMatrix covarianceMatrix; + /** The matrix inverse of the covariance matrix. */ + private final RealMatrix covarianceMatrixInverse; + /** The determinant of the covariance matrix. */ + private final double covarianceMatrixDeterminant; + /** Matrix used in computation of samples. */ + private final RealMatrix samplingMatrix; + + /** + * Creates a multivariate normal distribution with the given mean vector and + * covariance matrix. + *
+ * The number of dimensions is equal to the length of the mean vector + * and to the number of rows and columns of the covariance matrix. + * It is frequently written as "p" in formulae. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param means Vector of means. + * @param covariances Covariance matrix. + * @throws DimensionMismatchException if the arrays length are + * inconsistent. + * @throws SingularMatrixException if the eigenvalue decomposition cannot + * be performed on the provided covariance matrix. + * @throws NonPositiveDefiniteMatrixException if any of the eigenvalues is + * negative. + */ + public MultivariateNormalDistribution(final double[] means, + final double[][] covariances) + throws SingularMatrixException, + DimensionMismatchException, + NonPositiveDefiniteMatrixException { + this(new Well19937c(), means, covariances); + } + + /** + * Creates a multivariate normal distribution with the given mean vector and + * covariance matrix. + *
+ * The number of dimensions is equal to the length of the mean vector + * and to the number of rows and columns of the covariance matrix. + * It is frequently written as "p" in formulae. + * + * @param rng Random Number Generator. + * @param means Vector of means. + * @param covariances Covariance matrix. + * @throws DimensionMismatchException if the arrays length are + * inconsistent. + * @throws SingularMatrixException if the eigenvalue decomposition cannot + * be performed on the provided covariance matrix. + * @throws NonPositiveDefiniteMatrixException if any of the eigenvalues is + * negative. + */ + public MultivariateNormalDistribution(RandomGenerator rng, + final double[] means, + final double[][] covariances) + throws SingularMatrixException, + DimensionMismatchException, + NonPositiveDefiniteMatrixException { + super(rng, means.length); + + final int dim = means.length; + + if (covariances.length != dim) { + throw new DimensionMismatchException(covariances.length, dim); + } + + for (int i = 0; i < dim; i++) { + if (dim != covariances[i].length) { + throw new DimensionMismatchException(covariances[i].length, dim); + } + } + + this.means = MathArrays.copyOf(means); + + covarianceMatrix = new Array2DRowRealMatrix(covariances); + + // Covariance matrix eigen decomposition. + final EigenDecomposition covMatDec = new EigenDecomposition(covarianceMatrix); + + // Compute and store the inverse. + covarianceMatrixInverse = covMatDec.getSolver().getInverse(); + // Compute and store the determinant. + covarianceMatrixDeterminant = covMatDec.getDeterminant(); + + // Eigenvalues of the covariance matrix. + final double[] covMatEigenvalues = covMatDec.getRealEigenvalues(); + + for (int i = 0; i < covMatEigenvalues.length; i++) { + if (covMatEigenvalues[i] < 0) { + throw new NonPositiveDefiniteMatrixException(covMatEigenvalues[i], i, 0); + } + } + + // Matrix where each column is an eigenvector of the covariance matrix. + final Array2DRowRealMatrix covMatEigenvectors = new Array2DRowRealMatrix(dim, dim); + for (int v = 0; v < dim; v++) { + final double[] evec = covMatDec.getEigenvector(v).toArray(); + covMatEigenvectors.setColumn(v, evec); + } + + final RealMatrix tmpMatrix = covMatEigenvectors.transpose(); + + // Scale each eigenvector by the square root of its eigenvalue. + for (int row = 0; row < dim; row++) { + final double factor = FastMath.sqrt(covMatEigenvalues[row]); + for (int col = 0; col < dim; col++) { + tmpMatrix.multiplyEntry(row, col, factor); + } + } + + samplingMatrix = covMatEigenvectors.multiply(tmpMatrix); + } + + /** + * Gets the mean vector. + * + * @return the mean vector. + */ + public double[] getMeans() { + return MathArrays.copyOf(means); + } + + /** + * Gets the covariance matrix. + * + * @return the covariance matrix. + */ + public RealMatrix getCovariances() { + return covarianceMatrix.copy(); + } + + /** {@inheritDoc} */ + public double density(final double[] vals) throws DimensionMismatchException { + final int dim = getDimension(); + if (vals.length != dim) { + throw new DimensionMismatchException(vals.length, dim); + } + + return FastMath.pow(2 * FastMath.PI, -0.5 * dim) * + FastMath.pow(covarianceMatrixDeterminant, -0.5) * + getExponentTerm(vals); + } + + /** + * Gets the square root of each element on the diagonal of the covariance + * matrix. + * + * @return the standard deviations. + */ + public double[] getStandardDeviations() { + final int dim = getDimension(); + final double[] std = new double[dim]; + final double[][] s = covarianceMatrix.getData(); + for (int i = 0; i < dim; i++) { + std[i] = FastMath.sqrt(s[i][i]); + } + return std; + } + + /** {@inheritDoc} */ + @Override + public double[] sample() { + final int dim = getDimension(); + final double[] normalVals = new double[dim]; + + for (int i = 0; i < dim; i++) { + normalVals[i] = random.nextGaussian(); + } + + final double[] vals = samplingMatrix.operate(normalVals); + + for (int i = 0; i < dim; i++) { + vals[i] += means[i]; + } + + return vals; + } + + /** + * Computes the term used in the exponent (see definition of the distribution). + * + * @param values Values at which to compute density. + * @return the multiplication factor of density calculations. + */ + private double getExponentTerm(final double[] values) { + final double[] centered = new double[values.length]; + for (int i = 0; i < centered.length; i++) { + centered[i] = values[i] - getMeans()[i]; + } + final double[] preMultiplied = covarianceMatrixInverse.preMultiply(centered); + double sum = 0; + for (int i = 0; i < preMultiplied.length; i++) { + sum += preMultiplied[i] * centered[i]; + } + return FastMath.exp(-0.5 * sum); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateRealDistribution.java new file mode 100644 index 000000000..dc0d5870d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/MultivariateRealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Base interface for multivariate distributions on the reals. + * + * This is based largely on the RealDistribution interface, but cumulative + * distribution functions are not required because they are often quite + * difficult to compute for multivariate distributions. + * + * @since 3.1 + */ +public interface MultivariateRealDistribution { + /** + * Returns the probability density function (PDF) of this distribution + * evaluated at the specified point {@code x}. In general, the PDF is the + * derivative of the cumulative distribution function. If the derivative + * does not exist at {@code x}, then an appropriate replacement should be + * returned, e.g. {@code Double.POSITIVE_INFINITY}, {@code Double.NaN}, or + * the limit inferior or limit superior of the difference quotient. + * + * @param x Point at which the PDF is evaluated. + * @return the value of the probability density function at point {@code x}. + */ + double density(double[] x); + + /** + * Reseeds the random generator used to generate samples. + * + * @param seed Seed with which to initialize the random number generator. + */ + void reseedRandomGenerator(long seed); + + /** + * Gets the number of random variables of the distribution. + * It is the size of the array returned by the {@link #sample() sample} + * method. + * + * @return the number of variables. + */ + int getDimension(); + + /** + * Generates a random value vector sampled from this distribution. + * + * @return a random value vector. + */ + double[] sample(); + + /** + * Generates a list of a random value vectors from the distribution. + * + * @param sampleSize the number of random vectors to generate. + * @return an array representing the random samples. + * @throws NotStrictlyPositiveException + * if {@code sampleSize} is not positive. + * + * @see #sample() + */ + double[][] sample(int sampleSize) throws NotStrictlyPositiveException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NakagamiDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NakagamiDistribution.java new file mode 100644 index 000000000..70614e703 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NakagamiDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the Nakagami distribution. + * + * @see Nakagami Distribution (Wikipedia) + * + * @since 3.4 + */ +public class NakagamiDistribution extends AbstractRealDistribution { + + /** Default inverse cumulative probability accuracy. */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20141003; + + /** The shape parameter. */ + private final double mu; + /** The scale parameter. */ + private final double omega; + /** Inverse cumulative probability accuracy. */ + private final double inverseAbsoluteAccuracy; + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu shape parameter + * @param omega scale parameter (must be positive) + * @throws NumberIsTooSmallException if {@code mu < 0.5} + * @throws NotStrictlyPositiveException if {@code omega <= 0} + */ + public NakagamiDistribution(double mu, double omega) { + this(mu, omega, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Build a new instance. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mu shape parameter + * @param omega scale parameter (must be positive) + * @param inverseAbsoluteAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NumberIsTooSmallException if {@code mu < 0.5} + * @throws NotStrictlyPositiveException if {@code omega <= 0} + */ + public NakagamiDistribution(double mu, double omega, double inverseAbsoluteAccuracy) { + this(new Well19937c(), mu, omega, inverseAbsoluteAccuracy); + } + + /** + * Build a new instance. + * + * @param rng Random number generator + * @param mu shape parameter + * @param omega scale parameter (must be positive) + * @param inverseAbsoluteAccuracy the maximum absolute error in inverse + * cumulative probability estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NumberIsTooSmallException if {@code mu < 0.5} + * @throws NotStrictlyPositiveException if {@code omega <= 0} + */ + public NakagamiDistribution(RandomGenerator rng, double mu, double omega, double inverseAbsoluteAccuracy) { + super(rng); + + if (mu < 0.5) { + throw new NumberIsTooSmallException(mu, 0.5, true); + } + if (omega <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, omega); + } + + this.mu = mu; + this.omega = omega; + this.inverseAbsoluteAccuracy = inverseAbsoluteAccuracy; + } + + /** + * Access the shape parameter, {@code mu}. + * + * @return the shape parameter. + */ + public double getShape() { + return mu; + } + + /** + * Access the scale parameter, {@code omega}. + * + * @return the scale parameter. + */ + public double getScale() { + return omega; + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return inverseAbsoluteAccuracy; + } + + /** {@inheritDoc} */ + public double density(double x) { + if (x <= 0) { + return 0.0; + } + return 2.0 * FastMath.pow(mu, mu) / (Gamma.gamma(mu) * FastMath.pow(omega, mu)) * + FastMath.pow(x, 2 * mu - 1) * FastMath.exp(-mu * x * x / omega); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + return Gamma.regularizedGammaP(mu, mu * x * x / omega); + } + + /** {@inheritDoc} */ + public double getNumericalMean() { + return Gamma.gamma(mu + 0.5) / Gamma.gamma(mu) * FastMath.sqrt(omega / mu); + } + + /** {@inheritDoc} */ + public double getNumericalVariance() { + double v = Gamma.gamma(mu + 0.5) / Gamma.gamma(mu); + return omega * (1 - 1 / mu * v * v); + } + + /** {@inheritDoc} */ + public double getSupportLowerBound() { + return 0; + } + + /** {@inheritDoc} */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportConnected() { + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NormalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NormalDistribution.java new file mode 100644 index 000000000..44dfa677d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/NormalDistribution.java @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Erf; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the normal (gaussian) distribution. + * + * @see Normal distribution (Wikipedia) + * @see Normal distribution (MathWorld) + */ +public class NormalDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = 8589540077390120676L; + /** √(2) */ + private static final double SQRT2 = FastMath.sqrt(2.0); + /** Mean of this distribution. */ + private final double mean; + /** Standard deviation of this distribution. */ + private final double standardDeviation; + /** The value of {@code log(sd) + 0.5*log(2*pi)} stored for faster computation. */ + private final double logStandardDeviationPlusHalfLog2Pi; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Create a normal distribution with mean equal to zero and standard + * deviation equal to one. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + */ + public NormalDistribution() { + this(0, 1); + } + + /** + * Create a normal distribution using the given mean and standard deviation. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mean Mean for this distribution. + * @param sd Standard deviation for this distribution. + * @throws NotStrictlyPositiveException if {@code sd <= 0}. + */ + public NormalDistribution(double mean, double sd) + throws NotStrictlyPositiveException { + this(mean, sd, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a normal distribution using the given mean, standard deviation and + * inverse cumulative distribution accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param mean Mean for this distribution. + * @param sd Standard deviation for this distribution. + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code sd <= 0}. + * @since 2.1 + */ + public NormalDistribution(double mean, double sd, double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), mean, sd, inverseCumAccuracy); + } + + /** + * Creates a normal distribution. + * + * @param rng Random number generator. + * @param mean Mean for this distribution. + * @param sd Standard deviation for this distribution. + * @throws NotStrictlyPositiveException if {@code sd <= 0}. + * @since 3.3 + */ + public NormalDistribution(RandomGenerator rng, double mean, double sd) + throws NotStrictlyPositiveException { + this(rng, mean, sd, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a normal distribution. + * + * @param rng Random number generator. + * @param mean Mean for this distribution. + * @param sd Standard deviation for this distribution. + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code sd <= 0}. + * @since 3.1 + */ + public NormalDistribution(RandomGenerator rng, + double mean, + double sd, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (sd <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.STANDARD_DEVIATION, sd); + } + + this.mean = mean; + standardDeviation = sd; + logStandardDeviationPlusHalfLog2Pi = FastMath.log(sd) + 0.5 * FastMath.log(2 * FastMath.PI); + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Access the mean. + * + * @return the mean for this distribution. + */ + public double getMean() { + return mean; + } + + /** + * Access the standard deviation. + * + * @return the standard deviation for this distribution. + */ + public double getStandardDeviation() { + return standardDeviation; + } + + /** {@inheritDoc} */ + public double density(double x) { + return FastMath.exp(logDensity(x)); + } + + /** {@inheritDoc} */ + @Override + public double logDensity(double x) { + final double x0 = x - mean; + final double x1 = x0 / standardDeviation; + return -0.5 * x1 * x1 - logStandardDeviationPlusHalfLog2Pi; + } + + /** + * {@inheritDoc} + * + * If {@code x} is more than 40 standard deviations from the mean, 0 or 1 + * is returned, as in these cases the actual value is within + * {@code Double.MIN_VALUE} of 0 or 1. + */ + public double cumulativeProbability(double x) { + final double dev = x - mean; + if (FastMath.abs(dev) > 40 * standardDeviation) { + return dev < 0 ? 0.0d : 1.0d; + } + return 0.5 * Erf.erfc(-dev / (standardDeviation * SQRT2)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + @Override + public double inverseCumulativeProbability(final double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + return mean + standardDeviation * SQRT2 * Erf.erfInv(2 * p - 1); + } + + /** + * {@inheritDoc} + * + * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)} + */ + @Override@Deprecated + public double cumulativeProbability(double x0, double x1) + throws NumberIsTooLargeException { + return probability(x0, x1); + } + + /** {@inheritDoc} */ + @Override + public double probability(double x0, + double x1) + throws NumberIsTooLargeException { + if (x0 > x1) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, + x0, x1, true); + } + final double denom = standardDeviation * SQRT2; + final double v0 = (x0 - mean) / denom; + final double v1 = (x1 - mean) / denom; + return 0.5 * Erf.erf(v0, v1); + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For mean parameter {@code mu}, the mean is {@code mu}. + */ + public double getNumericalMean() { + return getMean(); + } + + /** + * {@inheritDoc} + * + * For standard deviation parameter {@code s}, the variance is {@code s^2}. + */ + public double getNumericalVariance() { + final double s = getStandardDeviation(); + return s * s; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always negative infinity + * no matter the parameters. + * + * @return lower bound of the support (always + * {@code Double.NEGATIVE_INFINITY}) + */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the parameters. + * + * @return upper bound of the support (always + * {@code Double.POSITIVE_INFINITY}) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double sample() { + return standardDeviation * random.nextGaussian() + mean; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ParetoDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ParetoDistribution.java new file mode 100644 index 000000000..49f4517a8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ParetoDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Pareto distribution. + * + *

+ * Parameters: + * The probability distribution function of {@code X} is given by (for {@code x >= k}): + *

+ *  α * k^α / x^(α + 1)
+ * 
+ *

+ *

    + *
  • {@code k} is the scale parameter: this is the minimum possible value of {@code X},
  • + *
  • {@code α} is the shape parameter: this is the Pareto index
  • + *
+ * + * @see + * Pareto distribution (Wikipedia) + * @see + * Pareto distribution (MathWorld) + * + * @since 3.3 + */ +public class ParetoDistribution extends AbstractRealDistribution { + + /** Default inverse cumulative probability accuracy. */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20130424; + + /** The scale parameter of this distribution. */ + private final double scale; + + /** The shape parameter of this distribution. */ + private final double shape; + + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Create a Pareto distribution with a scale of {@code 1} and a shape of {@code 1}. + */ + public ParetoDistribution() { + this(1, 1); + } + + /** + * Create a Pareto distribution using the specified scale and shape. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param scale the scale parameter of this distribution + * @param shape the shape parameter of this distribution + * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}. + */ + public ParetoDistribution(double scale, double shape) + throws NotStrictlyPositiveException { + this(scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a Pareto distribution using the specified scale, shape and + * inverse cumulative distribution accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param scale the scale parameter of this distribution + * @param shape the shape parameter of this distribution + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}. + */ + public ParetoDistribution(double scale, double shape, double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), scale, shape, inverseCumAccuracy); + } + + /** + * Creates a Pareto distribution. + * + * @param rng Random number generator. + * @param scale Scale parameter of this distribution. + * @param shape Shape parameter of this distribution. + * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}. + */ + public ParetoDistribution(RandomGenerator rng, double scale, double shape) + throws NotStrictlyPositiveException { + this(rng, scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a Pareto distribution. + * + * @param rng Random number generator. + * @param scale Scale parameter of this distribution. + * @param shape Shape parameter of this distribution. + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}. + */ + public ParetoDistribution(RandomGenerator rng, + double scale, + double shape, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (scale <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale); + } + + if (shape <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape); + } + + this.scale = scale; + this.shape = shape; + this.solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Returns the scale parameter of this distribution. + * + * @return the scale parameter + */ + public double getScale() { + return scale; + } + + /** + * Returns the shape parameter of this distribution. + * + * @return the shape parameter + */ + public double getShape() { + return shape; + } + + /** + * {@inheritDoc} + *

+ * For scale {@code k}, and shape {@code α} of this distribution, the PDF + * is given by + *

    + *
  • {@code 0} if {@code x < k},
  • + *
  • {@code α * k^α / x^(α + 1)} otherwise.
  • + *
+ */ + public double density(double x) { + if (x < scale) { + return 0; + } + return FastMath.pow(scale, shape) / FastMath.pow(x, shape + 1) * shape; + } + + /** {@inheritDoc} + * + * See documentation of {@link #density(double)} for computation details. + */ + @Override + public double logDensity(double x) { + if (x < scale) { + return Double.NEGATIVE_INFINITY; + } + return FastMath.log(scale) * shape - FastMath.log(x) * (shape + 1) + FastMath.log(shape); + } + + /** + * {@inheritDoc} + *

+ * For scale {@code k}, and shape {@code α} of this distribution, the CDF is given by + *

    + *
  • {@code 0} if {@code x < k},
  • + *
  • {@code 1 - (k / x)^α} otherwise.
  • + *
+ */ + public double cumulativeProbability(double x) { + if (x <= scale) { + return 0; + } + return 1 - FastMath.pow(scale / x, shape); + } + + /** + * {@inheritDoc} + * + * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)} + */ + @Override + @Deprecated + public double cumulativeProbability(double x0, double x1) + throws NumberIsTooLargeException { + return probability(x0, x1); + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + *

+ * For scale {@code k} and shape {@code α}, the mean is given by + *

    + *
  • {@code ∞} if {@code α <= 1},
  • + *
  • {@code α * k / (α - 1)} otherwise.
  • + *
+ */ + public double getNumericalMean() { + if (shape <= 1) { + return Double.POSITIVE_INFINITY; + } + return shape * scale / (shape - 1); + } + + /** + * {@inheritDoc} + *

+ * For scale {@code k} and shape {@code α}, the variance is given by + *

    + *
  • {@code ∞} if {@code 1 < α <= 2},
  • + *
  • {@code k^2 * α / ((α - 1)^2 * (α - 2))} otherwise.
  • + *
+ */ + public double getNumericalVariance() { + if (shape <= 2) { + return Double.POSITIVE_INFINITY; + } + double s = shape - 1; + return scale * scale * shape / (s * s) / (shape - 2); + } + + /** + * {@inheritDoc} + *

+ * The lower bound of the support is equal to the scale parameter {@code k}. + * + * @return lower bound of the support + */ + public double getSupportLowerBound() { + return scale; + } + + /** + * {@inheritDoc} + *

+ * The upper bound of the support is always positive infinity no matter the parameters. + * + * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY}) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + *

+ * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double sample() { + final double n = random.nextDouble(); + return scale / FastMath.pow(n, 1 / shape); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PascalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PascalDistribution.java new file mode 100644 index 000000000..29ee2e1af --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PascalDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Beta; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + *

+ * Implementation of the Pascal distribution. The Pascal distribution is a + * special case of the Negative Binomial distribution where the number of + * successes parameter is an integer. + *

+ *

+ * There are various ways to express the probability mass and distribution + * functions for the Pascal distribution. The present implementation represents + * the distribution of the number of failures before {@code r} successes occur. + * This is the convention adopted in e.g. + * MathWorld, + * but not in + * Wikipedia. + *

+ *

+ * For a random variable {@code X} whose values are distributed according to this + * distribution, the probability mass function is given by
+ * {@code P(X = k) = C(k + r - 1, r - 1) * p^r * (1 - p)^k,}
+ * where {@code r} is the number of successes, {@code p} is the probability of + * success, and {@code X} is the total number of failures. {@code C(n, k)} is + * the binomial coefficient ({@code n} choose {@code k}). The mean and variance + * of {@code X} are
+ * {@code E(X) = (1 - p) * r / p, var(X) = (1 - p) * r / p^2.}
+ * Finally, the cumulative distribution function is given by
+ * {@code P(X <= k) = I(p, r, k + 1)}, + * where I is the regularized incomplete Beta function. + *

+ * + * @see + * Negative binomial distribution (Wikipedia) + * @see + * Negative binomial distribution (MathWorld) + * @since 1.2 (changed to concrete class in 3.0) + */ +public class PascalDistribution extends AbstractIntegerDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = 6751309484392813623L; + /** The number of successes. */ + private final int numberOfSuccesses; + /** The probability of success. */ + private final double probabilityOfSuccess; + /** The value of {@code log(p)}, where {@code p} is the probability of success, + * stored for faster computation. */ + private final double logProbabilityOfSuccess; + /** The value of {@code log(1-p)}, where {@code p} is the probability of success, + * stored for faster computation. */ + private final double log1mProbabilityOfSuccess; + + /** + * Create a Pascal distribution with the given number of successes and + * probability of success. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param r Number of successes. + * @param p Probability of success. + * @throws NotStrictlyPositiveException if the number of successes is not positive + * @throws OutOfRangeException if the probability of success is not in the + * range {@code [0, 1]}. + */ + public PascalDistribution(int r, double p) + throws NotStrictlyPositiveException, OutOfRangeException { + this(new Well19937c(), r, p); + } + + /** + * Create a Pascal distribution with the given number of successes and + * probability of success. + * + * @param rng Random number generator. + * @param r Number of successes. + * @param p Probability of success. + * @throws NotStrictlyPositiveException if the number of successes is not positive + * @throws OutOfRangeException if the probability of success is not in the + * range {@code [0, 1]}. + * @since 3.1 + */ + public PascalDistribution(RandomGenerator rng, + int r, + double p) + throws NotStrictlyPositiveException, OutOfRangeException { + super(rng); + + if (r <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SUCCESSES, + r); + } + if (p < 0 || p > 1) { + throw new OutOfRangeException(p, 0, 1); + } + + numberOfSuccesses = r; + probabilityOfSuccess = p; + logProbabilityOfSuccess = FastMath.log(p); + log1mProbabilityOfSuccess = FastMath.log1p(-p); + } + + /** + * Access the number of successes for this distribution. + * + * @return the number of successes. + */ + public int getNumberOfSuccesses() { + return numberOfSuccesses; + } + + /** + * Access the probability of success for this distribution. + * + * @return the probability of success. + */ + public double getProbabilityOfSuccess() { + return probabilityOfSuccess; + } + + /** {@inheritDoc} */ + public double probability(int x) { + double ret; + if (x < 0) { + ret = 0.0; + } else { + ret = CombinatoricsUtils.binomialCoefficientDouble(x + + numberOfSuccesses - 1, numberOfSuccesses - 1) * + FastMath.pow(probabilityOfSuccess, numberOfSuccesses) * + FastMath.pow(1.0 - probabilityOfSuccess, x); + } + return ret; + } + + /** {@inheritDoc} */ + @Override + public double logProbability(int x) { + double ret; + if (x < 0) { + ret = Double.NEGATIVE_INFINITY; + } else { + ret = CombinatoricsUtils.binomialCoefficientLog(x + + numberOfSuccesses - 1, numberOfSuccesses - 1) + + logProbabilityOfSuccess * numberOfSuccesses + + log1mProbabilityOfSuccess * x; + } + return ret; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + double ret; + if (x < 0) { + ret = 0.0; + } else { + ret = Beta.regularizedBeta(probabilityOfSuccess, + numberOfSuccesses, x + 1.0); + } + return ret; + } + + /** + * {@inheritDoc} + * + * For number of successes {@code r} and probability of success {@code p}, + * the mean is {@code r * (1 - p) / p}. + */ + public double getNumericalMean() { + final double p = getProbabilityOfSuccess(); + final double r = getNumberOfSuccesses(); + return (r * (1 - p)) / p; + } + + /** + * {@inheritDoc} + * + * For number of successes {@code r} and probability of success {@code p}, + * the variance is {@code r * (1 - p) / p^2}. + */ + public double getNumericalVariance() { + final double p = getProbabilityOfSuccess(); + final double r = getNumberOfSuccesses(); + return r * (1 - p) / (p * p); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public int getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity no matter the + * parameters. Positive infinity is symbolized by {@code Integer.MAX_VALUE}. + * + * @return upper bound of the support (always {@code Integer.MAX_VALUE} + * for positive infinity) + */ + public int getSupportUpperBound() { + return Integer.MAX_VALUE; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PoissonDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PoissonDistribution.java new file mode 100644 index 000000000..2f37d8154 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/PoissonDistribution.java @@ -0,0 +1,395 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of the Poisson distribution. + * + * @see Poisson distribution (Wikipedia) + * @see Poisson distribution (MathWorld) + */ +public class PoissonDistribution extends AbstractIntegerDistribution { + /** + * Default maximum number of iterations for cumulative probability calculations. + * @since 2.1 + */ + public static final int DEFAULT_MAX_ITERATIONS = 10000000; + /** + * Default convergence criterion. + * @since 2.1 + */ + public static final double DEFAULT_EPSILON = 1e-12; + /** Serializable version identifier. */ + private static final long serialVersionUID = -3349935121172596109L; + /** Distribution used to compute normal approximation. */ + private final NormalDistribution normal; + /** Distribution needed for the {@link #sample()} method. */ + private final ExponentialDistribution exponential; + /** Mean of the distribution. */ + private final double mean; + + /** + * Maximum number of iterations for cumulative probability. Cumulative + * probabilities are estimated using either Lanczos series approximation + * of {@link Gamma#regularizedGammaP(double, double, double, int)} + * or continued fraction approximation of + * {@link Gamma#regularizedGammaQ(double, double, double, int)}. + */ + private final int maxIterations; + + /** Convergence criterion for cumulative probability. */ + private final double epsilon; + + /** + * Creates a new Poisson distribution with specified mean. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param p the Poisson mean + * @throws NotStrictlyPositiveException if {@code p <= 0}. + */ + public PoissonDistribution(double p) throws NotStrictlyPositiveException { + this(p, DEFAULT_EPSILON, DEFAULT_MAX_ITERATIONS); + } + + /** + * Creates a new Poisson distribution with specified mean, convergence + * criterion and maximum number of iterations. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param p Poisson mean. + * @param epsilon Convergence criterion for cumulative probabilities. + * @param maxIterations the maximum number of iterations for cumulative + * probabilities. + * @throws NotStrictlyPositiveException if {@code p <= 0}. + * @since 2.1 + */ + public PoissonDistribution(double p, double epsilon, int maxIterations) + throws NotStrictlyPositiveException { + this(new Well19937c(), p, epsilon, maxIterations); + } + + /** + * Creates a new Poisson distribution with specified mean, convergence + * criterion and maximum number of iterations. + * + * @param rng Random number generator. + * @param p Poisson mean. + * @param epsilon Convergence criterion for cumulative probabilities. + * @param maxIterations the maximum number of iterations for cumulative + * probabilities. + * @throws NotStrictlyPositiveException if {@code p <= 0}. + * @since 3.1 + */ + public PoissonDistribution(RandomGenerator rng, + double p, + double epsilon, + int maxIterations) + throws NotStrictlyPositiveException { + super(rng); + + if (p <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, p); + } + mean = p; + this.epsilon = epsilon; + this.maxIterations = maxIterations; + + // Use the same RNG instance as the parent class. + normal = new NormalDistribution(rng, p, FastMath.sqrt(p), + NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + exponential = new ExponentialDistribution(rng, 1, + ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a new Poisson distribution with the specified mean and + * convergence criterion. + * + * @param p Poisson mean. + * @param epsilon Convergence criterion for cumulative probabilities. + * @throws NotStrictlyPositiveException if {@code p <= 0}. + * @since 2.1 + */ + public PoissonDistribution(double p, double epsilon) + throws NotStrictlyPositiveException { + this(p, epsilon, DEFAULT_MAX_ITERATIONS); + } + + /** + * Creates a new Poisson distribution with the specified mean and maximum + * number of iterations. + * + * @param p Poisson mean. + * @param maxIterations Maximum number of iterations for cumulative + * probabilities. + * @since 2.1 + */ + public PoissonDistribution(double p, int maxIterations) { + this(p, DEFAULT_EPSILON, maxIterations); + } + + /** + * Get the mean for the distribution. + * + * @return the mean for the distribution. + */ + public double getMean() { + return mean; + } + + /** {@inheritDoc} */ + public double probability(int x) { + final double logProbability = logProbability(x); + return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability); + } + + /** {@inheritDoc} */ + @Override + public double logProbability(int x) { + double ret; + if (x < 0 || x == Integer.MAX_VALUE) { + ret = Double.NEGATIVE_INFINITY; + } else if (x == 0) { + ret = -mean; + } else { + ret = -SaddlePointExpansion.getStirlingError(x) - + SaddlePointExpansion.getDeviancePart(x, mean) - + 0.5 * FastMath.log(MathUtils.TWO_PI) - 0.5 * FastMath.log(x); + } + return ret; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + if (x < 0) { + return 0; + } + if (x == Integer.MAX_VALUE) { + return 1; + } + return Gamma.regularizedGammaQ((double) x + 1, mean, epsilon, + maxIterations); + } + + /** + * Calculates the Poisson distribution function using a normal + * approximation. The {@code N(mean, sqrt(mean))} distribution is used + * to approximate the Poisson distribution. The computation uses + * "half-correction" (evaluating the normal distribution function at + * {@code x + 0.5}). + * + * @param x Upper bound, inclusive. + * @return the distribution function value calculated using a normal + * approximation. + */ + public double normalApproximateProbability(int x) { + // calculate the probability using half-correction + return normal.cumulativeProbability(x + 0.5); + } + + /** + * {@inheritDoc} + * + * For mean parameter {@code p}, the mean is {@code p}. + */ + public double getNumericalMean() { + return getMean(); + } + + /** + * {@inheritDoc} + * + * For mean parameter {@code p}, the variance is {@code p}. + */ + public double getNumericalVariance() { + return getMean(); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the mean parameter. + * + * @return lower bound of the support (always 0) + */ + public int getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is positive infinity, + * regardless of the parameter values. There is no integer infinity, + * so this method returns {@code Integer.MAX_VALUE}. + * + * @return upper bound of the support (always {@code Integer.MAX_VALUE} for + * positive infinity) + */ + public int getSupportUpperBound() { + return Integer.MAX_VALUE; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + *

+ * Algorithm Description: + *

    + *
  • For small means, uses simulation of a Poisson process + * using Uniform deviates, as described + * here. + * The Poisson process (and hence value returned) is bounded by 1000 * mean. + *
  • + *
  • For large means, uses the rejection algorithm described in + *
    + * Devroye, Luc. (1981).The Computer Generation of Poisson Random Variables
    + * Computing vol. 26 pp. 197-207.
    + *
    + *
  • + *
+ *

+ * + * @return a random value. + * @since 2.2 + */ + @Override + public int sample() { + return (int) FastMath.min(nextPoisson(mean), Integer.MAX_VALUE); + } + + /** + * @param meanPoisson Mean of the Poisson distribution. + * @return the next sample. + */ + private long nextPoisson(double meanPoisson) { + final double pivot = 40.0d; + if (meanPoisson < pivot) { + double p = FastMath.exp(-meanPoisson); + long n = 0; + double r = 1.0d; + double rnd = 1.0d; + + while (n < 1000 * meanPoisson) { + rnd = random.nextDouble(); + r *= rnd; + if (r >= p) { + n++; + } else { + return n; + } + } + return n; + } else { + final double lambda = FastMath.floor(meanPoisson); + final double lambdaFractional = meanPoisson - lambda; + final double logLambda = FastMath.log(lambda); + final double logLambdaFactorial = CombinatoricsUtils.factorialLog((int) lambda); + final long y2 = lambdaFractional < Double.MIN_VALUE ? 0 : nextPoisson(lambdaFractional); + final double delta = FastMath.sqrt(lambda * FastMath.log(32 * lambda / FastMath.PI + 1)); + final double halfDelta = delta / 2; + final double twolpd = 2 * lambda + delta; + final double a1 = FastMath.sqrt(FastMath.PI * twolpd) * FastMath.exp(1 / (8 * lambda)); + final double a2 = (twolpd / delta) * FastMath.exp(-delta * (1 + delta) / twolpd); + final double aSum = a1 + a2 + 1; + final double p1 = a1 / aSum; + final double p2 = a2 / aSum; + final double c1 = 1 / (8 * lambda); + + double x = 0; + double y = 0; + double v = 0; + int a = 0; + double t = 0; + double qr = 0; + double qa = 0; + for (;;) { + final double u = random.nextDouble(); + if (u <= p1) { + final double n = random.nextGaussian(); + x = n * FastMath.sqrt(lambda + halfDelta) - 0.5d; + if (x > delta || x < -lambda) { + continue; + } + y = x < 0 ? FastMath.floor(x) : FastMath.ceil(x); + final double e = exponential.sample(); + v = -e - (n * n / 2) + c1; + } else { + if (u > p1 + p2) { + y = lambda; + break; + } else { + x = delta + (twolpd / delta) * exponential.sample(); + y = FastMath.ceil(x); + v = -exponential.sample() - delta * (x + 1) / twolpd; + } + } + a = x < 0 ? 1 : 0; + t = y * (y + 1) / (2 * lambda); + if (v < -t && a == 0) { + y = lambda + y; + break; + } + qr = t * ((2 * y + 1) / (6 * lambda) - 1); + qa = qr - (t * t) / (3 * (lambda + a * (y + 1))); + if (v < qa) { + y = lambda + y; + break; + } + if (v > qr) { + continue; + } + if (v < y * logLambda - CombinatoricsUtils.factorialLog((int) (y + lambda)) + logLambdaFactorial) { + y = lambda + y; + break; + } + } + return y2 + (long) y; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/RealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/RealDistribution.java new file mode 100644 index 000000000..815ac9232 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/RealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Base interface for distributions on the reals. + * + * @since 3.0 + */ +public interface RealDistribution { + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(X = x)}. In other + * words, this method represents the probability mass function (PMF) + * for the distribution. + * + * @param x the point at which the PMF is evaluated + * @return the value of the probability mass function at point {@code x} + */ + double probability(double x); + + /** + * Returns the probability density function (PDF) of this distribution + * evaluated at the specified point {@code x}. In general, the PDF is + * the derivative of the {@link #cumulativeProbability(double) CDF}. + * If the derivative does not exist at {@code x}, then an appropriate + * replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY}, + * {@code Double.NaN}, or the limit inferior or limit superior of the + * difference quotient. + * + * @param x the point at which the PDF is evaluated + * @return the value of the probability density function at point {@code x} + */ + double density(double x); + + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(X <= x)}. In other + * words, this method represents the (cumulative) distribution function + * (CDF) for this distribution. + * + * @param x the point at which the CDF is evaluated + * @return the probability that a random variable with this + * distribution takes a value less than or equal to {@code x} + */ + double cumulativeProbability(double x); + + /** + * For a random variable {@code X} whose values are distributed according + * to this distribution, this method returns {@code P(x0 < X <= x1)}. + * + * @param x0 the exclusive lower bound + * @param x1 the inclusive upper bound + * @return the probability that a random variable with this distribution + * takes a value between {@code x0} and {@code x1}, + * excluding the lower and including the upper endpoint + * @throws NumberIsTooLargeException if {@code x0 > x1} + * + * @deprecated As of 3.1. In 4.0, this method will be renamed + * {@code probability(double x0, double x1)}. + */ + @Deprecated + double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException; + + /** + * Computes the quantile function of this distribution. For a random + * variable {@code X} distributed according to this distribution, the + * returned value is + *
    + *
  • inf{x in R | P(X<=x) >= p} for {@code 0 < p <= 1},
  • + *
  • inf{x in R | P(X<=x) > 0} for {@code p = 0}.
  • + *
+ * + * @param p the cumulative probability + * @return the smallest {@code p}-quantile of this distribution + * (largest 0-quantile for {@code p = 0}) + * @throws OutOfRangeException if {@code p < 0} or {@code p > 1} + */ + double inverseCumulativeProbability(double p) throws OutOfRangeException; + + /** + * Use this method to get the numerical value of the mean of this + * distribution. + * + * @return the mean or {@code Double.NaN} if it is not defined + */ + double getNumericalMean(); + + /** + * Use this method to get the numerical value of the variance of this + * distribution. + * + * @return the variance (possibly {@code Double.POSITIVE_INFINITY} as + * for certain cases in {@link TDistribution}) or {@code Double.NaN} if it + * is not defined + */ + double getNumericalVariance(); + + /** + * Access the lower bound of the support. This method must return the same + * value as {@code inverseCumulativeProbability(0)}. In other words, this + * method must return + *

inf {x in R | P(X <= x) > 0}.

+ * + * @return lower bound of the support (might be + * {@code Double.NEGATIVE_INFINITY}) + */ + double getSupportLowerBound(); + + /** + * Access the upper bound of the support. This method must return the same + * value as {@code inverseCumulativeProbability(1)}. In other words, this + * method must return + *

inf {x in R | P(X <= x) = 1}.

+ * + * @return upper bound of the support (might be + * {@code Double.POSITIVE_INFINITY}) + */ + double getSupportUpperBound(); + + /** + * Whether or not the lower bound of support is in the domain of the density + * function. Returns true iff {@code getSupporLowerBound()} is finite and + * {@code density(getSupportLowerBound())} returns a non-NaN, non-infinite + * value. + * + * @return true if the lower bound of support is finite and the density + * function returns a non-NaN, non-infinite value there + * @deprecated to be removed in 4.0 + */ + @Deprecated + boolean isSupportLowerBoundInclusive(); + + /** + * Whether or not the upper bound of support is in the domain of the density + * function. Returns true iff {@code getSupportUpperBound()} is finite and + * {@code density(getSupportUpperBound())} returns a non-NaN, non-infinite + * value. + * + * @return true if the upper bound of support is finite and the density + * function returns a non-NaN, non-infinite value there + * @deprecated to be removed in 4.0 + */ + @Deprecated + boolean isSupportUpperBoundInclusive(); + + /** + * Use this method to get information about whether the support is connected, + * i.e. whether all values between the lower and upper bound of the support + * are included in the support. + * + * @return whether the support is connected or not + */ + boolean isSupportConnected(); + + /** + * Reseed the random generator used to generate samples. + * + * @param seed the new seed + */ + void reseedRandomGenerator(long seed); + + /** + * Generate a random value sampled from this distribution. + * + * @return a random value. + */ + double sample(); + + /** + * Generate a random sample from the distribution. + * + * @param sampleSize the number of random values to generate + * @return an array representing the random sample + * @throws NotStrictlyPositiveException + * if {@code sampleSize} is not positive + */ + double[] sample(int sampleSize); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/SaddlePointExpansion.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/SaddlePointExpansion.java new file mode 100644 index 000000000..5cc675d7f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/SaddlePointExpansion.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

+ * Utility class used by various distributions to accurately compute their + * respective probability mass functions. The implementation for this class is + * based on the Catherine Loader's dbinom routines. + *

+ *

+ * This class is not intended to be called directly. + *

+ *

+ * References: + *

    + *
  1. Catherine Loader (2000). "Fast and Accurate Computation of Binomial + * Probabilities.". + * http://www.herine.net/stat/papers/dbinom.pdf
  2. + *
+ *

+ * + * @since 2.1 + */ +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. + *

+ * References: + *

    + *
  1. Eric W. Weisstein. "Stirling's Series." From MathWorld--A Wolfram Web + * Resource. + * http://mathworld.wolfram.com/StirlingsSeries.html
  2. + *
+ *

+ * + * @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. + *

+ * References: + *

    + *
  1. Catherine Loader (2000). "Fast and Accurate Computation of Binomial + * Probabilities.". + * http://www.herine.net/stat/papers/dbinom.pdf
  2. + *
+ *

+ * + * @param x the x value. + * @param mu the average. + * @return a part of the deviance. + */ + static double getDeviancePart(double x, double mu) { + double ret; + if (FastMath.abs(x - mu) < 0.1 * (x + mu)) { + double d = x - mu; + double v = d / (x + mu); + double s1 = v * d; + double s = Double.NaN; + double ej = 2.0 * x * v; + v *= v; + int j = 1; + while (s1 != s) { + s = s1; + ej *= v; + s1 = s + ej / ((j * 2) + 1); + ++j; + } + ret = s1; + } else { + ret = x * FastMath.log(x / mu) + mu - x; + } + return ret; + } + + /** + * Compute the logarithm of the PMF for a binomial distribution + * using the saddle point expansion. + * + * @param x the value at which the probability is evaluated. + * @param n the number of trials. + * @param p the probability of success. + * @param q the probability of failure (1 - p). + * @return log(p(x)). + */ + static double logBinomialProbability(int x, int n, double p, double q) { + double ret; + if (x == 0) { + if (p < 0.1) { + ret = -getDeviancePart(n, n * q) - n * p; + } else { + ret = n * FastMath.log(q); + } + } else if (x == n) { + if (q < 0.1) { + ret = -getDeviancePart(n, n * p) - n * q; + } else { + ret = n * FastMath.log(p); + } + } else { + ret = getStirlingError(n) - getStirlingError(x) - + getStirlingError(n - x) - getDeviancePart(x, n * p) - + getDeviancePart(n - x, n * q); + double f = (MathUtils.TWO_PI * x * (n - x)) / n; + ret = -0.5 * FastMath.log(f) + ret; + } + return ret; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TDistribution.java new file mode 100644 index 000000000..5902f1a03 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Beta; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of Student's t-distribution. + * + * @see "Student's t-distribution (Wikipedia)" + * @see "Student's t-distribution (MathWorld)" + */ +public class TDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier */ + private static final long serialVersionUID = -5852615386664158222L; + /** The degrees of freedom. */ + private final double degreesOfFreedom; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + /** Static computation factor based on degreesOfFreedom. */ + private final double factor; + + /** + * Create a t distribution using the given degrees of freedom. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param degreesOfFreedom Degrees of freedom. + * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0} + */ + public TDistribution(double degreesOfFreedom) + throws NotStrictlyPositiveException { + this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a t distribution using the given degrees of freedom and the + * specified inverse cumulative probability absolute accuracy. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param degreesOfFreedom Degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0} + * @since 2.1 + */ + public TDistribution(double degreesOfFreedom, double inverseCumAccuracy) + throws NotStrictlyPositiveException { + this(new Well19937c(), degreesOfFreedom, inverseCumAccuracy); + } + + /** + * Creates a t distribution. + * + * @param rng Random number generator. + * @param degreesOfFreedom Degrees of freedom. + * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0} + * @since 3.3 + */ + public TDistribution(RandomGenerator rng, double degreesOfFreedom) + throws NotStrictlyPositiveException { + this(rng, degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a t distribution. + * + * @param rng Random number generator. + * @param degreesOfFreedom Degrees of freedom. + * @param inverseCumAccuracy the maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0} + * @since 3.1 + */ + public TDistribution(RandomGenerator rng, + double degreesOfFreedom, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (degreesOfFreedom <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DEGREES_OF_FREEDOM, + degreesOfFreedom); + } + this.degreesOfFreedom = degreesOfFreedom; + solverAbsoluteAccuracy = inverseCumAccuracy; + + final double n = degreesOfFreedom; + final double nPlus1Over2 = (n + 1) / 2; + factor = Gamma.logGamma(nPlus1Over2) - + 0.5 * (FastMath.log(FastMath.PI) + FastMath.log(n)) - + Gamma.logGamma(n / 2); + } + + /** + * Access the degrees of freedom. + * + * @return the degrees of freedom. + */ + public double getDegreesOfFreedom() { + return degreesOfFreedom; + } + + /** {@inheritDoc} */ + public double density(double x) { + return FastMath.exp(logDensity(x)); + } + + /** {@inheritDoc} */ + @Override + public double logDensity(double x) { + final double n = degreesOfFreedom; + final double nPlus1Over2 = (n + 1) / 2; + return factor - nPlus1Over2 * FastMath.log(1 + x * x / n); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + double ret; + if (x == 0) { + ret = 0.5; + } else { + double t = + Beta.regularizedBeta( + degreesOfFreedom / (degreesOfFreedom + (x * x)), + 0.5 * degreesOfFreedom, + 0.5); + if (x < 0.0) { + ret = 0.5 * t; + } else { + ret = 1.0 - 0.5 * t; + } + } + + return ret; + } + + /** {@inheritDoc} */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For degrees of freedom parameter {@code df}, the mean is + *

    + *
  • if {@code df > 1} then {@code 0},
  • + *
  • else undefined ({@code Double.NaN}).
  • + *
+ */ + public double getNumericalMean() { + final double df = getDegreesOfFreedom(); + + if (df > 1) { + return 0; + } + + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * For degrees of freedom parameter {@code df}, the variance is + *
    + *
  • if {@code df > 2} then {@code df / (df - 2)},
  • + *
  • if {@code 1 < df <= 2} then positive infinity + * ({@code Double.POSITIVE_INFINITY}),
  • + *
  • else undefined ({@code Double.NaN}).
  • + *
+ */ + public double getNumericalVariance() { + final double df = getDegreesOfFreedom(); + + if (df > 2) { + return df / (df - 2); + } + + if (df > 1 && df <= 2) { + return Double.POSITIVE_INFINITY; + } + + return Double.NaN; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always negative infinity no matter the + * parameters. + * + * @return lower bound of the support (always + * {@code Double.NEGATIVE_INFINITY}) + */ + public double getSupportLowerBound() { + return Double.NEGATIVE_INFINITY; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity no matter the + * parameters. + * + * @return upper bound of the support (always + * {@code Double.POSITIVE_INFINITY}) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return false; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TriangularDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TriangularDistribution.java new file mode 100644 index 000000000..2cae574e5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/TriangularDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the triangular real distribution. + * + * @see + * Triangular distribution (Wikipedia) + * + * @since 3.0 + */ +public class TriangularDistribution extends AbstractRealDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = 20120112L; + /** Lower limit of this distribution (inclusive). */ + private final double a; + /** Upper limit of this distribution (inclusive). */ + private final double b; + /** Mode of this distribution. */ + private final double c; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + + /** + * Creates a triangular real distribution using the given lower limit, + * upper limit, and mode. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param a Lower limit of this distribution (inclusive). + * @param b Upper limit of this distribution (inclusive). + * @param c Mode of this distribution. + * @throws NumberIsTooLargeException if {@code a >= b} or if {@code c > b}. + * @throws NumberIsTooSmallException if {@code c < a}. + */ + public TriangularDistribution(double a, double c, double b) + throws NumberIsTooLargeException, NumberIsTooSmallException { + this(new Well19937c(), a, c, b); + } + + /** + * Creates a triangular distribution. + * + * @param rng Random number generator. + * @param a Lower limit of this distribution (inclusive). + * @param b Upper limit of this distribution (inclusive). + * @param c Mode of this distribution. + * @throws NumberIsTooLargeException if {@code a >= b} or if {@code c > b}. + * @throws NumberIsTooSmallException if {@code c < a}. + * @since 3.1 + */ + public TriangularDistribution(RandomGenerator rng, + double a, + double c, + double b) + throws NumberIsTooLargeException, NumberIsTooSmallException { + super(rng); + + if (a >= b) { + throw new NumberIsTooLargeException( + LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + a, b, false); + } + if (c < a) { + throw new NumberIsTooSmallException( + LocalizedFormats.NUMBER_TOO_SMALL, c, a, true); + } + if (c > b) { + throw new NumberIsTooLargeException( + LocalizedFormats.NUMBER_TOO_LARGE, c, b, true); + } + + this.a = a; + this.c = c; + this.b = b; + solverAbsoluteAccuracy = FastMath.max(FastMath.ulp(a), FastMath.ulp(b)); + } + + /** + * Returns the mode {@code c} of this distribution. + * + * @return the mode {@code c} of this distribution + */ + public double getMode() { + return c; + } + + /** + * {@inheritDoc} + * + *

+ * For this distribution, the returned value is not really meaningful, + * since exact formulas are implemented for the computation of the + * {@link #inverseCumulativeProbability(double)} (no solver is invoked). + *

+ *

+ * For lower limit {@code a} and upper limit {@code b}, the current + * implementation returns {@code max(ulp(a), ulp(b)}. + *

+ */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * For lower limit {@code a}, upper limit {@code b} and mode {@code c}, the + * PDF is given by + *
    + *
  • {@code 2 * (x - a) / [(b - a) * (c - a)]} if {@code a <= x < c},
  • + *
  • {@code 2 / (b - a)} if {@code x = c},
  • + *
  • {@code 2 * (b - x) / [(b - a) * (b - c)]} if {@code c < x <= b},
  • + *
  • {@code 0} otherwise. + *
+ */ + public double density(double x) { + if (x < a) { + return 0; + } + if (a <= x && x < c) { + double divident = 2 * (x - a); + double divisor = (b - a) * (c - a); + return divident / divisor; + } + if (x == c) { + return 2 / (b - a); + } + if (c < x && x <= b) { + double divident = 2 * (b - x); + double divisor = (b - a) * (b - c); + return divident / divisor; + } + return 0; + } + + /** + * {@inheritDoc} + * + * For lower limit {@code a}, upper limit {@code b} and mode {@code c}, the + * CDF is given by + *
    + *
  • {@code 0} if {@code x < a},
  • + *
  • {@code (x - a)^2 / [(b - a) * (c - a)]} if {@code a <= x < c},
  • + *
  • {@code (c - a) / (b - a)} if {@code x = c},
  • + *
  • {@code 1 - (b - x)^2 / [(b - a) * (b - c)]} if {@code c < x <= b},
  • + *
  • {@code 1} if {@code x > b}.
  • + *
+ */ + public double cumulativeProbability(double x) { + if (x < a) { + return 0; + } + if (a <= x && x < c) { + double divident = (x - a) * (x - a); + double divisor = (b - a) * (c - a); + return divident / divisor; + } + if (x == c) { + return (c - a) / (b - a); + } + if (c < x && x <= b) { + double divident = (b - x) * (b - x); + double divisor = (b - a) * (b - c); + return 1 - (divident / divisor); + } + return 1; + } + + /** + * {@inheritDoc} + * + * For lower limit {@code a}, upper limit {@code b}, and mode {@code c}, + * the mean is {@code (a + b + c) / 3}. + */ + public double getNumericalMean() { + return (a + b + c) / 3; + } + + /** + * {@inheritDoc} + * + * For lower limit {@code a}, upper limit {@code b}, and mode {@code c}, + * the variance is {@code (a^2 + b^2 + c^2 - a * b - a * c - b * c) / 18}. + */ + public double getNumericalVariance() { + return (a * a + b * b + c * c - a * b - a * c - b * c) / 18; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is equal to the lower limit parameter + * {@code a} of the distribution. + * + * @return lower bound of the support + */ + public double getSupportLowerBound() { + return a; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is equal to the upper limit parameter + * {@code b} of the distribution. + * + * @return upper bound of the support + */ + public double getSupportUpperBound() { + return b; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(double p) + throws OutOfRangeException { + if (p < 0 || p > 1) { + throw new OutOfRangeException(p, 0, 1); + } + if (p == 0) { + return a; + } + if (p == 1) { + return b; + } + if (p < (c - a) / (b - a)) { + return a + FastMath.sqrt(p * (b - a) * (c - a)); + } + return b - FastMath.sqrt((1 - p) * (b - a) * (b - c)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformIntegerDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformIntegerDistribution.java new file mode 100644 index 000000000..8df7caa83 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformIntegerDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; + +/** + * Implementation of the uniform integer distribution. + * + * @see Uniform distribution (discrete), at Wikipedia + * + * @since 3.0 + */ +public class UniformIntegerDistribution extends AbstractIntegerDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = 20120109L; + /** Lower bound (inclusive) of this distribution. */ + private final int lower; + /** Upper bound (inclusive) of this distribution. */ + private final int upper; + + /** + * Creates a new uniform integer distribution using the given lower and + * upper bounds (both inclusive). + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param lower Lower bound (inclusive) of this distribution. + * @param upper Upper bound (inclusive) of this distribution. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + public UniformIntegerDistribution(int lower, int upper) + throws NumberIsTooLargeException { + this(new Well19937c(), lower, upper); + } + + /** + * Creates a new uniform integer distribution using the given lower and + * upper bounds (both inclusive). + * + * @param rng Random number generator. + * @param lower Lower bound (inclusive) of this distribution. + * @param upper Upper bound (inclusive) of this distribution. + * @throws NumberIsTooLargeException if {@code lower > upper}. + * @since 3.1 + */ + public UniformIntegerDistribution(RandomGenerator rng, + int lower, + int upper) + throws NumberIsTooLargeException { + super(rng); + + if (lower > upper) { + throw new NumberIsTooLargeException( + LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + lower, upper, true); + } + this.lower = lower; + this.upper = upper; + } + + /** {@inheritDoc} */ + public double probability(int x) { + if (x < lower || x > upper) { + return 0; + } + return 1.0 / (upper - lower + 1); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(int x) { + if (x < lower) { + return 0; + } + if (x > upper) { + return 1; + } + return (x - lower + 1.0) / (upper - lower + 1.0); + } + + /** + * {@inheritDoc} + * + * For lower bound {@code lower} and upper bound {@code upper}, the mean is + * {@code 0.5 * (lower + upper)}. + */ + public double getNumericalMean() { + return 0.5 * (lower + upper); + } + + /** + * {@inheritDoc} + * + * For lower bound {@code lower} and upper bound {@code upper}, and + * {@code n = upper - lower + 1}, the variance is {@code (n^2 - 1) / 12}. + */ + public double getNumericalVariance() { + double n = upper - lower + 1; + return (n * n - 1) / 12.0; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is equal to the lower bound parameter + * of the distribution. + * + * @return lower bound of the support + */ + public int getSupportLowerBound() { + return lower; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is equal to the upper bound parameter + * of the distribution. + * + * @return upper bound of the support + */ + public int getSupportUpperBound() { + return upper; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public int sample() { + final int max = (upper - lower) + 1; + if (max <= 0) { + // The range is too wide to fit in a positive int (larger + // than 2^31); as it covers more than half the integer range, + // we use a simple rejection method. + while (true) { + final int r = random.nextInt(); + if (r >= lower && + r <= upper) { + return r; + } + } + } else { + // We can shift the range and directly generate a positive int. + return lower + random.nextInt(max); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformRealDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformRealDistribution.java new file mode 100644 index 000000000..3a5973956 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/UniformRealDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; + +/** + * Implementation of the uniform real distribution. + * + * @see Uniform distribution (continuous), at Wikipedia + * + * @since 3.0 + */ +public class UniformRealDistribution extends AbstractRealDistribution { + /** Default inverse cumulative probability accuracy. + * @deprecated as of 3.2 not used anymore, will be removed in 4.0 + */ + @Deprecated + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = 20120109L; + /** Lower bound of this distribution (inclusive). */ + private final double lower; + /** Upper bound of this distribution (exclusive). */ + private final double upper; + + /** + * Create a standard uniform real distribution with lower bound (inclusive) + * equal to zero and upper bound (exclusive) equal to one. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + */ + public UniformRealDistribution() { + this(0, 1); + } + + /** + * Create a uniform real distribution using the given lower and upper + * bounds. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param lower Lower bound of this distribution (inclusive). + * @param upper Upper bound of this distribution (exclusive). + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + public UniformRealDistribution(double lower, double upper) + throws NumberIsTooLargeException { + this(new Well19937c(), lower, upper); + } + + /** + * Create a uniform distribution. + * + * @param lower Lower bound of this distribution (inclusive). + * @param upper Upper bound of this distribution (exclusive). + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + * @deprecated as of 3.2, inverse CDF is now calculated analytically, use + * {@link #UniformRealDistribution(double, double)} instead. + */ + @Deprecated + public UniformRealDistribution(double lower, double upper, double inverseCumAccuracy) + throws NumberIsTooLargeException { + this(new Well19937c(), lower, upper); + } + + /** + * Creates a uniform distribution. + * + * @param rng Random number generator. + * @param lower Lower bound of this distribution (inclusive). + * @param upper Upper bound of this distribution (exclusive). + * @param inverseCumAccuracy Inverse cumulative probability accuracy. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + * @since 3.1 + * @deprecated as of 3.2, inverse CDF is now calculated analytically, use + * {@link #UniformRealDistribution(RandomGenerator, double, double)} + * instead. + */ + @Deprecated + public UniformRealDistribution(RandomGenerator rng, + double lower, + double upper, + double inverseCumAccuracy){ + this(rng, lower, upper); + } + + /** + * Creates a uniform distribution. + * + * @param rng Random number generator. + * @param lower Lower bound of this distribution (inclusive). + * @param upper Upper bound of this distribution (exclusive). + * @throws NumberIsTooLargeException if {@code lower >= upper}. + * @since 3.1 + */ + public UniformRealDistribution(RandomGenerator rng, + double lower, + double upper) + throws NumberIsTooLargeException { + super(rng); + if (lower >= upper) { + throw new NumberIsTooLargeException( + LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + lower, upper, false); + } + + this.lower = lower; + this.upper = upper; + } + + /** {@inheritDoc} */ + public double density(double x) { + if (x < lower || x > upper) { + return 0.0; + } + return 1 / (upper - lower); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + if (x <= lower) { + return 0; + } + if (x >= upper) { + return 1; + } + return (x - lower) / (upper - lower); + } + + /** {@inheritDoc} */ + @Override + public double inverseCumulativeProbability(final double p) + throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + return p * (upper - lower) + lower; + } + + /** + * {@inheritDoc} + * + * For lower bound {@code lower} and upper bound {@code upper}, the mean is + * {@code 0.5 * (lower + upper)}. + */ + public double getNumericalMean() { + return 0.5 * (lower + upper); + } + + /** + * {@inheritDoc} + * + * For lower bound {@code lower} and upper bound {@code upper}, the + * variance is {@code (upper - lower)^2 / 12}. + */ + public double getNumericalVariance() { + double ul = upper - lower; + return ul * ul / 12; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is equal to the lower bound parameter + * of the distribution. + * + * @return lower bound of the support + */ + public double getSupportLowerBound() { + return lower; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is equal to the upper bound parameter + * of the distribution. + * + * @return upper bound of the support + */ + public double getSupportUpperBound() { + return upper; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** {@inheritDoc} */ + @Override + public double sample() { + final double u = random.nextDouble(); + return u * upper + (1 - u) * lower; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/WeibullDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/WeibullDistribution.java new file mode 100644 index 000000000..917ed71d0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/WeibullDistribution.java @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.special.Gamma; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Weibull distribution. This implementation uses the + * two parameter form of the distribution defined by + * + * Weibull Distribution, equations (1) and (2). + * + * @see Weibull distribution (Wikipedia) + * @see Weibull distribution (MathWorld) + * @since 1.1 (changed to concrete class in 3.0) + */ +public class WeibullDistribution extends AbstractRealDistribution { + /** + * Default inverse cumulative probability accuracy. + * @since 2.1 + */ + public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; + /** Serializable version identifier. */ + private static final long serialVersionUID = 8589540077390120676L; + /** The shape parameter. */ + private final double shape; + /** The scale parameter. */ + private final double scale; + /** Inverse cumulative probability accuracy. */ + private final double solverAbsoluteAccuracy; + /** Cached numerical mean */ + private double numericalMean = Double.NaN; + /** Whether or not the numerical mean has been calculated */ + private boolean numericalMeanIsCalculated = false; + /** Cached numerical variance */ + private double numericalVariance = Double.NaN; + /** Whether or not the numerical variance has been calculated */ + private boolean numericalVarianceIsCalculated = false; + + /** + * Create a Weibull distribution with the given shape and scale and a + * location equal to zero. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param alpha Shape parameter. + * @param beta Scale parameter. + * @throws NotStrictlyPositiveException if {@code alpha <= 0} or + * {@code beta <= 0}. + */ + public WeibullDistribution(double alpha, double beta) + throws NotStrictlyPositiveException { + this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Create a Weibull distribution with the given shape, scale and inverse + * cumulative probability accuracy and a location equal to zero. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param alpha Shape parameter. + * @param beta Scale parameter. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code alpha <= 0} or + * {@code beta <= 0}. + * @since 2.1 + */ + public WeibullDistribution(double alpha, double beta, + double inverseCumAccuracy) { + this(new Well19937c(), alpha, beta, inverseCumAccuracy); + } + + /** + * Creates a Weibull distribution. + * + * @param rng Random number generator. + * @param alpha Shape parameter. + * @param beta Scale parameter. + * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}. + * @since 3.3 + */ + public WeibullDistribution(RandomGenerator rng, double alpha, double beta) + throws NotStrictlyPositiveException { + this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + + /** + * Creates a Weibull distribution. + * + * @param rng Random number generator. + * @param alpha Shape parameter. + * @param beta Scale parameter. + * @param inverseCumAccuracy Maximum absolute error in inverse + * cumulative probability estimates + * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}). + * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}. + * @since 3.1 + */ + public WeibullDistribution(RandomGenerator rng, + double alpha, + double beta, + double inverseCumAccuracy) + throws NotStrictlyPositiveException { + super(rng); + + if (alpha <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, + alpha); + } + if (beta <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, + beta); + } + scale = beta; + shape = alpha; + solverAbsoluteAccuracy = inverseCumAccuracy; + } + + /** + * Access the shape parameter, {@code alpha}. + * + * @return the shape parameter, {@code alpha}. + */ + public double getShape() { + return shape; + } + + /** + * Access the scale parameter, {@code beta}. + * + * @return the scale parameter, {@code beta}. + */ + public double getScale() { + return scale; + } + + /** {@inheritDoc} */ + public double density(double x) { + if (x < 0) { + return 0; + } + + final double xscale = x / scale; + final double xscalepow = FastMath.pow(xscale, shape - 1); + + /* + * FastMath.pow(x / scale, shape) = + * FastMath.pow(xscale, shape) = + * FastMath.pow(xscale, shape - 1) * xscale + */ + final double xscalepowshape = xscalepow * xscale; + + return (shape / scale) * xscalepow * FastMath.exp(-xscalepowshape); + } + + /** {@inheritDoc} */ + @Override + public double logDensity(double x) { + if (x < 0) { + return Double.NEGATIVE_INFINITY; + } + + final double xscale = x / scale; + final double logxscalepow = FastMath.log(xscale) * (shape - 1); + + /* + * FastMath.pow(x / scale, shape) = + * FastMath.pow(xscale, shape) = + * FastMath.pow(xscale, shape - 1) * xscale + */ + final double xscalepowshape = FastMath.exp(logxscalepow) * xscale; + + return FastMath.log(shape / scale) + logxscalepow - xscalepowshape; + } + + /** {@inheritDoc} */ + public double cumulativeProbability(double x) { + double ret; + if (x <= 0.0) { + ret = 0.0; + } else { + ret = 1.0 - FastMath.exp(-FastMath.pow(x / scale, shape)); + } + return ret; + } + + /** + * {@inheritDoc} + * + * Returns {@code 0} when {@code p == 0} and + * {@code Double.POSITIVE_INFINITY} when {@code p == 1}. + */ + @Override + public double inverseCumulativeProbability(double p) { + double ret; + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0.0, 1.0); + } else if (p == 0) { + ret = 0.0; + } else if (p == 1) { + ret = Double.POSITIVE_INFINITY; + } else { + ret = scale * FastMath.pow(-FastMath.log1p(-p), 1.0 / shape); + } + return ret; + } + + /** + * Return the absolute accuracy setting of the solver used to estimate + * inverse cumulative probabilities. + * + * @return the solver absolute accuracy. + * @since 2.1 + */ + @Override + protected double getSolverAbsoluteAccuracy() { + return solverAbsoluteAccuracy; + } + + /** + * {@inheritDoc} + * + * The mean is {@code scale * Gamma(1 + (1 / shape))}, where {@code Gamma()} + * is the Gamma-function. + */ + public double getNumericalMean() { + if (!numericalMeanIsCalculated) { + numericalMean = calculateNumericalMean(); + numericalMeanIsCalculated = true; + } + return numericalMean; + } + + /** + * used by {@link #getNumericalMean()} + * + * @return the mean of this distribution + */ + protected double calculateNumericalMean() { + final double sh = getShape(); + final double sc = getScale(); + + return sc * FastMath.exp(Gamma.logGamma(1 + (1 / sh))); + } + + /** + * {@inheritDoc} + * + * The variance is {@code scale^2 * Gamma(1 + (2 / shape)) - mean^2} + * where {@code Gamma()} is the Gamma-function. + */ + public double getNumericalVariance() { + if (!numericalVarianceIsCalculated) { + numericalVariance = calculateNumericalVariance(); + numericalVarianceIsCalculated = true; + } + return numericalVariance; + } + + /** + * used by {@link #getNumericalVariance()} + * + * @return the variance of this distribution + */ + protected double calculateNumericalVariance() { + final double sh = getShape(); + final double sc = getScale(); + final double mn = getNumericalMean(); + + return (sc * sc) * FastMath.exp(Gamma.logGamma(1 + (2 / sh))) - + (mn * mn); + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 0 no matter the parameters. + * + * @return lower bound of the support (always 0) + */ + public double getSupportLowerBound() { + return 0; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is always positive infinity + * no matter the parameters. + * + * @return upper bound of the support (always + * {@code Double.POSITIVE_INFINITY}) + */ + public double getSupportUpperBound() { + return Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** {@inheritDoc} */ + public boolean isSupportUpperBoundInclusive() { + return false; + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ZipfDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ZipfDistribution.java new file mode 100644 index 000000000..0cc12ffd8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/ZipfDistribution.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 com.fr.third.org.apache.commons.math3.distribution; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Zipf distribution. + *

+ * Parameters: + * For a random variable {@code X} whose values are distributed according to this + * distribution, the probability mass function is given by + *

+ *   P(X = k) = H(N,s) * 1 / k^s    for {@code k = 1,2,...,N}.
+ * 
+ * {@code H(N,s)} is the normalizing constant + * which corresponds to the generalized harmonic number of order N of s. + *

+ *

    + *
  • {@code N} is the number of elements
  • + *
  • {@code s} is the exponent
  • + *
+ * @see Zipf's law (Wikipedia) + * @see Generalized harmonic numbers + */ +public class ZipfDistribution extends AbstractIntegerDistribution { + /** Serializable version identifier. */ + private static final long serialVersionUID = -140627372283420404L; + /** Number of elements. */ + private final int numberOfElements; + /** Exponent parameter of the distribution. */ + private final double exponent; + /** Cached numerical mean */ + private double numericalMean = Double.NaN; + /** Whether or not the numerical mean has been calculated */ + private boolean numericalMeanIsCalculated = false; + /** Cached numerical variance */ + private double numericalVariance = Double.NaN; + /** Whether or not the numerical variance has been calculated */ + private boolean numericalVarianceIsCalculated = false; + /** The sampler to be used for the sample() method */ + private transient ZipfRejectionInversionSampler sampler; + + /** + * Create a new Zipf distribution with the given number of elements and + * exponent. + *

+ * Note: this constructor will implicitly create an instance of + * {@link Well19937c} as random generator to be used for sampling only (see + * {@link #sample()} and {@link #sample(int)}). In case no sampling is + * needed for the created distribution, it is advised to pass {@code null} + * as random generator via the appropriate constructors to avoid the + * additional initialisation overhead. + * + * @param numberOfElements Number of elements. + * @param exponent Exponent. + * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} + * or {@code exponent <= 0}. + */ + public ZipfDistribution(final int numberOfElements, final double exponent) { + this(new Well19937c(), numberOfElements, exponent); + } + + /** + * Creates a Zipf distribution. + * + * @param rng Random number generator. + * @param numberOfElements Number of elements. + * @param exponent Exponent. + * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} + * or {@code exponent <= 0}. + * @since 3.1 + */ + public ZipfDistribution(RandomGenerator rng, + int numberOfElements, + double exponent) + throws NotStrictlyPositiveException { + super(rng); + + if (numberOfElements <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, + numberOfElements); + } + if (exponent <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.EXPONENT, + exponent); + } + + this.numberOfElements = numberOfElements; + this.exponent = exponent; + } + + /** + * Get the number of elements (e.g. corpus size) for the distribution. + * + * @return the number of elements + */ + public int getNumberOfElements() { + return numberOfElements; + } + + /** + * Get the exponent characterizing the distribution. + * + * @return the exponent + */ + public double getExponent() { + return exponent; + } + + /** {@inheritDoc} */ + public double probability(final int x) { + if (x <= 0 || x > numberOfElements) { + return 0.0; + } + + return (1.0 / FastMath.pow(x, exponent)) / generalizedHarmonic(numberOfElements, exponent); + } + + /** {@inheritDoc} */ + @Override + public double logProbability(int x) { + if (x <= 0 || x > numberOfElements) { + return Double.NEGATIVE_INFINITY; + } + + return -FastMath.log(x) * exponent - FastMath.log(generalizedHarmonic(numberOfElements, exponent)); + } + + /** {@inheritDoc} */ + public double cumulativeProbability(final int x) { + if (x <= 0) { + return 0.0; + } else if (x >= numberOfElements) { + return 1.0; + } + + return generalizedHarmonic(x, exponent) / generalizedHarmonic(numberOfElements, exponent); + } + + /** + * {@inheritDoc} + * + * For number of elements {@code N} and exponent {@code s}, the mean is + * {@code Hs1 / Hs}, where + *

    + *
  • {@code Hs1 = generalizedHarmonic(N, s - 1)},
  • + *
  • {@code Hs = generalizedHarmonic(N, s)}.
  • + *
+ */ + public double getNumericalMean() { + if (!numericalMeanIsCalculated) { + numericalMean = calculateNumericalMean(); + numericalMeanIsCalculated = true; + } + return numericalMean; + } + + /** + * Used by {@link #getNumericalMean()}. + * + * @return the mean of this distribution + */ + protected double calculateNumericalMean() { + final int N = getNumberOfElements(); + final double s = getExponent(); + + final double Hs1 = generalizedHarmonic(N, s - 1); + final double Hs = generalizedHarmonic(N, s); + + return Hs1 / Hs; + } + + /** + * {@inheritDoc} + * + * For number of elements {@code N} and exponent {@code s}, the mean is + * {@code (Hs2 / Hs) - (Hs1^2 / Hs^2)}, where + *
    + *
  • {@code Hs2 = generalizedHarmonic(N, s - 2)},
  • + *
  • {@code Hs1 = generalizedHarmonic(N, s - 1)},
  • + *
  • {@code Hs = generalizedHarmonic(N, s)}.
  • + *
+ */ + public double getNumericalVariance() { + if (!numericalVarianceIsCalculated) { + numericalVariance = calculateNumericalVariance(); + numericalVarianceIsCalculated = true; + } + return numericalVariance; + } + + /** + * Used by {@link #getNumericalVariance()}. + * + * @return the variance of this distribution + */ + protected double calculateNumericalVariance() { + final int N = getNumberOfElements(); + final double s = getExponent(); + + final double Hs2 = generalizedHarmonic(N, s - 2); + final double Hs1 = generalizedHarmonic(N, s - 1); + final double Hs = generalizedHarmonic(N, s); + + return (Hs2 / Hs) - ((Hs1 * Hs1) / (Hs * Hs)); + } + + /** + * Calculates the Nth generalized harmonic number. See + * Harmonic + * Series. + * + * @param n Term in the series to calculate (must be larger than 1) + * @param m Exponent (special case {@code m = 1} is the harmonic series). + * @return the 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; + } + + /** + * {@inheritDoc} + * + * The lower bound of the support is always 1 no matter the parameters. + * + * @return lower bound of the support (always 1) + */ + public int getSupportLowerBound() { + return 1; + } + + /** + * {@inheritDoc} + * + * The upper bound of the support is the number of elements. + * + * @return upper bound of the support + */ + public int getSupportUpperBound() { + return getNumberOfElements(); + } + + /** + * {@inheritDoc} + * + * The support of this distribution is connected. + * + * @return {@code true} + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int sample() { + if (sampler == null) { + sampler = new ZipfRejectionInversionSampler(numberOfElements, exponent); + } + return sampler.sample(random); + } + + /** + * Utility class implementing a rejection inversion sampling method for a discrete, + * bounded Zipf distribution that is based on the method described in + *

+ * Wolfgang Hörmann and Gerhard Derflinger + * "Rejection-inversion to generate variates from monotone discrete distributions." + * ACM Transactions on Modeling and Computer Simulation (TOMACS) 6.3 (1996): 169-184. + *

+ * The paper describes an algorithm for exponents larger than 1 (Algorithm ZRI). + * The original method uses {@code H(x) := (v + x)^(1 - q) / (1 - q)} + * as the integral of the hat function. This function is undefined for + * q = 1, which is the reason for the limitation of the exponent. + * If instead the integral function + * {@code H(x) := ((v + x)^(1 - q) - 1) / (1 - q)} is used, + * for which a meaningful limit exists for q = 1, + * the method works for all positive exponents. + *

+ * The following implementation uses v := 0 and generates integral numbers + * in the range [1, numberOfElements]. This is different to the original method + * where v is defined to be positive and numbers are taken from [0, i_max]. + * This explains why the implementation looks slightly different. + * + * @since 3.6 + */ + static final class ZipfRejectionInversionSampler { + + /** Exponent parameter of the distribution. */ + private final double exponent; + /** Number of elements. */ + private final int numberOfElements; + /** Constant equal to {@code hIntegral(1.5) - 1}. */ + private final double hIntegralX1; + /** Constant equal to {@code hIntegral(numberOfElements + 0.5)}. */ + private final double hIntegralNumberOfElements; + /** Constant equal to {@code 2 - hIntegralInverse(hIntegral(2.5) - h(2)}. */ + private final double s; + + /** Simple constructor. + * @param numberOfElements number of elements + * @param exponent exponent parameter of the distribution + */ + ZipfRejectionInversionSampler(final int numberOfElements, final double exponent) { + this.exponent = exponent; + this.numberOfElements = numberOfElements; + this.hIntegralX1 = hIntegral(1.5) - 1d; + this.hIntegralNumberOfElements = hIntegral(numberOfElements + 0.5); + this.s = 2d - hIntegralInverse(hIntegral(2.5) - h(2)); + } + + /** Generate one integral number in the range [1, numberOfElements]. + * @param random random generator to use + * @return generated integral number in the range [1, numberOfElements] + */ + int sample(final RandomGenerator random) { + while(true) { + + final double u = hIntegralNumberOfElements + random.nextDouble() * (hIntegralX1 - hIntegralNumberOfElements); + // u is uniformly distributed in (hIntegralX1, hIntegralNumberOfElements] + + double x = hIntegralInverse(u); + + int k = (int)(x + 0.5); + + // Limit k to the range [1, numberOfElements] + // (k could be outside due to numerical inaccuracies) + if (k < 1) { + k = 1; + } + else if (k > numberOfElements) { + k = numberOfElements; + } + + // Here, the distribution of k is given by: + // + // P(k = 1) = C * (hIntegral(1.5) - hIntegralX1) = C + // P(k = m) = C * (hIntegral(m + 1/2) - hIntegral(m - 1/2)) for m >= 2 + // + // where C := 1 / (hIntegralNumberOfElements - hIntegralX1) + + if (k - x <= s || u >= hIntegral(k + 0.5) - h(k)) { + + // Case k = 1: + // + // The right inequality is always true, because replacing k by 1 gives + // u >= hIntegral(1.5) - h(1) = hIntegralX1 and u is taken from + // (hIntegralX1, hIntegralNumberOfElements]. + // + // Therefore, the acceptance rate for k = 1 is P(accepted | k = 1) = 1 + // and the probability that 1 is returned as random value is + // P(k = 1 and accepted) = P(accepted | k = 1) * P(k = 1) = C = C / 1^exponent + // + // Case k >= 2: + // + // The left inequality (k - x <= s) is just a short cut + // to avoid the more expensive evaluation of the right inequality + // (u >= hIntegral(k + 0.5) - h(k)) in many cases. + // + // If the left inequality is true, the right inequality is also true: + // Theorem 2 in the paper is valid for all positive exponents, because + // the requirements h'(x) = -exponent/x^(exponent + 1) < 0 and + // (-1/hInverse'(x))'' = (1+1/exponent) * x^(1/exponent-1) >= 0 + // are both fulfilled. + // Therefore, f(x) := x - hIntegralInverse(hIntegral(x + 0.5) - h(x)) + // is a non-decreasing function. If k - x <= s holds, + // k - x <= s + f(k) - f(2) is obviously also true which is equivalent to + // -x <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), + // -hIntegralInverse(u) <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), + // and finally u >= hIntegral(k + 0.5) - h(k). + // + // Hence, the right inequality determines the acceptance rate: + // P(accepted | k = m) = h(m) / (hIntegrated(m+1/2) - hIntegrated(m-1/2)) + // The probability that m is returned is given by + // P(k = m and accepted) = P(accepted | k = m) * P(k = m) = C * h(m) = C / m^exponent. + // + // In both cases the probabilities are proportional to the probability mass function + // of the Zipf distribution. + + return k; + } + } + } + + /** + * {@code H(x) :=} + *

    + *
  • {@code (x^(1-exponent) - 1)/(1 - exponent)}, if {@code exponent != 1}
  • + *
  • {@code log(x)}, if {@code exponent == 1}
  • + *
+ * H(x) is an integral function of h(x), + * the derivative of H(x) is h(x). + * + * @param x free parameter + * @return {@code H(x)} + */ + private double hIntegral(final double x) { + final double logX = FastMath.log(x); + return helper2((1d-exponent)*logX)*logX; + } + + /** + * {@code h(x) := 1/x^exponent} + * + * @param x free parameter + * @return h(x) + */ + private double h(final double x) { + return FastMath.exp(-exponent * FastMath.log(x)); + } + + /** + * The inverse function of H(x). + * + * @param x free parameter + * @return y for which {@code H(y) = x} + */ + private double hIntegralInverse(final double x) { + double t = x*(1d-exponent); + if (t < -1d) { + // Limit value to the range [-1, +inf). + // t could be smaller than -1 in some rare cases due to numerical errors. + t = -1; + } + return FastMath.exp(helper1(t)*x); + } + + /** + * Helper function that calculates {@code log(1+x)/x}. + *

+ * A Taylor series expansion is used, if x is close to 0. + * + * @param x a value larger than or equal to -1 + * @return {@code log(1+x)/x} + */ + static double helper1(final double x) { + if (FastMath.abs(x)>1e-8) { + return FastMath.log1p(x)/x; + } + else { + return 1.-x*((1./2.)-x*((1./3.)-x*(1./4.))); + } + } + + /** + * Helper function to calculate {@code (exp(x)-1)/x}. + *

+ * A Taylor series expansion is used, if x is close to 0. + * + * @param x free parameter + * @return {@code (exp(x)-1)/x} if x is non-zero, or 1 if x=0 + */ + static double helper2(final double x) { + if (FastMath.abs(x)>1e-8) { + return FastMath.expm1(x)/x; + } + else { + return 1.+x*(1./2.)*(1.+x*(1./3.)*(1.+x*(1./4.))); + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java new file mode 100644 index 000000000..40fcefc70 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.distribution.fitting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.distribution.MixtureMultivariateNormalDistribution; +import com.fr.third.org.apache.commons.math3.distribution.MultivariateNormalDistribution; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.stat.correlation.Covariance; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Expectation-Maximization algorithm for fitting the parameters of + * multivariate normal mixture model distributions. + * + * This implementation is pure original code based on + * EM Demystified: An Expectation-Maximization Tutorial by Yihua Chen and Maya R. Gupta, + * Department of Electrical Engineering, University of Washington, Seattle, WA 98195. + * It was verified using external tools like CRAN Mixtools + * (see the JUnit test cases) but it is not based on Mixtools code at all. + * The discussion of the origin of this class can be seen in the comments of the MATH-817 JIRA issue. + * @since 3.2 + */ +public class MultivariateNormalMixtureExpectationMaximization { + /** + * Default maximum number of iterations allowed per fitting process. + */ + private static final int DEFAULT_MAX_ITERATIONS = 1000; + /** + * Default convergence threshold for fitting. + */ + private static final double DEFAULT_THRESHOLD = 1E-5; + /** + * The data to fit. + */ + private final double[][] data; + /** + * The model fit against the data. + */ + private MixtureMultivariateNormalDistribution fittedModel; + /** + * The log likelihood of the data given the fitted model. + */ + private double logLikelihood = 0d; + + /** + * Creates an object to fit a multivariate normal mixture model to data. + * + * @param data Data to use in fitting procedure + * @throws NotStrictlyPositiveException if data has no rows + * @throws DimensionMismatchException if rows of data have different numbers + * of columns + * @throws NumberIsTooSmallException if the number of columns in the data is + * less than 2 + */ + public MultivariateNormalMixtureExpectationMaximization(double[][] data) + throws NotStrictlyPositiveException, + DimensionMismatchException, + NumberIsTooSmallException { + if (data.length < 1) { + throw new NotStrictlyPositiveException(data.length); + } + + this.data = new double[data.length][data[0].length]; + + for (int i = 0; i < data.length; i++) { + if (data[i].length != data[0].length) { + // Jagged arrays not allowed + throw new DimensionMismatchException(data[i].length, + data[0].length); + } + if (data[i].length < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_TOO_SMALL, + data[i].length, 2, true); + } + this.data[i] = MathArrays.copyOf(data[i], data[i].length); + } + } + + /** + * Fit a mixture model to the data supplied to the constructor. + * + * The quality of the fit depends on the concavity of the data provided to + * the constructor and the initial mixture provided to this function. If the + * data has many local optima, multiple runs of the fitting function with + * different initial mixtures may be required to find the optimal solution. + * If a SingularMatrixException is encountered, it is possible that another + * initialization would work. + * + * @param initialMixture Model containing initial values of weights and + * multivariate normals + * @param maxIterations Maximum iterations allowed for fit + * @param threshold Convergence threshold computed as difference in + * logLikelihoods between successive iterations + * @throws SingularMatrixException if any component's covariance matrix is + * singular during fitting + * @throws NotStrictlyPositiveException if numComponents is less than one + * or threshold is less than Double.MIN_VALUE + * @throws DimensionMismatchException if initialMixture mean vector and data + * number of columns are not equal + */ + public void fit(final MixtureMultivariateNormalDistribution initialMixture, + final int maxIterations, + final double threshold) + throws SingularMatrixException, + NotStrictlyPositiveException, + DimensionMismatchException { + if (maxIterations < 1) { + throw new NotStrictlyPositiveException(maxIterations); + } + + if (threshold < Double.MIN_VALUE) { + throw new NotStrictlyPositiveException(threshold); + } + + final int n = data.length; + + // Number of data columns. Jagged data already rejected in constructor, + // so we can assume the lengths of each row are equal. + final int numCols = data[0].length; + final int k = initialMixture.getComponents().size(); + + final int numMeanColumns + = initialMixture.getComponents().get(0).getSecond().getMeans().length; + + if (numMeanColumns != numCols) { + throw new DimensionMismatchException(numMeanColumns, numCols); + } + + int numIterations = 0; + double previousLogLikelihood = 0d; + + logLikelihood = Double.NEGATIVE_INFINITY; + + // Initialize model to fit to initial mixture. + fittedModel = new MixtureMultivariateNormalDistribution(initialMixture.getComponents()); + + while (numIterations++ <= maxIterations && + FastMath.abs(previousLogLikelihood - logLikelihood) > threshold) { + previousLogLikelihood = logLikelihood; + double sumLogLikelihood = 0d; + + // Mixture components + final List> components + = fittedModel.getComponents(); + + // Weight and distribution of each component + final double[] weights = new double[k]; + + final MultivariateNormalDistribution[] mvns = new MultivariateNormalDistribution[k]; + + for (int j = 0; j < k; j++) { + weights[j] = components.get(j).getFirst(); + mvns[j] = components.get(j).getSecond(); + } + + // E-step: compute the data dependent parameters of the expectation + // function. + // The percentage of row's total density between a row and a + // component + final double[][] gamma = new double[n][k]; + + // Sum of gamma for each component + final double[] gammaSums = new double[k]; + + // Sum of gamma times its row for each each component + final double[][] gammaDataProdSums = new double[k][numCols]; + + for (int i = 0; i < n; i++) { + final double rowDensity = fittedModel.density(data[i]); + sumLogLikelihood += FastMath.log(rowDensity); + + for (int j = 0; j < k; j++) { + gamma[i][j] = weights[j] * mvns[j].density(data[i]) / rowDensity; + gammaSums[j] += gamma[i][j]; + + for (int col = 0; col < numCols; col++) { + gammaDataProdSums[j][col] += gamma[i][j] * data[i][col]; + } + } + } + + logLikelihood = sumLogLikelihood / n; + + // M-step: compute the new parameters based on the expectation + // function. + final double[] newWeights = new double[k]; + final double[][] newMeans = new double[k][numCols]; + + for (int j = 0; j < k; j++) { + newWeights[j] = gammaSums[j] / n; + for (int col = 0; col < numCols; col++) { + newMeans[j][col] = gammaDataProdSums[j][col] / gammaSums[j]; + } + } + + // Compute new covariance matrices + final RealMatrix[] newCovMats = new RealMatrix[k]; + for (int j = 0; j < k; j++) { + newCovMats[j] = new Array2DRowRealMatrix(numCols, numCols); + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < k; j++) { + final RealMatrix vec + = new Array2DRowRealMatrix(MathArrays.ebeSubtract(data[i], newMeans[j])); + final RealMatrix dataCov + = vec.multiply(vec.transpose()).scalarMultiply(gamma[i][j]); + newCovMats[j] = newCovMats[j].add(dataCov); + } + } + + // Converting to arrays for use by fitted model + final double[][][] newCovMatArrays = new double[k][numCols][numCols]; + for (int j = 0; j < k; j++) { + newCovMats[j] = newCovMats[j].scalarMultiply(1d / gammaSums[j]); + newCovMatArrays[j] = newCovMats[j].getData(); + } + + // Update current model + fittedModel = new MixtureMultivariateNormalDistribution(newWeights, + newMeans, + newCovMatArrays); + } + + if (FastMath.abs(previousLogLikelihood - logLikelihood) > threshold) { + // Did not converge before the maximum number of iterations + throw new ConvergenceException(); + } + } + + /** + * Fit a mixture model to the data supplied to the constructor. + * + * The quality of the fit depends on the concavity of the data provided to + * the constructor and the initial mixture provided to this function. If the + * data has many local optima, multiple runs of the fitting function with + * different initial mixtures may be required to find the optimal solution. + * If a SingularMatrixException is encountered, it is possible that another + * initialization would work. + * + * @param initialMixture Model containing initial values of weights and + * multivariate normals + * @throws SingularMatrixException if any component's covariance matrix is + * singular during fitting + * @throws NotStrictlyPositiveException if numComponents is less than one or + * threshold is less than Double.MIN_VALUE + */ + public void fit(MixtureMultivariateNormalDistribution initialMixture) + throws SingularMatrixException, + NotStrictlyPositiveException { + fit(initialMixture, DEFAULT_MAX_ITERATIONS, DEFAULT_THRESHOLD); + } + + /** + * Helper method to create a multivariate normal mixture model which can be + * used to initialize {@link #fit(MixtureMultivariateNormalDistribution)}. + * + * This method uses the data supplied to the constructor to try to determine + * a good mixture model at which to start the fit, but it is not guaranteed + * to supply a model which will find the optimal solution or even converge. + * + * @param data Data to estimate distribution + * @param numComponents Number of components for estimated mixture + * @return Multivariate normal mixture model estimated from the data + * @throws NumberIsTooLargeException if {@code numComponents} is greater + * than the number of data rows. + * @throws NumberIsTooSmallException if {@code numComponents < 2}. + * @throws NotStrictlyPositiveException if data has less than 2 rows + * @throws DimensionMismatchException if rows of data have different numbers + * of columns + */ + public static MixtureMultivariateNormalDistribution estimate(final double[][] data, + final int numComponents) + throws NotStrictlyPositiveException, + DimensionMismatchException { + if (data.length < 2) { + throw new NotStrictlyPositiveException(data.length); + } + if (numComponents < 2) { + throw new NumberIsTooSmallException(numComponents, 2, true); + } + if (numComponents > data.length) { + throw new NumberIsTooLargeException(numComponents, data.length, true); + } + + final int numRows = data.length; + final int numCols = data[0].length; + + // sort the data + final DataRow[] sortedData = new DataRow[numRows]; + for (int i = 0; i < numRows; i++) { + sortedData[i] = new DataRow(data[i]); + } + Arrays.sort(sortedData); + + // uniform weight for each bin + final double weight = 1d / numComponents; + + // components of mixture model to be created + final List> components = + new ArrayList>(numComponents); + + // create a component based on data in each bin + for (int binIndex = 0; binIndex < numComponents; binIndex++) { + // minimum index (inclusive) from sorted data for this bin + final int minIndex = (binIndex * numRows) / numComponents; + + // maximum index (exclusive) from sorted data for this bin + final int maxIndex = ((binIndex + 1) * numRows) / numComponents; + + // number of data records that will be in this bin + final int numBinRows = maxIndex - minIndex; + + // data for this bin + final double[][] binData = new double[numBinRows][numCols]; + + // mean of each column for the data in the this bin + final double[] columnMeans = new double[numCols]; + + // populate bin and create component + for (int i = minIndex, iBin = 0; i < maxIndex; i++, iBin++) { + for (int j = 0; j < numCols; j++) { + final double val = sortedData[i].getRow()[j]; + columnMeans[j] += val; + binData[iBin][j] = val; + } + } + + MathArrays.scaleInPlace(1d / numBinRows, columnMeans); + + // covariance matrix for this bin + final double[][] covMat + = new Covariance(binData).getCovarianceMatrix().getData(); + final MultivariateNormalDistribution mvn + = new MultivariateNormalDistribution(columnMeans, covMat); + + components.add(new Pair(weight, mvn)); + } + + return new MixtureMultivariateNormalDistribution(components); + } + + /** + * Gets the log likelihood of the data under the fitted model. + * + * @return Log likelihood of data or zero of no data has been fit + */ + public double getLogLikelihood() { + return logLikelihood; + } + + /** + * Gets the fitted model. + * + * @return fitted model or {@code null} if no fit has been performed yet. + */ + public MixtureMultivariateNormalDistribution getFittedModel() { + return new MixtureMultivariateNormalDistribution(fittedModel.getComponents()); + } + + /** + * Class used for sorting user-supplied data. + */ + private static class DataRow implements Comparable { + /** One data row. */ + private final double[] row; + /** Mean of the data row. */ + private Double mean; + + /** + * Create a data row. + * @param data Data to use for the row + */ + DataRow(final double[] data) { + // Store reference. + row = data; + // Compute mean. + mean = 0d; + for (int i = 0; i < data.length; i++) { + mean += data[i]; + } + mean /= data.length; + } + + /** + * Compare two data rows. + * @param other The other row + * @return int for sorting + */ + public int compareTo(final DataRow other) { + return mean.compareTo(other.mean); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof DataRow) { + return MathArrays.equals(row, ((DataRow) other).row); + } + + return false; + + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(row); + } + /** + * Get a data row. + * @return data row array + */ + public double[] getRow() { + return row; + } + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/package-info.java new file mode 100644 index 000000000..0a6002a17 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/fitting/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Fitting of parameters against distributions. + */ +package com.fr.third.org.apache.commons.math3.distribution.fitting; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/package-info.java new file mode 100644 index 000000000..a38604084 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/distribution/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Implementations of common discrete and continuous distributions. + */ +package com.fr.third.org.apache.commons.math3.distribution; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/ConvergenceException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/ConvergenceException.java new file mode 100644 index 000000000..48e17cf74 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/ConvergenceException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Error thrown when a numerical computation can not be performed because the + * numerical result failed to converge to a finite value. + * + * @since 2.2 + */ +public class ConvergenceException extends MathIllegalStateException { + /** Serializable version Id. */ + private static final long serialVersionUID = 4330003017885151975L; + + /** + * Construct the exception. + */ + public ConvergenceException() { + this(LocalizedFormats.CONVERGENCE_FAILED); + } + + /** + * Construct the exception with a specific context and arguments. + * + * @param pattern Message pattern providing the specific context of + * the error. + * @param args Arguments. + */ + public ConvergenceException(Localizable pattern, + Object ... args) { + getContext().addMessage(pattern, args); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/DimensionMismatchException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/DimensionMismatchException.java new file mode 100644 index 000000000..0e3089c85 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/DimensionMismatchException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when two dimensions differ. + * + * @since 2.2 + */ +public class DimensionMismatchException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = -8415396756375798143L; + /** Correct dimension. */ + private final int dimension; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param specific Specific context information pattern. + * @param wrong Wrong dimension. + * @param expected Expected dimension. + */ + public DimensionMismatchException(Localizable specific, + int wrong, + int expected) { + super(specific, Integer.valueOf(wrong), Integer.valueOf(expected)); + dimension = expected; + } + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrong Wrong dimension. + * @param expected Expected dimension. + */ + public DimensionMismatchException(int wrong, + int expected) { + this(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, wrong, expected); + } + + /** + * @return the expected dimension. + */ + public int getDimension() { + return dimension; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/InsufficientDataException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/InsufficientDataException.java new file mode 100644 index 000000000..7411ffb21 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/InsufficientDataException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when there is insufficient data to perform a computation. + * + * @since 3.3 + */ +public class InsufficientDataException + extends MathIllegalArgumentException { + + /** Serializable version Id. */ + private static final long serialVersionUID = -2629324471511903359L; + + /** + * Construct the exception. + */ + public InsufficientDataException() { + this(LocalizedFormats.INSUFFICIENT_DATA); + } + + /** + * Construct the exception with a specific context. + * + * @param pattern Message pattern providing the specific context of the error. + * @param arguments Values for replacing the placeholders in {@code pattern}. + */ + public InsufficientDataException(Localizable pattern, Object... arguments) { + super(pattern, arguments); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathArithmeticException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathArithmeticException.java new file mode 100644 index 000000000..d0f366ddf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathArithmeticException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Base class for arithmetic exceptions. + * It is used for all the exceptions that have the semantics of the standard + * {@link ArithmeticException}, but must also provide a localized + * message. + * + * @since 3.0 + */ +public class MathArithmeticException extends ArithmeticException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + /** Context. */ + private final ExceptionContext context; + + /** + * Default constructor. + */ + public MathArithmeticException() { + context = new ExceptionContext(this); + context.addMessage(LocalizedFormats.ARITHMETIC_EXCEPTION); + } + + /** + * Constructor with a specific message. + * + * @param pattern Message pattern providing the specific context of + * the error. + * @param args Arguments. + */ + public MathArithmeticException(Localizable pattern, + Object ... args) { + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** {@inheritDoc} */ + public ExceptionContext getContext() { + return context; + } + + /** {@inheritDoc} */ + @Override + public String getMessage() { + return context.getMessage(); + } + + /** {@inheritDoc} */ + @Override + public String getLocalizedMessage() { + return context.getLocalizedMessage(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalArgumentException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalArgumentException.java new file mode 100644 index 000000000..a2cd3faf4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalArgumentException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Base class for all preconditions violation exceptions. + * In most cases, this class should not be instantiated directly: it should + * serve as a base class to create all the exceptions that have the semantics + * of the standard {@link IllegalArgumentException}. + * + * @since 2.2 + */ +public class MathIllegalArgumentException extends IllegalArgumentException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + /** Context. */ + private final ExceptionContext context; + + /** + * @param pattern Message pattern explaining the cause of the error. + * @param args Arguments. + */ + public MathIllegalArgumentException(Localizable pattern, + Object ... args) { + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** {@inheritDoc} */ + public ExceptionContext getContext() { + return context; + } + + /** {@inheritDoc} */ + @Override + public String getMessage() { + return context.getMessage(); + } + + /** {@inheritDoc} */ + @Override + public String getLocalizedMessage() { + return context.getLocalizedMessage(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalNumberException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalNumberException.java new file mode 100644 index 000000000..1ec0d9b49 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalNumberException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Base class for exceptions raised by a wrong number. + * This class is not intended to be instantiated directly: it should serve + * as a base class to create all the exceptions that are raised because some + * precondition is violated by a number argument. + * + * @since 2.2 + */ +public class MathIllegalNumberException extends MathIllegalArgumentException { + + /** Helper to avoid boxing warnings. @since 3.3 */ + protected static final Integer INTEGER_ZERO = Integer.valueOf(0); + + /** Serializable version Id. */ + private static final long serialVersionUID = -7447085893598031110L; + + /** Requested. */ + private final Number argument; + + /** + * Construct an exception. + * + * @param pattern Localizable pattern. + * @param wrong Wrong number. + * @param arguments Arguments. + */ + protected MathIllegalNumberException(Localizable pattern, + Number wrong, + Object ... arguments) { + super(pattern, wrong, arguments); + argument = wrong; + } + + /** + * @return the requested value. + */ + public Number getArgument() { + return argument; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalStateException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalStateException.java new file mode 100644 index 000000000..10b35b16e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathIllegalStateException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Base class for all exceptions that signal that the process + * throwing the exception is in a state that does not comply with + * the set of states that it is designed to be in. + * + * @since 2.2 + */ +public class MathIllegalStateException extends IllegalStateException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + /** Context. */ + private final ExceptionContext context; + + /** + * Simple constructor. + * + * @param pattern Message pattern explaining the cause of the error. + * @param args Arguments. + */ + public MathIllegalStateException(Localizable pattern, + Object ... args) { + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** + * Simple constructor. + * + * @param cause Root cause. + * @param pattern Message pattern explaining the cause of the error. + * @param args Arguments. + */ + public MathIllegalStateException(Throwable cause, + Localizable pattern, + Object ... args) { + super(cause); + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** + * Default constructor. + */ + public MathIllegalStateException() { + this(LocalizedFormats.ILLEGAL_STATE); + } + + /** {@inheritDoc} */ + public ExceptionContext getContext() { + return context; + } + + /** {@inheritDoc} */ + @Override + public String getMessage() { + return context.getMessage(); + } + + /** {@inheritDoc} */ + @Override + public String getLocalizedMessage() { + return context.getLocalizedMessage(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathInternalError.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathInternalError.java new file mode 100644 index 000000000..12defa0d8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathInternalError.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception triggered when something that shouldn't happen does happen. + * + * @since 2.2 + */ +public class MathInternalError extends MathIllegalStateException { + /** Serializable version Id. */ + private static final long serialVersionUID = -6276776513966934846L; + /** URL for reporting problems. */ + private static final String REPORT_URL = "https://issues.apache.org/jira/browse/MATH"; + + /** + * Simple constructor. + */ + public MathInternalError() { + getContext().addMessage(LocalizedFormats.INTERNAL_ERROR, REPORT_URL); + } + + /** + * Simple constructor. + * @param cause root cause + */ + public MathInternalError(final Throwable cause) { + super(cause, LocalizedFormats.INTERNAL_ERROR, REPORT_URL); + } + + /** + * Constructor accepting a localized message. + * + * @param pattern Message pattern explaining the cause of the error. + * @param args Arguments. + */ + public MathInternalError(Localizable pattern, Object ... args) { + super(pattern, args); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathParseException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathParseException.java new file mode 100644 index 000000000..259f2a72f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathParseException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Class to signal parse failures. + * + * @since 2.2 + */ +public class MathParseException extends MathIllegalStateException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + + /** + * @param wrong Bad string representation of the object. + * @param position Index, in the {@code wrong} string, that caused the + * parsing to fail. + * @param type Class of the object supposedly represented by the + * {@code wrong} string. + */ + public MathParseException(String wrong, + int position, + Class type) { + getContext().addMessage(LocalizedFormats.CANNOT_PARSE_AS_TYPE, + wrong, Integer.valueOf(position), type.getName()); + } + + /** + * @param wrong Bad string representation of the object. + * @param position Index, in the {@code wrong} string, that caused the + * parsing to fail. + */ + public MathParseException(String wrong, + int position) { + getContext().addMessage(LocalizedFormats.CANNOT_PARSE, + wrong, Integer.valueOf(position)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathRuntimeException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathRuntimeException.java new file mode 100644 index 000000000..381025709 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathRuntimeException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * As of release 4.0, all exceptions thrown by the Commons Math code (except + * {@link NullArgumentException}) inherit from this class. + * In most cases, this class should not be instantiated directly: it should + * serve as a base class for implementing exception classes that describe a + * specific "problem". + * + * @since 3.1 + */ +public class MathRuntimeException extends RuntimeException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = 20120926L; + /** Context. */ + private final ExceptionContext context; + + /** + * @param pattern Message pattern explaining the cause of the error. + * @param args Arguments. + */ + public MathRuntimeException(Localizable pattern, + Object ... args) { + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** {@inheritDoc} */ + public ExceptionContext getContext() { + return context; + } + + /** {@inheritDoc} */ + @Override + public String getMessage() { + return context.getMessage(); + } + + /** {@inheritDoc} */ + @Override + public String getLocalizedMessage() { + return context.getLocalizedMessage(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathUnsupportedOperationException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathUnsupportedOperationException.java new file mode 100644 index 000000000..9adac393f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MathUnsupportedOperationException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContextProvider; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Base class for all unsupported features. + * It is used for all the exceptions that have the semantics of the standard + * {@link UnsupportedOperationException}, but must also provide a localized + * message. + * + * @since 2.2 + */ +public class MathUnsupportedOperationException extends UnsupportedOperationException + implements ExceptionContextProvider { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + /** Context. */ + private final ExceptionContext context; + + /** + * Default constructor. + */ + public MathUnsupportedOperationException() { + this(LocalizedFormats.UNSUPPORTED_OPERATION); + } + /** + * @param pattern Message pattern providing the specific context of + * the error. + * @param args Arguments. + */ + public MathUnsupportedOperationException(Localizable pattern, + Object ... args) { + context = new ExceptionContext(this); + context.addMessage(pattern, args); + } + + /** {@inheritDoc} */ + public ExceptionContext getContext() { + return context; + } + + /** {@inheritDoc} */ + @Override + public String getMessage() { + return context.getMessage(); + } + + /** {@inheritDoc} */ + @Override + public String getLocalizedMessage() { + return context.getLocalizedMessage(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MaxCountExceededException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MaxCountExceededException.java new file mode 100644 index 000000000..97f97a0e5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MaxCountExceededException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when some counter maximum value is exceeded. + * + * @since 3.0 + */ +public class MaxCountExceededException extends MathIllegalStateException { + /** Serializable version Id. */ + private static final long serialVersionUID = 4330003017885151975L; + /** + * Maximum number of evaluations. + */ + private final Number max; + + /** + * Construct the exception. + * + * @param max Maximum. + */ + public MaxCountExceededException(Number max) { + this(LocalizedFormats.MAX_COUNT_EXCEEDED, max); + } + /** + * Construct the exception with a specific context. + * + * @param specific Specific context pattern. + * @param max Maximum. + * @param args Additional arguments. + */ + public MaxCountExceededException(Localizable specific, + Number max, + Object ... args) { + getContext().addMessage(specific, max, args); + this.max = max; + } + + /** + * @return the maximum number of evaluations. + */ + public Number getMax() { + return max; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MultiDimensionMismatchException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MultiDimensionMismatchException.java new file mode 100644 index 000000000..35b433fbd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/MultiDimensionMismatchException.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when two sets of dimensions differ. + * + * @since 3.0 + */ +public class MultiDimensionMismatchException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -8415396756375798143L; + + /** Wrong dimensions. */ + private final Integer[] wrong; + /** Correct dimensions. */ + private final Integer[] expected; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrong Wrong dimensions. + * @param expected Expected dimensions. + */ + public MultiDimensionMismatchException(Integer[] wrong, + Integer[] expected) { + this(LocalizedFormats.DIMENSIONS_MISMATCH, wrong, expected); + } + + /** + * Construct an exception from the mismatched dimensions. + * + * @param specific Message pattern providing the specific context of + * the error. + * @param wrong Wrong dimensions. + * @param expected Expected dimensions. + */ + public MultiDimensionMismatchException(Localizable specific, + Integer[] wrong, + Integer[] expected) { + super(specific, wrong, expected); + this.wrong = wrong.clone(); + this.expected = expected.clone(); + } + + /** + * @return an array containing the wrong dimensions. + */ + public Integer[] getWrongDimensions() { + return wrong.clone(); + } + /** + * @return an array containing the expected dimensions. + */ + public Integer[] getExpectedDimensions() { + return expected.clone(); + } + + /** + * @param index Dimension index. + * @return the wrong dimension stored at {@code index}. + */ + public int getWrongDimension(int index) { + return wrong[index].intValue(); + } + /** + * @param index Dimension index. + * @return the expected dimension stored at {@code index}. + */ + public int getExpectedDimension(int index) { + return expected[index].intValue(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoBracketingException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoBracketingException.java new file mode 100644 index 000000000..a34b51148 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoBracketingException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when function values have the same sign at both + * ends of an interval. + * + * @since 3.0 + */ +public class NoBracketingException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -3629324471511904459L; + /** Lower end of the interval. */ + private final double lo; + /** Higher end of the interval. */ + private final double hi; + /** Value at lower end of the interval. */ + private final double fLo; + /** Value at higher end of the interval. */ + private final double fHi; + + /** + * Construct the exception. + * + * @param lo Lower end of the interval. + * @param hi Higher end of the interval. + * @param fLo Value at lower end of the interval. + * @param fHi Value at higher end of the interval. + */ + public NoBracketingException(double lo, double hi, + double fLo, double fHi) { + this(LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, lo, hi, fLo, fHi); + } + + /** + * Construct the exception with a specific context. + * + * @param specific Contextual information on what caused the exception. + * @param lo Lower end of the interval. + * @param hi Higher end of the interval. + * @param fLo Value at lower end of the interval. + * @param fHi Value at higher end of the interval. + * @param args Additional arguments. + */ + public NoBracketingException(Localizable specific, + double lo, double hi, + double fLo, double fHi, + Object ... args) { + super(specific, Double.valueOf(lo), Double.valueOf(hi), Double.valueOf(fLo), Double.valueOf(fHi), args); + this.lo = lo; + this.hi = hi; + this.fLo = fLo; + this.fHi = fHi; + } + + /** + * Get the lower end of the interval. + * + * @return the lower end. + */ + public double getLo() { + return lo; + } + /** + * Get the higher end of the interval. + * + * @return the higher end. + */ + public double getHi() { + return hi; + } + /** + * Get the value at the lower end of the interval. + * + * @return the value at the lower end. + */ + public double getFLo() { + return fLo; + } + /** + * Get the value at the higher end of the interval. + * + * @return the value at the higher end. + */ + public double getFHi() { + return fHi; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoDataException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoDataException.java new file mode 100644 index 000000000..5f2433905 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NoDataException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when the required data is missing. + * + * @since 2.2 + */ +public class NoDataException extends MathIllegalArgumentException { + + /** Serializable version Id. */ + private static final long serialVersionUID = -3629324471511904459L; + + /** + * Construct the exception. + */ + public NoDataException() { + this(LocalizedFormats.NO_DATA); + } + /** + * Construct the exception with a specific context. + * + * @param specific Contextual information on what caused the exception. + */ + public NoDataException(Localizable specific) { + super(specific); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NonMonotonicSequenceException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NonMonotonicSequenceException.java new file mode 100644 index 000000000..68d45fada --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NonMonotonicSequenceException.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when the a sequence of values is not monotonically + * increasing or decreasing. + * + * @since 2.2 (name changed to "NonMonotonicSequenceException" in 3.0) + */ +public class NonMonotonicSequenceException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = 3596849179428944575L; + /** + * Direction (positive for increasing, negative for decreasing). + */ + private final MathArrays.OrderDirection direction; + /** + * Whether the sequence must be strictly increasing or decreasing. + */ + private final boolean strict; + /** + * Index of the wrong value. + */ + private final int index; + /** + * Previous value. + */ + private final Number previous; + + /** + * Construct the exception. + * This constructor uses default values assuming that the sequence should + * have been strictly increasing. + * + * @param wrong Value that did not match the requirements. + * @param previous Previous value in the sequence. + * @param index Index of the value that did not match the requirements. + */ + public NonMonotonicSequenceException(Number wrong, + Number previous, + int index) { + this(wrong, previous, index, MathArrays.OrderDirection.INCREASING, true); + } + + /** + * Construct the exception. + * + * @param wrong Value that did not match the requirements. + * @param previous Previous value in the sequence. + * @param index Index of the value that did not match the requirements. + * @param direction Strictly positive for a sequence required to be + * increasing, negative (or zero) for a decreasing sequence. + * @param strict Whether the sequence must be strictly increasing or + * decreasing. + */ + public NonMonotonicSequenceException(Number wrong, + Number previous, + int index, + MathArrays.OrderDirection direction, + boolean strict) { + super(direction == MathArrays.OrderDirection.INCREASING ? + (strict ? + LocalizedFormats.NOT_STRICTLY_INCREASING_SEQUENCE : + LocalizedFormats.NOT_INCREASING_SEQUENCE) : + (strict ? + LocalizedFormats.NOT_STRICTLY_DECREASING_SEQUENCE : + LocalizedFormats.NOT_DECREASING_SEQUENCE), + wrong, previous, Integer.valueOf(index), Integer.valueOf(index - 1)); + + this.direction = direction; + this.strict = strict; + this.index = index; + this.previous = previous; + } + + /** + * @return the order direction. + **/ + public MathArrays.OrderDirection getDirection() { + return direction; + } + /** + * @return {@code true} is the sequence should be strictly monotonic. + **/ + public boolean getStrict() { + return strict; + } + /** + * Get the index of the wrong value. + * + * @return the current index. + */ + public int getIndex() { + return index; + } + /** + * @return the previous value. + */ + public Number getPrevious() { + return previous; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotANumberException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotANumberException.java new file mode 100644 index 000000000..2a77e513d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotANumberException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a number is not a number. + * + * @since 3.1 + */ +public class NotANumberException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = 20120906L; + + /** + * Construct the exception. + */ + public NotANumberException() { + super(LocalizedFormats.NAN_NOT_ALLOWED, Double.valueOf(Double.NaN)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotFiniteNumberException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotFiniteNumberException.java new file mode 100644 index 000000000..5f8c92153 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotFiniteNumberException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a number is not finite. + * + * @since 3.0 + */ +public class NotFiniteNumberException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = -6100997100383932834L; + + /** + * Construct the exception. + * + * @param wrong Value that is infinite or NaN. + * @param args Optional arguments. + */ + public NotFiniteNumberException(Number wrong, + Object ... args) { + this(LocalizedFormats.NOT_FINITE_NUMBER, wrong, args); + } + + /** + * Construct the exception with a specific context. + * + * @param specific Specific context pattern. + * @param wrong Value that is infinite or NaN. + * @param args Optional arguments. + */ + public NotFiniteNumberException(Localizable specific, + Number wrong, + Object ... args) { + super(specific, wrong, args); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotPositiveException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotPositiveException.java new file mode 100644 index 000000000..12a1fc721 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotPositiveException.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Exception to be thrown when the argument is negative. + * + * @since 2.2 + */ +public class NotPositiveException extends NumberIsTooSmallException { + /** Serializable version Id. */ + private static final long serialVersionUID = -2250556892093726375L; + + /** + * Construct the exception. + * + * @param value Argument. + */ + public NotPositiveException(Number value) { + super(value, INTEGER_ZERO, true); + } + /** + * Construct the exception with a specific context. + * + * @param specific Specific context where the error occurred. + * @param value Argument. + */ + public NotPositiveException(Localizable specific, + Number value) { + super(specific, value, INTEGER_ZERO, true); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotStrictlyPositiveException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotStrictlyPositiveException.java new file mode 100644 index 000000000..50ee49983 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NotStrictlyPositiveException.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Exception to be thrown when the argument is not greater than 0. + * + * @since 2.2 + */ +public class NotStrictlyPositiveException extends NumberIsTooSmallException { + + /** Serializable version Id. */ + private static final long serialVersionUID = -7824848630829852237L; + + /** + * Construct the exception. + * + * @param value Argument. + */ + public NotStrictlyPositiveException(Number value) { + super(value, INTEGER_ZERO, false); + } + /** + * Construct the exception with a specific context. + * + * @param specific Specific context where the error occurred. + * @param value Argument. + */ + public NotStrictlyPositiveException(Localizable specific, + Number value) { + super(specific, value, INTEGER_ZERO, false); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NullArgumentException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NullArgumentException.java new file mode 100644 index 000000000..14f2cf687 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NullArgumentException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * All conditions checks that fail due to a {@code null} argument must throw + * this exception. + * This class is meant to signal a precondition violation ("null is an illegal + * argument") and so does not extend the standard {@code NullPointerException}. + * Propagation of {@code NullPointerException} from within Commons-Math is + * construed to be a bug. + * + * @since 2.2 + */ +public class NullArgumentException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + + /** + * Default constructor. + */ + public NullArgumentException() { + this(LocalizedFormats.NULL_NOT_ALLOWED); + } + /** + * @param pattern Message pattern providing the specific context of + * the error. + * @param arguments Values for replacing the placeholders in {@code pattern}. + */ + public NullArgumentException(Localizable pattern, + Object ... arguments) { + super(pattern, arguments); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooLargeException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooLargeException.java new file mode 100644 index 000000000..544866bb6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooLargeException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a number is too large. + * + * @since 2.2 + */ +public class NumberIsTooLargeException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = 4330003017885151975L; + /** + * Higher bound. + */ + private final Number max; + /** + * Whether the maximum is included in the allowed range. + */ + private final boolean boundIsAllowed; + + /** + * Construct the exception. + * + * @param wrong Value that is larger than the maximum. + * @param max Maximum. + * @param boundIsAllowed if true the maximum is included in the allowed range. + */ + public NumberIsTooLargeException(Number wrong, + Number max, + boolean boundIsAllowed) { + this(boundIsAllowed ? + LocalizedFormats.NUMBER_TOO_LARGE : + LocalizedFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED, + wrong, max, boundIsAllowed); + } + /** + * Construct the exception with a specific context. + * + * @param specific Specific context pattern. + * @param wrong Value that is larger than the maximum. + * @param max Maximum. + * @param boundIsAllowed if true the maximum is included in the allowed range. + */ + public NumberIsTooLargeException(Localizable specific, + Number wrong, + Number max, + boolean boundIsAllowed) { + super(specific, wrong, max); + + this.max = max; + this.boundIsAllowed = boundIsAllowed; + } + + /** + * @return {@code true} if the maximum is included in the allowed range. + */ + public boolean getBoundIsAllowed() { + return boundIsAllowed; + } + + /** + * @return the maximum. + */ + public Number getMax() { + return max; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooSmallException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooSmallException.java new file mode 100644 index 000000000..99eb1ac4e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/NumberIsTooSmallException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a number is too small. + * + * @since 2.2 + */ +public class NumberIsTooSmallException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = -6100997100383932834L; + /** + * Higher bound. + */ + private final Number min; + /** + * Whether the maximum is included in the allowed range. + */ + private final boolean boundIsAllowed; + + /** + * Construct the exception. + * + * @param wrong Value that is smaller than the minimum. + * @param min Minimum. + * @param boundIsAllowed Whether {@code min} is included in the allowed range. + */ + public NumberIsTooSmallException(Number wrong, + Number min, + boolean boundIsAllowed) { + this(boundIsAllowed ? + LocalizedFormats.NUMBER_TOO_SMALL : + LocalizedFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED, + wrong, min, boundIsAllowed); + } + + /** + * Construct the exception with a specific context. + * + * @param specific Specific context pattern. + * @param wrong Value that is smaller than the minimum. + * @param min Minimum. + * @param boundIsAllowed Whether {@code min} is included in the allowed range. + */ + public NumberIsTooSmallException(Localizable specific, + Number wrong, + Number min, + boolean boundIsAllowed) { + super(specific, wrong, min); + + this.min = min; + this.boundIsAllowed = boundIsAllowed; + } + + /** + * @return {@code true} if the minimum is included in the allowed range. + */ + public boolean getBoundIsAllowed() { + return boundIsAllowed; + } + + /** + * @return the minimum. + */ + public Number getMin() { + return min; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/OutOfRangeException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/OutOfRangeException.java new file mode 100644 index 000000000..e0e16ada1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/OutOfRangeException.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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when some argument is out of range. + * + * @since 2.2 + */ +public class OutOfRangeException extends MathIllegalNumberException { + /** Serializable version Id. */ + private static final long serialVersionUID = 111601815794403609L; + /** Lower bound. */ + private final Number lo; + /** Higher bound. */ + private final Number hi; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrong Requested value. + * @param lo Lower bound. + * @param hi Higher bound. + */ + public OutOfRangeException(Number wrong, + Number lo, + Number hi) { + this(LocalizedFormats.OUT_OF_RANGE_SIMPLE, wrong, lo, hi); + } + + /** + * Construct an exception from the mismatched dimensions with a + * specific context information. + * + * @param specific Context information. + * @param wrong Requested value. + * @param lo Lower bound. + * @param hi Higher bound. + */ + public OutOfRangeException(Localizable specific, + Number wrong, + Number lo, + Number hi) { + super(specific, wrong, lo, hi); + this.lo = lo; + this.hi = hi; + } + + /** + * @return the lower bound. + */ + public Number getLo() { + return lo; + } + /** + * @return the higher bound. + */ + public Number getHi() { + return hi; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyEvaluationsException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyEvaluationsException.java new file mode 100644 index 000000000..8c111dbff --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyEvaluationsException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when the maximal number of evaluations is exceeded. + * + * @since 3.0 + */ +public class TooManyEvaluationsException extends MaxCountExceededException { + /** Serializable version Id. */ + private static final long serialVersionUID = 4330003017885151975L; + + /** + * Construct the exception. + * + * @param max Maximum number of evaluations. + */ + public TooManyEvaluationsException(Number max) { + super(max); + getContext().addMessage(LocalizedFormats.EVALUATIONS); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyIterationsException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyIterationsException.java new file mode 100644 index 000000000..75b5e7af3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/TooManyIterationsException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when the maximal number of iterations is exceeded. + * + * @since 3.1 + */ +public class TooManyIterationsException extends MaxCountExceededException { + /** Serializable version Id. */ + private static final long serialVersionUID = 20121211L; + + /** + * Construct the exception. + * + * @param max Maximum number of evaluations. + */ + public TooManyIterationsException(Number max) { + super(max); + getContext().addMessage(LocalizedFormats.ITERATIONS); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/ZeroException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/ZeroException.java new file mode 100644 index 000000000..93edad978 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/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 com.fr.third.org.apache.commons.math3.exception; + +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when zero is provided where it is not allowed. + * + * @since 2.2 + */ +public class ZeroException extends MathIllegalNumberException { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1960874856936000015L; + + /** + * Construct the exception. + */ + public ZeroException() { + this(LocalizedFormats.ZERO_NOT_ALLOWED); + } + + /** + * Construct the exception with a specific context. + * + * @param specific Specific context pattern. + * @param arguments Arguments. + */ + public ZeroException(Localizable specific, Object ... arguments) { + super(specific, INTEGER_ZERO, arguments); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/package-info.java new file mode 100644 index 000000000..1c5c54c1c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Specialized exceptions for algorithms errors. The exceptions can be localized + * using simple java properties. + * + */ +package com.fr.third.org.apache.commons.math3.exception; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ArgUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ArgUtils.java new file mode 100644 index 000000000..22ade93cc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ArgUtils.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +import java.util.List; +import java.util.ArrayList; + +/** + * Utility class for transforming the list of arguments passed to + * constructors of exceptions. + * + */ +public class ArgUtils { + /** + * Class contains only static methods. + */ + private ArgUtils() {} + + /** + * Transform a multidimensional array into a one-dimensional list. + * + * @param array Array (possibly multidimensional). + * @return a list of all the {@code Object} instances contained in + * {@code array}. + */ + public static Object[] flatten(Object[] array) { + final List list = new ArrayList(); + 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/DummyLocalizable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/DummyLocalizable.java new file mode 100644 index 000000000..d8e7a95e1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/DummyLocalizable.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +import java.util.Locale; + +/** + * Dummy implementation of the {@link Localizable} interface, without localization. + * + * @since 2.2 + */ +public class DummyLocalizable implements Localizable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 8843275624471387299L; + + /** Source string. */ + private final String source; + + /** Simple constructor. + * @param source source text + */ + public DummyLocalizable(final String source) { + this.source = source; + } + + /** {@inheritDoc} */ + public String getSourceString() { + return source; + } + + /** {@inheritDoc} */ + public String getLocalizedString(Locale locale) { + return source; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return source; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContext.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContext.java new file mode 100644 index 000000000..18358afd4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContext.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.Map; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.util.HashMap; +import java.text.MessageFormat; +import java.util.Locale; + +/** + * Class that contains the actual implementation of the functionality mandated + * by the {@link ExceptionContext} interface. + * All Commons Math exceptions delegate the interface's methods to this class. + * + * @since 3.0 + */ +public class ExceptionContext implements Serializable { + /** Serializable version Id. */ + private static final long serialVersionUID = -6024911025449780478L; + /** + * The throwable to which this context refers to. + */ + private Throwable throwable; + /** + * Various informations that enrich the informative message. + */ + private List msgPatterns; + /** + * Various informations that enrich the informative message. + * The arguments will replace the corresponding place-holders in + * {@link #msgPatterns}. + */ + private List msgArguments; + /** + * Arbitrary context information. + */ + private Map context; + + /** Simple constructor. + * @param throwable the exception this context refers too + */ + public ExceptionContext(final Throwable throwable) { + this.throwable = throwable; + msgPatterns = new ArrayList(); + msgArguments = new ArrayList(); + context = new HashMap(); + } + + /** Get a reference to the exception to which the context relates. + * @return a reference to the exception to which the context relates + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Adds a message. + * + * @param pattern Message pattern. + * @param arguments Values for replacing the placeholders in the message + * pattern. + */ + public void addMessage(Localizable pattern, + Object ... arguments) { + msgPatterns.add(pattern); + msgArguments.add(ArgUtils.flatten(arguments)); + } + + /** + * Sets the context (key, value) pair. + * Keys are assumed to be unique within an instance. If the same key is + * assigned a new value, the previous one will be lost. + * + * @param key Context key (not null). + * @param value Context value. + */ + public void setValue(String key, Object value) { + context.put(key, value); + } + + /** + * Gets the value associated to the given context key. + * + * @param key Context key. + * @return the context value or {@code null} if the key does not exist. + */ + public Object getValue(String key) { + return context.get(key); + } + + /** + * Gets all the keys stored in the exception + * + * @return the set of keys. + */ + public Set getKeys() { + return context.keySet(); + } + + /** + * Gets the default message. + * + * @return the message. + */ + public String getMessage() { + return getMessage(Locale.US); + } + + /** + * Gets the message in the default locale. + * + * @return the localized message. + */ + public String getLocalizedMessage() { + return getMessage(Locale.getDefault()); + } + + /** + * Gets the message in a specified locale. + * + * @param locale Locale in which the message should be translated. + * @return the localized message. + */ + public String getMessage(final Locale locale) { + return buildMessage(locale, ": "); + } + + /** + * Gets the message in a specified locale. + * + * @param locale Locale in which the message should be translated. + * @param separator Separator inserted between the message parts. + * @return the localized message. + */ + public String getMessage(final Locale locale, + final String separator) { + return buildMessage(locale, separator); + } + + /** + * Builds a message string. + * + * @param locale Locale in which the message should be translated. + * @param separator Message separator. + * @return a localized message string. + */ + private String buildMessage(Locale locale, + String separator) { + final StringBuilder sb = new StringBuilder(); + int count = 0; + final int len = msgPatterns.size(); + for (int i = 0; i < len; i++) { + final Localizable pat = msgPatterns.get(i); + final Object[] args = msgArguments.get(i); + final MessageFormat fmt = new MessageFormat(pat.getLocalizedString(locale), + locale); + sb.append(fmt.format(args)); + if (++count < len) { + // Add a separator if there are other messages. + sb.append(separator); + } + } + + return sb.toString(); + } + + /** + * Serialize this object to the given stream. + * + * @param out Stream. + * @throws IOException This should never happen. + */ + private void writeObject(ObjectOutputStream out) + throws IOException { + out.writeObject(throwable); + serializeMessages(out); + serializeContext(out); + } + /** + * Deserialize this object from the given stream. + * + * @param in Stream. + * @throws IOException This should never happen. + * @throws ClassNotFoundException This should never happen. + */ + private void readObject(ObjectInputStream in) + throws IOException, + ClassNotFoundException { + throwable = (Throwable) in.readObject(); + deSerializeMessages(in); + deSerializeContext(in); + } + + /** + * Serialize {@link #msgPatterns} and {@link #msgArguments}. + * + * @param out Stream. + * @throws IOException This should never happen. + */ + private void serializeMessages(ObjectOutputStream out) + throws IOException { + // Step 1. + final int len = msgPatterns.size(); + out.writeInt(len); + // Step 2. + for (int i = 0; i < len; i++) { + final Localizable pat = msgPatterns.get(i); + // Step 3. + out.writeObject(pat); + final Object[] args = msgArguments.get(i); + final int aLen = args.length; + // Step 4. + out.writeInt(aLen); + for (int j = 0; j < aLen; j++) { + if (args[j] instanceof Serializable) { + // Step 5a. + out.writeObject(args[j]); + } else { + // Step 5b. + out.writeObject(nonSerializableReplacement(args[j])); + } + } + } + } + + /** + * Deserialize {@link #msgPatterns} and {@link #msgArguments}. + * + * @param in Stream. + * @throws IOException This should never happen. + * @throws ClassNotFoundException This should never happen. + */ + private void deSerializeMessages(ObjectInputStream in) + throws IOException, + ClassNotFoundException { + // Step 1. + final int len = in.readInt(); + msgPatterns = new ArrayList(len); + msgArguments = new ArrayList(len); + // Step 2. + for (int i = 0; i < len; i++) { + // Step 3. + final Localizable pat = (Localizable) in.readObject(); + msgPatterns.add(pat); + // Step 4. + final int aLen = in.readInt(); + final Object[] args = new Object[aLen]; + for (int j = 0; j < aLen; j++) { + // Step 5. + args[j] = in.readObject(); + } + msgArguments.add(args); + } + } + + /** + * Serialize {@link #context}. + * + * @param out Stream. + * @throws IOException This should never happen. + */ + private void serializeContext(ObjectOutputStream out) + throws IOException { + // Step 1. + final int len = context.size(); + out.writeInt(len); + for (Map.Entry entry : context.entrySet()) { + // Step 2. + out.writeObject(entry.getKey()); + final Object value = entry.getValue(); + if (value instanceof Serializable) { + // Step 3a. + out.writeObject(value); + } else { + // Step 3b. + out.writeObject(nonSerializableReplacement(value)); + } + } + } + + /** + * Deserialize {@link #context}. + * + * @param in Stream. + * @throws IOException This should never happen. + * @throws ClassNotFoundException This should never happen. + */ + private void deSerializeContext(ObjectInputStream in) + throws IOException, + ClassNotFoundException { + // Step 1. + final int len = in.readInt(); + context = new HashMap(); + for (int i = 0; i < len; i++) { + // Step 2. + final String key = (String) in.readObject(); + // Step 3. + final Object value = in.readObject(); + context.put(key, value); + } + } + + /** + * Replaces a non-serializable object with an error message string. + * + * @param obj Object that does not implement the {@code Serializable} + * interface. + * @return a string that mentions which class could not be serialized. + */ + private String nonSerializableReplacement(Object obj) { + return "[Object could not be serialized: " + obj.getClass().getName() + "]"; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContextProvider.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContextProvider.java new file mode 100644 index 000000000..6cc4b6a7b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/ExceptionContextProvider.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +/** + * Interface for accessing the context data structure stored in Commons Math + * exceptions. + * + */ +public interface ExceptionContextProvider { + /** + * Gets a reference to the "rich context" data structure that allows to + * customize error messages and store key, value pairs in exceptions. + * + * @return a reference to the exception context. + */ + ExceptionContext getContext(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/Localizable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/Localizable.java new file mode 100644 index 000000000..97c240166 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/Localizable.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +import java.io.Serializable; +import java.util.Locale; + +/** + * Interface for localizable strings. + * + * @since 2.2 + */ +public interface Localizable extends Serializable { + /** + * Gets the source (non-localized) string. + * + * @return the source string. + */ + String getSourceString(); + + /** + * Gets the localized string. + * + * @param locale locale into which to get the string. + * @return the localized string or the source string if no + * localized version is available. + */ + String getLocalizedString(Locale locale); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/LocalizedFormats.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/LocalizedFormats.java new file mode 100644 index 000000000..9fbbc386f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/LocalizedFormats.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.exception.util; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Enumeration for localized messages formats used in exceptions messages. + *

+ * 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. + *

+ * @since 2.2 + */ +public enum LocalizedFormats implements Localizable { + + // CHECKSTYLE: stop MultipleVariableDeclarations + // CHECKSTYLE: stop JavadocVariable + + ARGUMENT_OUTSIDE_DOMAIN("Argument {0} outside domain [{1} ; {2}]"), + ARRAY_SIZE_EXCEEDS_MAX_VARIABLES("array size cannot be greater than {0}"), + ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1("array sizes should have difference 1 ({0} != {1} + 1)"), + ARRAY_SUMS_TO_ZERO("array sums to zero"), + ASSYMETRIC_EIGEN_NOT_SUPPORTED("eigen decomposition of assymetric matrices not supported yet"), + AT_LEAST_ONE_COLUMN("matrix must have at least one column"), + AT_LEAST_ONE_ROW("matrix must have at least one row"), + BANDWIDTH("bandwidth ({0})"), + BESSEL_FUNCTION_BAD_ARGUMENT("Bessel function of order {0} cannot be computed for x = {1}"), + BESSEL_FUNCTION_FAILED_CONVERGENCE("Bessel function of order {0} failed to converge for x = {1}"), + BINOMIAL_INVALID_PARAMETERS_ORDER("must have n >= k for binomial coefficient (n, k), got k = {0}, n = {1}"), + BINOMIAL_NEGATIVE_PARAMETER("must have n >= 0 for binomial coefficient (n, k), got n = {0}"), + CANNOT_CLEAR_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS("statistics constructed from external moments cannot be cleared"), + CANNOT_COMPUTE_0TH_ROOT_OF_UNITY("cannot compute 0-th root of unity, indefinite result"), + CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA("cannot compute beta density at 0 when alpha = {0,number}"), + CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA("cannot compute beta density at 1 when beta = %.3g"), + CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N("cannot compute nth root for null or negative n: {0}"), + CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS("cannot discard a negative number of elements ({0})"), + CANNOT_FORMAT_INSTANCE_AS_3D_VECTOR("cannot format a {0} instance as a 3D vector"), + CANNOT_FORMAT_INSTANCE_AS_COMPLEX("cannot format a {0} instance as a complex number"), + CANNOT_FORMAT_INSTANCE_AS_REAL_VECTOR("cannot format a {0} instance as a real vector"), + CANNOT_FORMAT_OBJECT_TO_FRACTION("cannot format given object as a fraction number"), + CANNOT_INCREMENT_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS("statistics constructed from external moments cannot be incremented"), + CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR("cannot normalize a zero norm vector"), + CANNOT_RETRIEVE_AT_NEGATIVE_INDEX("elements cannot be retrieved from a negative array index {0}"), + CANNOT_SET_AT_NEGATIVE_INDEX("cannot set an element at a negative index {0}"), + CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY("cannot substitute an element from an empty array"), + CANNOT_TRANSFORM_TO_DOUBLE("Conversion Exception in Transformation: {0}"), + CARDAN_ANGLES_SINGULARITY("Cardan angles singularity"), + CLASS_DOESNT_IMPLEMENT_COMPARABLE("class ({0}) does not implement Comparable"), + CLOSE_VERTICES("too close vertices near point ({0}, {1}, {2})"), + CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT("the closest orthogonal matrix has a negative determinant {0}"), + COLUMN_INDEX_OUT_OF_RANGE("column index {0} out of allowed range [{1}, {2}]"), + COLUMN_INDEX("column index ({0})"), /* keep */ + CONSTRAINT("constraint"), /* keep */ + CONTINUED_FRACTION_INFINITY_DIVERGENCE("Continued fraction convergents diverged to +/- infinity for value {0}"), + CONTINUED_FRACTION_NAN_DIVERGENCE("Continued fraction diverged to NaN for value {0}"), + CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR("contraction criteria ({0}) smaller than the expansion factor ({1}). This would lead to a never ending loop of expansion and contraction as a newly expanded internal storage array would immediately satisfy the criteria for contraction."), + CONTRACTION_CRITERIA_SMALLER_THAN_ONE("contraction criteria smaller than one ({0}). This would lead to a never ending loop of expansion and contraction as an internal storage array length equal to the number of elements would satisfy the contraction criteria."), + CONVERGENCE_FAILED("convergence failed"), /* keep */ + CROSSING_BOUNDARY_LOOPS("some outline boundary loops cross each other"), + CROSSOVER_RATE("crossover rate ({0})"), + CUMULATIVE_PROBABILITY_RETURNED_NAN("Cumulative probability function returned NaN for argument {0} p = {1}"), + DIFFERENT_ROWS_LENGTHS("some rows have length {0} while others have length {1}"), + DIFFERENT_ORIG_AND_PERMUTED_DATA("original and permuted data must contain the same elements"), + DIGEST_NOT_INITIALIZED("digest not initialized"), + DIMENSIONS_MISMATCH_2x2("got {0}x{1} but expected {2}x{3}"), /* keep */ + DIMENSIONS_MISMATCH_SIMPLE("{0} != {1}"), /* keep */ + DIMENSIONS_MISMATCH("dimensions mismatch"), /* keep */ + DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN("Discrete cumulative probability function returned NaN for argument {0}"), + DISTRIBUTION_NOT_LOADED("distribution not loaded"), + DUPLICATED_ABSCISSA_DIVISION_BY_ZERO("duplicated abscissa {0} causes division by zero"), + EDGE_CONNECTED_TO_ONE_FACET("edge joining points ({0}, {1}, {2}) and ({3}, {4}, {5}) is connected to one facet only"), + ELITISM_RATE("elitism rate ({0})"), + EMPTY_CLUSTER_IN_K_MEANS("empty cluster in k-means"), + EMPTY_INTERPOLATION_SAMPLE("sample for interpolation is empty"), + EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY("empty polynomials coefficients array"), /* keep */ + EMPTY_SELECTED_COLUMN_INDEX_ARRAY("empty selected column index array"), + EMPTY_SELECTED_ROW_INDEX_ARRAY("empty selected row index array"), + EMPTY_STRING_FOR_IMAGINARY_CHARACTER("empty string for imaginary character"), + ENDPOINTS_NOT_AN_INTERVAL("endpoints do not specify an interval: [{0}, {1}]"), + EQUAL_VERTICES_IN_SIMPLEX("equal vertices {0} and {1} in simplex configuration"), + EULER_ANGLES_SINGULARITY("Euler angles singularity"), + EVALUATION("evaluation"), /* keep */ + EXPANSION_FACTOR_SMALLER_THAN_ONE("expansion factor smaller than one ({0})"), + FACET_ORIENTATION_MISMATCH("facets orientation mismatch around edge joining points ({0}, {1}, {2}) and ({3}, {4}, {5})"), + FACTORIAL_NEGATIVE_PARAMETER("must have n >= 0 for n!, got n = {0}"), + FAILED_BRACKETING("number of iterations={4}, maximum iterations={5}, initial={6}, lower bound={7}, upper bound={8}, final a value={0}, final b value={1}, f(a)={2}, f(b)={3}"), + FAILED_FRACTION_CONVERSION("Unable to convert {0} to fraction after {1} iterations"), + FIRST_COLUMNS_NOT_INITIALIZED_YET("first {0} columns are not initialized yet"), + FIRST_ELEMENT_NOT_ZERO("first element is not 0: {0}"), + FIRST_ROWS_NOT_INITIALIZED_YET("first {0} rows are not initialized yet"), + FRACTION_CONVERSION_OVERFLOW("Overflow trying to convert {0} to fraction ({1}/{2})"), + FUNCTION_NOT_DIFFERENTIABLE("function is not differentiable"), + FUNCTION_NOT_POLYNOMIAL("function is not polynomial"), + GCD_OVERFLOW_32_BITS("overflow: gcd({0}, {1}) is 2^31"), + GCD_OVERFLOW_64_BITS("overflow: gcd({0}, {1}) is 2^63"), + HOLE_BETWEEN_MODELS_TIME_RANGES("{0} wide hole between models time ranges"), + ILL_CONDITIONED_OPERATOR("condition number {1} is too high "), + INCONSISTENT_STATE_AT_2_PI_WRAPPING("inconsistent state at 2\u03c0 wrapping"), + INDEX_LARGER_THAN_MAX("the index specified: {0} is larger than the current maximal index {1}"), + INDEX_NOT_POSITIVE("index ({0}) is not positive"), + INDEX_OUT_OF_RANGE("index {0} out of allowed range [{1}, {2}]"), + INDEX("index ({0})"), /* keep */ + NOT_FINITE_NUMBER("{0} is not a finite number"), /* keep */ + INFINITE_BOUND("interval bounds must be finite"), + ARRAY_ELEMENT("value {0} at index {1}"), /* keep */ + INFINITE_ARRAY_ELEMENT("Array contains an infinite element, {0} at index {1}"), + INFINITE_VALUE_CONVERSION("cannot convert infinite value"), + INITIAL_CAPACITY_NOT_POSITIVE("initial capacity ({0}) is not positive"), + INITIAL_COLUMN_AFTER_FINAL_COLUMN("initial column {1} after final column {0}"), + INITIAL_ROW_AFTER_FINAL_ROW("initial row {1} after final row {0}"), + @Deprecated + INPUT_DATA_FROM_UNSUPPORTED_DATASOURCE("input data comes from unsupported datasource: {0}, supported sources: {1}, {2}"), + INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES("instance of class {0} not comparable to existing values"), + INSUFFICIENT_DATA("insufficient data"), + INSUFFICIENT_DATA_FOR_T_STATISTIC("insufficient data for t statistic, needs at least 2, got {0}"), + INSUFFICIENT_DIMENSION("insufficient dimension {0}, must be at least {1}"), + DIMENSION("dimension ({0})"), /* keep */ + INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE("sample contains {0} observed points, at least {1} are required"), + INSUFFICIENT_ROWS_AND_COLUMNS("insufficient data: only {0} rows and {1} columns."), + INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS("multistep method needs at least {0} previous steps, got {1}"), + INTERNAL_ERROR("internal error, please fill a bug report at {0}"), + INVALID_BINARY_DIGIT("invalid binary digit: {0}"), + INVALID_BINARY_CHROMOSOME("binary mutation works on BinaryChromosome only"), + INVALID_BRACKETING_PARAMETERS("invalid bracketing parameters: lower bound={0}, initial={1}, upper bound={2}"), + INVALID_FIXED_LENGTH_CHROMOSOME("one-point crossover only works with fixed-length chromosomes"), + INVALID_IMPLEMENTATION("required functionality is missing in {0}"), + INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS("invalid interval, initial value parameters: lower={0}, initial={1}, upper={2}"), + INVALID_ITERATIONS_LIMITS("invalid iteration limits: min={0}, max={1}"), + INVALID_MAX_ITERATIONS("bad value for maximum iterations number: {0}"), + NOT_ENOUGH_DATA_REGRESSION("the number of observations is not sufficient to conduct regression"), + INVALID_REGRESSION_ARRAY("input data array length = {0} does not match the number of observations = {1} and the number of regressors = {2}"), + INVALID_REGRESSION_OBSERVATION("length of regressor array = {0} does not match the number of variables = {1} in the model"), + INVALID_ROUNDING_METHOD("invalid rounding method {0}, valid methods: {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}), {11} ({12}), {13} ({14}), {15} ({16})"), + ITERATOR_EXHAUSTED("iterator exhausted"), + ITERATIONS("iterations"), /* keep */ + LCM_OVERFLOW_32_BITS("overflow: lcm({0}, {1}) is 2^31"), + LCM_OVERFLOW_64_BITS("overflow: lcm({0}, {1}) is 2^63"), + LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE("list of chromosomes bigger than maxPopulationSize"), + LOESS_EXPECTS_AT_LEAST_ONE_POINT("Loess expects at least 1 point"), + LOWER_BOUND_NOT_BELOW_UPPER_BOUND("lower bound ({0}) must be strictly less than upper bound ({1})"), /* keep */ + LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT("lower endpoint ({0}) must be less than or equal to upper endpoint ({1})"), + MAP_MODIFIED_WHILE_ITERATING("map has been modified while iterating"), + MULTISTEP_STARTER_STOPPED_EARLY("multistep integrator starter stopped early, maybe too large step size"), + EVALUATIONS("evaluations"), /* keep */ + MAX_COUNT_EXCEEDED("maximal count ({0}) exceeded"), /* keep */ + MAX_ITERATIONS_EXCEEDED("maximal number of iterations ({0}) exceeded"), + MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION("minimal step size ({1,number,0.00E00}) reached, integration needs {0,number,0.00E00}"), + MISMATCHED_LOESS_ABSCISSA_ORDINATE_ARRAYS("Loess expects the abscissa and ordinate arrays to be of the same size, but got {0} abscissae and {1} ordinatae"), + MUTATION_RATE("mutation rate ({0})"), + NAN_ELEMENT_AT_INDEX("element {0} is NaN"), + NAN_VALUE_CONVERSION("cannot convert NaN value"), + NEGATIVE_BRIGHTNESS_EXPONENT("brightness exponent should be positive or null, but got {0}"), + NEGATIVE_COMPLEX_MODULE("negative complex module {0}"), + NEGATIVE_ELEMENT_AT_2D_INDEX("element ({0}, {1}) is negative: {2}"), + NEGATIVE_ELEMENT_AT_INDEX("element {0} is negative: {1}"), + NEGATIVE_NUMBER_OF_SUCCESSES("number of successes must be non-negative ({0})"), + NUMBER_OF_SUCCESSES("number of successes ({0})"), /* keep */ + NEGATIVE_NUMBER_OF_TRIALS("number of trials must be non-negative ({0})"), + NUMBER_OF_INTERPOLATION_POINTS("number of interpolation points ({0})"), /* keep */ + NUMBER_OF_TRIALS("number of trials ({0})"), + NOT_CONVEX("vertices do not form a convex hull in CCW winding"), + NOT_CONVEX_HYPERPLANES("hyperplanes do not define a convex region"), + ROBUSTNESS_ITERATIONS("number of robustness iterations ({0})"), + START_POSITION("start position ({0})"), /* keep */ + NON_CONVERGENT_CONTINUED_FRACTION("Continued fraction convergents failed to converge (in less than {0} iterations) for value {1}"), + NON_INVERTIBLE_TRANSFORM("non-invertible affine transform collapses some lines into single points"), + NON_POSITIVE_MICROSPHERE_ELEMENTS("number of microsphere elements must be positive, but got {0}"), + NON_POSITIVE_POLYNOMIAL_DEGREE("polynomial degree must be positive: degree={0}"), + NON_REAL_FINITE_ABSCISSA("all abscissae must be finite real numbers, but {0}-th is {1}"), + NON_REAL_FINITE_ORDINATE("all ordinatae must be finite real numbers, but {0}-th is {1}"), + NON_REAL_FINITE_WEIGHT("all weights must be finite real numbers, but {0}-th is {1}"), + NON_SQUARE_MATRIX("non square ({0}x{1}) matrix"), + NORM("Norm ({0})"), /* keep */ + NORMALIZE_INFINITE("Cannot normalize to an infinite value"), + NORMALIZE_NAN("Cannot normalize to NaN"), + NOT_ADDITION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not addition compatible"), + NOT_DECREASING_NUMBER_OF_POINTS("points {0} and {1} are not decreasing ({2} < {3})"), + NOT_DECREASING_SEQUENCE("points {3} and {2} are not decreasing ({1} < {0})"), /* keep */ + NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS("not enough data ({0} rows) for this many predictors ({1} predictors)"), + NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION("spline partition must have at least {0} points, got {1}"), + NOT_INCREASING_NUMBER_OF_POINTS("points {0} and {1} are not increasing ({2} > {3})"), + NOT_INCREASING_SEQUENCE("points {3} and {2} are not increasing ({1} > {0})"), /* keep */ + NOT_MULTIPLICATION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not multiplication compatible"), + NOT_POSITIVE_DEFINITE_MATRIX("not positive definite matrix"), /* keep */ + NON_POSITIVE_DEFINITE_MATRIX("not positive definite matrix: diagonal element at ({1},{1}) is smaller than {2} ({0})"), + NON_POSITIVE_DEFINITE_OPERATOR("non positive definite linear operator"), /* keep */ + NON_SELF_ADJOINT_OPERATOR("non self-adjoint linear operator"), /* keep */ + NON_SQUARE_OPERATOR("non square ({0}x{1}) linear operator"), /* keep */ + DEGREES_OF_FREEDOM("degrees of freedom ({0})"), /* keep */ + NOT_POSITIVE_DEGREES_OF_FREEDOM("degrees of freedom must be positive ({0})"), + NOT_POSITIVE_ELEMENT_AT_INDEX("element {0} is not positive: {1}"), + NOT_POSITIVE_EXPONENT("invalid exponent {0} (must be positive)"), + NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE("number of elements should be positive ({0})"), + BASE("base ({0})"), /* keep */ + EXPONENT("exponent ({0})"), /* keep */ + NOT_POSITIVE_LENGTH("length must be positive ({0})"), + LENGTH("length ({0})"), /* keep */ + NOT_POSITIVE_MEAN("mean must be positive ({0})"), + MEAN("mean ({0})"), /* keep */ + NOT_POSITIVE_NUMBER_OF_SAMPLES("number of sample is not positive: {0}"), + NUMBER_OF_SAMPLES("number of samples ({0})"), /* keep */ + NOT_POSITIVE_PERMUTATION("permutation k ({0}) must be positive"), + PERMUTATION_SIZE("permutation size ({0}"), /* keep */ + NOT_POSITIVE_POISSON_MEAN("the Poisson mean must be positive ({0})"), + NOT_POSITIVE_POPULATION_SIZE("population size must be positive ({0})"), + POPULATION_SIZE("population size ({0})"), /* keep */ + NOT_POSITIVE_ROW_DIMENSION("invalid row dimension: {0} (must be positive)"), + NOT_POSITIVE_SAMPLE_SIZE("sample size must be positive ({0})"), + NOT_POSITIVE_SCALE("scale must be positive ({0})"), + SCALE("scale ({0})"), /* keep */ + NOT_POSITIVE_SHAPE("shape must be positive ({0})"), + SHAPE("shape ({0})"), /* keep */ + NOT_POSITIVE_STANDARD_DEVIATION("standard deviation must be positive ({0})"), + STANDARD_DEVIATION("standard deviation ({0})"), /* keep */ + NOT_POSITIVE_UPPER_BOUND("upper bound must be positive ({0})"), + NOT_POSITIVE_WINDOW_SIZE("window size must be positive ({0})"), + NOT_POWER_OF_TWO("{0} is not a power of 2"), + NOT_POWER_OF_TWO_CONSIDER_PADDING("{0} is not a power of 2, consider padding for fix"), + NOT_POWER_OF_TWO_PLUS_ONE("{0} is not a power of 2 plus one"), + NOT_STRICTLY_DECREASING_NUMBER_OF_POINTS("points {0} and {1} are not strictly decreasing ({2} <= {3})"), + NOT_STRICTLY_DECREASING_SEQUENCE("points {3} and {2} are not strictly decreasing ({1} <= {0})"), /* keep */ + NOT_STRICTLY_INCREASING_KNOT_VALUES("knot values must be strictly increasing"), + NOT_STRICTLY_INCREASING_NUMBER_OF_POINTS("points {0} and {1} are not strictly increasing ({2} >= {3})"), + NOT_STRICTLY_INCREASING_SEQUENCE("points {3} and {2} are not strictly increasing ({1} >= {0})"), /* keep */ + NOT_SUBTRACTION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not subtraction compatible"), + NOT_SUPPORTED_IN_DIMENSION_N("method not supported in dimension {0}"), + NOT_SYMMETRIC_MATRIX("not symmetric matrix"), + NON_SYMMETRIC_MATRIX("non symmetric matrix: the difference between entries at ({0},{1}) and ({1},{0}) is larger than {2}"), /* keep */ + NO_BIN_SELECTED("no bin selected"), + NO_CONVERGENCE_WITH_ANY_START_POINT("none of the {0} start points lead to convergence"), /* keep */ + NO_DATA("no data"), /* keep */ + NO_DEGREES_OF_FREEDOM("no degrees of freedom ({0} measurements, {1} parameters)"), + NO_DENSITY_FOR_THIS_DISTRIBUTION("This distribution does not have a density function implemented"), + NO_FEASIBLE_SOLUTION("no feasible solution"), + NO_OPTIMUM_COMPUTED_YET("no optimum computed yet"), /* keep */ + NO_REGRESSORS("Regression model must include at least one regressor"), + NO_RESULT_AVAILABLE("no result available"), + NO_SUCH_MATRIX_ENTRY("no entry at indices ({0}, {1}) in a {2}x{3} matrix"), + NAN_NOT_ALLOWED("NaN is not allowed"), + NULL_NOT_ALLOWED("null is not allowed"), /* keep */ + ARRAY_ZERO_LENGTH_OR_NULL_NOT_ALLOWED("a null or zero length array not allowed"), + COVARIANCE_MATRIX("covariance matrix"), /* keep */ + DENOMINATOR("denominator"), /* keep */ + DENOMINATOR_FORMAT("denominator format"), /* keep */ + FRACTION("fraction"), /* keep */ + FUNCTION("function"), /* keep */ + IMAGINARY_FORMAT("imaginary format"), /* keep */ + INPUT_ARRAY("input array"), /* keep */ + NUMERATOR("numerator"), /* keep */ + NUMERATOR_FORMAT("numerator format"), /* keep */ + OBJECT_TRANSFORMATION("conversion exception in transformation"), /* keep */ + REAL_FORMAT("real format"), /* keep */ + WHOLE_FORMAT("whole format"), /* keep */ + NUMBER_TOO_LARGE("{0} is larger than the maximum ({1})"), /* keep */ + NUMBER_TOO_SMALL("{0} is smaller than the minimum ({1})"), /* keep */ + NUMBER_TOO_LARGE_BOUND_EXCLUDED("{0} is larger than, or equal to, the maximum ({1})"), /* keep */ + NUMBER_TOO_SMALL_BOUND_EXCLUDED("{0} is smaller than, or equal to, the minimum ({1})"), /* keep */ + NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE("number of successes ({0}) must be less than or equal to population size ({1})"), + NUMERATOR_OVERFLOW_AFTER_MULTIPLY("overflow, numerator too large after multiply: {0}"), + N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED("{0} points Legendre-Gauss integrator not supported, number of points must be in the {1}-{2} range"), + OBSERVED_COUNTS_ALL_ZERO("observed counts are all 0 in observed array {0}"), + OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY("observed counts are both zero for entry {0}"), + BOBYQA_BOUND_DIFFERENCE_CONDITION("the difference between the upper and lower bound must be larger than twice the initial trust region radius ({0})"), + OUT_OF_BOUNDS_QUANTILE_VALUE("out of bounds quantile value: {0}, must be in (0, 100]"), + OUT_OF_BOUNDS_CONFIDENCE_LEVEL("out of bounds confidence level {0}, must be between {1} and {2}"), + OUT_OF_BOUND_SIGNIFICANCE_LEVEL("out of bounds significance level {0}, must be between {1} and {2}"), + SIGNIFICANCE_LEVEL("significance level ({0})"), /* keep */ + OUT_OF_ORDER_ABSCISSA_ARRAY("the abscissae array must be sorted in a strictly increasing order, but the {0}-th element is {1} whereas {2}-th is {3}"), + OUT_OF_PLANE("point ({0}, {1}, {2}) is out of plane"), + OUT_OF_RANGE_ROOT_OF_UNITY_INDEX("out of range root of unity index {0} (must be in [{1};{2}])"), + OUT_OF_RANGE("out of range"), /* keep */ + OUT_OF_RANGE_SIMPLE("{0} out of [{1}, {2}] range"), /* keep */ + OUT_OF_RANGE_LEFT("{0} out of ({1}, {2}] range"), + OUT_OF_RANGE_RIGHT("{0} out of [{1}, {2}) range"), + OUTLINE_BOUNDARY_LOOP_OPEN("an outline boundary loop is open"), + OVERFLOW("overflow"), /* keep */ + OVERFLOW_IN_FRACTION("overflow in fraction {0}/{1}, cannot negate"), + OVERFLOW_IN_ADDITION("overflow in addition: {0} + {1}"), + OVERFLOW_IN_SUBTRACTION("overflow in subtraction: {0} - {1}"), + OVERFLOW_IN_MULTIPLICATION("overflow in multiplication: {0} * {1}"), + PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD("cannot access {0} method in percentile implementation {1}"), + PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD("percentile implementation {0} does not support {1}"), + PERMUTATION_EXCEEDS_N("permutation size ({0}) exceeds permuation domain ({1})"), /* keep */ + POLYNOMIAL("polynomial"), /* keep */ + POLYNOMIAL_INTERPOLANTS_MISMATCH_SEGMENTS("number of polynomial interpolants must match the number of segments ({0} != {1} - 1)"), + POPULATION_LIMIT_NOT_POSITIVE("population limit has to be positive"), + POWER_NEGATIVE_PARAMETERS("cannot raise an integral value to a negative power ({0}^{1})"), + PROPAGATION_DIRECTION_MISMATCH("propagation direction mismatch"), + RANDOMKEY_MUTATION_WRONG_CLASS("RandomKeyMutation works only with RandomKeys, not {0}"), + ROOTS_OF_UNITY_NOT_COMPUTED_YET("roots of unity have not been computed yet"), + ROTATION_MATRIX_DIMENSIONS("a {0}x{1} matrix cannot be a rotation matrix"), + ROW_INDEX_OUT_OF_RANGE("row index {0} out of allowed range [{1}, {2}]"), + ROW_INDEX("row index ({0})"), /* keep */ + SAME_SIGN_AT_ENDPOINTS("function values at endpoints do not have different signs, endpoints: [{0}, {1}], values: [{2}, {3}]"), + SAMPLE_SIZE_EXCEEDS_COLLECTION_SIZE("sample size ({0}) exceeds collection size ({1})"), /* keep */ + SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE("sample size ({0}) must be less than or equal to population size ({1})"), + SIMPLEX_NEED_ONE_POINT("simplex must contain at least one point"), + SIMPLE_MESSAGE("{0}"), + SINGULAR_MATRIX("matrix is singular"), /* keep */ + SINGULAR_OPERATOR("operator is singular"), + SUBARRAY_ENDS_AFTER_ARRAY_END("subarray ends after array end"), + TOO_LARGE_CUTOFF_SINGULAR_VALUE("cutoff singular value is {0}, should be at most {1}"), + TOO_LARGE_TOURNAMENT_ARITY("tournament arity ({0}) cannot be bigger than population size ({1})"), + TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY("cannot discard {0} elements from a {1} elements array"), + TOO_MANY_REGRESSORS("too many regressors ({0}) specified, only {1} in the model"), + TOO_SMALL_COST_RELATIVE_TOLERANCE("cost relative tolerance is too small ({0}), no further reduction in the sum of squares is possible"), + TOO_SMALL_INTEGRATION_INTERVAL("too small integration interval: length = {0}"), + TOO_SMALL_ORTHOGONALITY_TOLERANCE("orthogonality tolerance is too small ({0}), solution is orthogonal to the jacobian"), + TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE("parameters relative tolerance is too small ({0}), no further improvement in the approximate solution is possible"), + TRUST_REGION_STEP_FAILED("trust region step has failed to reduce Q"), + TWO_OR_MORE_CATEGORIES_REQUIRED("two or more categories required, got {0}"), + TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED("two or more values required in each category, one has {0}"), + UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH("unable to bracket optimum in line search"), + UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM("unable to compute covariances: singular problem"), + UNABLE_TO_FIRST_GUESS_HARMONIC_COEFFICIENTS("unable to first guess the harmonic coefficients"), + UNABLE_TO_ORTHOGONOLIZE_MATRIX("unable to orthogonalize matrix in {0} iterations"), + UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN("unable to perform Q.R decomposition on the {0}x{1} jacobian matrix"), + UNABLE_TO_SOLVE_SINGULAR_PROBLEM("unable to solve: singular problem"), + UNBOUNDED_SOLUTION("unbounded solution"), + UNKNOWN_MODE("unknown mode {0}, known modes: {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}) and {11} ({12})"), + UNKNOWN_PARAMETER("unknown parameter {0}"), + UNMATCHED_ODE_IN_EXPANDED_SET("ode does not match the main ode set in the extended set"), + CANNOT_PARSE_AS_TYPE("string \"{0}\" unparseable (from position {1}) as an object of type {2}"), /* keep */ + CANNOT_PARSE("string \"{0}\" unparseable (from position {1})"), /* keep */ + UNPARSEABLE_3D_VECTOR("unparseable 3D vector: \"{0}\""), + UNPARSEABLE_COMPLEX_NUMBER("unparseable complex number: \"{0}\""), + UNPARSEABLE_REAL_VECTOR("unparseable real vector: \"{0}\""), + UNSUPPORTED_EXPANSION_MODE("unsupported expansion mode {0}, supported modes are {1} ({2}) and {3} ({4})"), + UNSUPPORTED_OPERATION("unsupported operation"), /* keep */ + ARITHMETIC_EXCEPTION("arithmetic exception"), /* keep */ + ILLEGAL_STATE("illegal state"), /* keep */ + USER_EXCEPTION("exception generated in user code"), /* keep */ + URL_CONTAINS_NO_DATA("URL {0} contains no data"), + VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC("{0} values have been added before statistic is configured"), + VECTOR_LENGTH_MISMATCH("vector length mismatch: got {0} but expected {1}"), + VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT("vector must have at least one element"), + WEIGHT_AT_LEAST_ONE_NON_ZERO("weigth array must contain at least one non-zero value"), + WRONG_BLOCK_LENGTH("wrong array shape (block length = {0}, expected {1})"), + WRONG_NUMBER_OF_POINTS("{0} points are required, got only {1}"), + NUMBER_OF_POINTS("number of points ({0})"), /* keep */ + ZERO_DENOMINATOR("denominator must be different from 0"), /* keep */ + ZERO_DENOMINATOR_IN_FRACTION("zero denominator in fraction {0}/{1}"), + ZERO_FRACTION_TO_DIVIDE_BY("the fraction to divide by must not be zero: {0}/{1}"), + ZERO_NORM("zero norm"), + ZERO_NORM_FOR_ROTATION_AXIS("zero norm for rotation axis"), + ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR("zero norm for rotation defining vector"), + ZERO_NOT_ALLOWED("zero not allowed here"); + + // CHECKSTYLE: resume JavadocVariable + // CHECKSTYLE: resume MultipleVariableDeclarations + + + /** Source English format. */ + private final String sourceFormat; + + /** Simple constructor. + * @param sourceFormat source English format to use when no + * localized version is available + */ + LocalizedFormats(final String sourceFormat) { + this.sourceFormat = sourceFormat; + } + + /** {@inheritDoc} */ + public String getSourceString() { + return sourceFormat; + } + + /** {@inheritDoc} */ + public String getLocalizedString(final Locale locale) { + try { + final String path = LocalizedFormats.class.getName().replaceAll("\\.", "/"); + ResourceBundle bundle = + ResourceBundle.getBundle("assets/" + path, locale); + if (bundle.getLocale().getLanguage().equals(locale.getLanguage())) { + // the value of the resource is the translated format + return bundle.getString(toString()); + } + + } catch (MissingResourceException mre) { // NOPMD + // do nothing here + } + + // either the locale is not supported or the resource is unknown + // don't translate and fall back to using the source format + return sourceFormat; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/package-info.java new file mode 100644 index 000000000..982bcae30 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/exception/util/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Classes supporting exception localization. + * + */ +package com.fr.third.org.apache.commons.math3.exception.util; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultMeasurementModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultMeasurementModel.java new file mode 100644 index 000000000..1d2b42f51 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultMeasurementModel.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 com.fr.third.org.apache.commons.math3.filter; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Default implementation of a {@link MeasurementModel} for the use with a {@link KalmanFilter}. + * + * @since 3.0 + */ +public class DefaultMeasurementModel implements MeasurementModel { + + /** + * The measurement matrix, used to associate the measurement vector to the + * internal state estimation vector. + */ + private RealMatrix measurementMatrix; + + /** + * The measurement noise covariance matrix. + */ + private RealMatrix measurementNoise; + + /** + * Create a new {@link MeasurementModel}, taking double arrays as input parameters for the + * respective measurement matrix and noise. + * + * @param measMatrix + * the measurement matrix + * @param measNoise + * the measurement noise matrix + * @throws NullArgumentException + * if any of the input matrices is {@code null} + * @throws NoDataException + * if any row / column dimension of the input matrices is zero + * @throws DimensionMismatchException + * if any of the input matrices is non-rectangular + */ + public DefaultMeasurementModel(final double[][] measMatrix, final double[][] measNoise) + throws NullArgumentException, NoDataException, DimensionMismatchException { + this(new Array2DRowRealMatrix(measMatrix), new Array2DRowRealMatrix(measNoise)); + } + + /** + * Create a new {@link MeasurementModel}, taking {@link RealMatrix} objects + * as input parameters for the respective measurement matrix and noise. + * + * @param measMatrix the measurement matrix + * @param measNoise the measurement noise matrix + */ + public DefaultMeasurementModel(final RealMatrix measMatrix, final RealMatrix measNoise) { + this.measurementMatrix = measMatrix; + this.measurementNoise = measNoise; + } + + /** {@inheritDoc} */ + public RealMatrix getMeasurementMatrix() { + return measurementMatrix; + } + + /** {@inheritDoc} */ + public RealMatrix getMeasurementNoise() { + return measurementNoise; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultProcessModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultProcessModel.java new file mode 100644 index 000000000..3a9c0c867 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/DefaultProcessModel.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 com.fr.third.org.apache.commons.math3.filter; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * Default implementation of a {@link ProcessModel} for the use with a {@link KalmanFilter}. + * + * @since 3.0 + */ +public class DefaultProcessModel implements ProcessModel { + /** + * The state transition matrix, used to advance the internal state estimation each time-step. + */ + private RealMatrix stateTransitionMatrix; + + /** + * The control matrix, used to integrate a control input into the state estimation. + */ + private RealMatrix controlMatrix; + + /** The process noise covariance matrix. */ + private RealMatrix processNoiseCovMatrix; + + /** The initial state estimation of the observed process. */ + private RealVector initialStateEstimateVector; + + /** The initial error covariance matrix of the observed process. */ + private RealMatrix initialErrorCovMatrix; + + /** + * Create a new {@link ProcessModel}, taking double arrays as input parameters. + * + * @param stateTransition + * the state transition matrix + * @param control + * the control matrix + * @param processNoise + * the process noise matrix + * @param initialStateEstimate + * the initial state estimate vector + * @param initialErrorCovariance + * the initial error covariance matrix + * @throws NullArgumentException + * if any of the input arrays is {@code null} + * @throws NoDataException + * if any row / column dimension of the input matrices is zero + * @throws DimensionMismatchException + * if any of the input matrices is non-rectangular + */ + public DefaultProcessModel(final double[][] stateTransition, + final double[][] control, + final double[][] processNoise, + final double[] initialStateEstimate, + final double[][] initialErrorCovariance) + throws NullArgumentException, NoDataException, DimensionMismatchException { + + this(new Array2DRowRealMatrix(stateTransition), + new Array2DRowRealMatrix(control), + new Array2DRowRealMatrix(processNoise), + new ArrayRealVector(initialStateEstimate), + new Array2DRowRealMatrix(initialErrorCovariance)); + } + + /** + * Create a new {@link ProcessModel}, taking double arrays as input parameters. + *

+ * The initial state estimate and error covariance are omitted and will be initialized by the + * {@link KalmanFilter} to default values. + * + * @param stateTransition + * the state transition matrix + * @param control + * the control matrix + * @param processNoise + * the process noise matrix + * @throws NullArgumentException + * if any of the input arrays is {@code null} + * @throws NoDataException + * if any row / column dimension of the input matrices is zero + * @throws DimensionMismatchException + * if any of the input matrices is non-rectangular + */ + public DefaultProcessModel(final double[][] stateTransition, + final double[][] control, + final double[][] processNoise) + throws NullArgumentException, NoDataException, DimensionMismatchException { + + this(new Array2DRowRealMatrix(stateTransition), + new Array2DRowRealMatrix(control), + new Array2DRowRealMatrix(processNoise), null, null); + } + + /** + * Create a new {@link ProcessModel}, taking double arrays as input parameters. + * + * @param stateTransition + * the state transition matrix + * @param control + * the control matrix + * @param processNoise + * the process noise matrix + * @param initialStateEstimate + * the initial state estimate vector + * @param initialErrorCovariance + * the initial error covariance matrix + */ + public DefaultProcessModel(final RealMatrix stateTransition, + final RealMatrix control, + final RealMatrix processNoise, + final RealVector initialStateEstimate, + final RealMatrix initialErrorCovariance) { + this.stateTransitionMatrix = stateTransition; + this.controlMatrix = control; + this.processNoiseCovMatrix = processNoise; + this.initialStateEstimateVector = initialStateEstimate; + this.initialErrorCovMatrix = initialErrorCovariance; + } + + /** {@inheritDoc} */ + public RealMatrix getStateTransitionMatrix() { + return stateTransitionMatrix; + } + + /** {@inheritDoc} */ + public RealMatrix getControlMatrix() { + return controlMatrix; + } + + /** {@inheritDoc} */ + public RealMatrix getProcessNoise() { + return processNoiseCovMatrix; + } + + /** {@inheritDoc} */ + public RealVector getInitialStateEstimate() { + return initialStateEstimateVector; + } + + /** {@inheritDoc} */ + public RealMatrix getInitialErrorCovariance() { + return initialErrorCovMatrix; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/KalmanFilter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/KalmanFilter.java new file mode 100644 index 000000000..54318072a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/KalmanFilter.java @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.filter; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.CholeskyDecomposition; +import com.fr.third.org.apache.commons.math3.linear.MatrixDimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.NonSquareMatrixException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of a Kalman filter to estimate the state xk + * of a discrete-time controlled process that is governed by the linear + * stochastic difference equation: + * + *

+ * xk = Axk-1 + Buk-1 + wk-1
+ * 
+ * + * with a measurement xk that is + * + *
+ * zk = Hxk + vk.
+ * 
+ * + *

+ * The random variables wk and vk represent + * the process and measurement noise and are assumed to be independent of each + * other and distributed with normal probability (white noise). + *

+ * The Kalman filter cycle involves the following steps: + *

    + *
  1. predict: project the current state estimate ahead in time
  2. + *
  3. correct: adjust the projected estimate by an actual measurement
  4. + *
+ *

+ * The Kalman filter is initialized with a {@link ProcessModel} and a + * {@link MeasurementModel}, which contain the corresponding transformation and + * noise covariance matrices. The parameter names used in the respective models + * correspond to the following names commonly used in the mathematical + * literature: + *

    + *
  • A - state transition matrix
  • + *
  • B - control input matrix
  • + *
  • H - measurement matrix
  • + *
  • Q - process noise covariance matrix
  • + *
  • R - measurement noise covariance matrix
  • + *
  • P - error covariance matrix
  • + *
+ * + * @see Kalman filter + * resources + * @see An + * introduction to the Kalman filter by Greg Welch and Gary Bishop + * @see + * Kalman filter example by Dan Simon + * @see ProcessModel + * @see MeasurementModel + * @since 3.0 + */ +public class KalmanFilter { + /** The process model used by this filter instance. */ + private final ProcessModel processModel; + /** The measurement model used by this filter instance. */ + private final MeasurementModel measurementModel; + /** The transition matrix, equivalent to A. */ + private RealMatrix transitionMatrix; + /** The transposed transition matrix. */ + private RealMatrix transitionMatrixT; + /** The control matrix, equivalent to B. */ + private RealMatrix controlMatrix; + /** The measurement matrix, equivalent to H. */ + private RealMatrix measurementMatrix; + /** The transposed measurement matrix. */ + private RealMatrix measurementMatrixT; + /** The internal state estimation vector, equivalent to x hat. */ + private RealVector stateEstimation; + /** The error covariance matrix, equivalent to P. */ + private RealMatrix errorCovariance; + + /** + * Creates a new Kalman filter with the given process and measurement models. + * + * @param process + * the model defining the underlying process dynamics + * @param measurement + * the model defining the given measurement characteristics + * @throws NullArgumentException + * if any of the given inputs is null (except for the control matrix) + * @throws NonSquareMatrixException + * if the transition matrix is non square + * @throws DimensionMismatchException + * if the column dimension of the transition matrix does not match the dimension of the + * initial state estimation vector + * @throws MatrixDimensionMismatchException + * if the matrix dimensions do not fit together + */ + public KalmanFilter(final ProcessModel process, final MeasurementModel measurement) + throws NullArgumentException, NonSquareMatrixException, DimensionMismatchException, + MatrixDimensionMismatchException { + + MathUtils.checkNotNull(process); + MathUtils.checkNotNull(measurement); + + this.processModel = process; + this.measurementModel = measurement; + + transitionMatrix = processModel.getStateTransitionMatrix(); + MathUtils.checkNotNull(transitionMatrix); + transitionMatrixT = transitionMatrix.transpose(); + + // create an empty matrix if no control matrix was given + if (processModel.getControlMatrix() == null) { + controlMatrix = new Array2DRowRealMatrix(); + } else { + controlMatrix = processModel.getControlMatrix(); + } + + measurementMatrix = measurementModel.getMeasurementMatrix(); + MathUtils.checkNotNull(measurementMatrix); + measurementMatrixT = measurementMatrix.transpose(); + + // check that the process and measurement noise matrices are not null + // they will be directly accessed from the model as they may change + // over time + RealMatrix processNoise = processModel.getProcessNoise(); + MathUtils.checkNotNull(processNoise); + RealMatrix measNoise = measurementModel.getMeasurementNoise(); + MathUtils.checkNotNull(measNoise); + + // set the initial state estimate to a zero vector if it is not + // available from the process model + if (processModel.getInitialStateEstimate() == null) { + stateEstimation = new ArrayRealVector(transitionMatrix.getColumnDimension()); + } else { + stateEstimation = processModel.getInitialStateEstimate(); + } + + if (transitionMatrix.getColumnDimension() != stateEstimation.getDimension()) { + throw new DimensionMismatchException(transitionMatrix.getColumnDimension(), + stateEstimation.getDimension()); + } + + // initialize the error covariance to the process noise if it is not + // available from the process model + if (processModel.getInitialErrorCovariance() == null) { + errorCovariance = processNoise.copy(); + } else { + errorCovariance = processModel.getInitialErrorCovariance(); + } + + // sanity checks, the control matrix B may be null + + // A must be a square matrix + if (!transitionMatrix.isSquare()) { + throw new NonSquareMatrixException( + transitionMatrix.getRowDimension(), + transitionMatrix.getColumnDimension()); + } + + // row dimension of B must be equal to A + // if no control matrix is available, the row and column dimension will be 0 + if (controlMatrix != null && + controlMatrix.getRowDimension() > 0 && + controlMatrix.getColumnDimension() > 0 && + controlMatrix.getRowDimension() != transitionMatrix.getRowDimension()) { + throw new MatrixDimensionMismatchException(controlMatrix.getRowDimension(), + controlMatrix.getColumnDimension(), + transitionMatrix.getRowDimension(), + controlMatrix.getColumnDimension()); + } + + // Q must be equal to A + MatrixUtils.checkAdditionCompatible(transitionMatrix, processNoise); + + // column dimension of H must be equal to row dimension of A + if (measurementMatrix.getColumnDimension() != transitionMatrix.getRowDimension()) { + throw new MatrixDimensionMismatchException(measurementMatrix.getRowDimension(), + measurementMatrix.getColumnDimension(), + measurementMatrix.getRowDimension(), + transitionMatrix.getRowDimension()); + } + + // row dimension of R must be equal to row dimension of H + if (measNoise.getRowDimension() != measurementMatrix.getRowDimension()) { + throw new MatrixDimensionMismatchException(measNoise.getRowDimension(), + measNoise.getColumnDimension(), + measurementMatrix.getRowDimension(), + measNoise.getColumnDimension()); + } + } + + /** + * Returns the dimension of the state estimation vector. + * + * @return the state dimension + */ + public int getStateDimension() { + return stateEstimation.getDimension(); + } + + /** + * Returns the dimension of the measurement vector. + * + * @return the measurement vector dimension + */ + public int getMeasurementDimension() { + return measurementMatrix.getRowDimension(); + } + + /** + * Returns the current state estimation vector. + * + * @return the state estimation vector + */ + public double[] getStateEstimation() { + return stateEstimation.toArray(); + } + + /** + * Returns a copy of the current state estimation vector. + * + * @return the state estimation vector + */ + public RealVector getStateEstimationVector() { + return stateEstimation.copy(); + } + + /** + * Returns the current error covariance matrix. + * + * @return the error covariance matrix + */ + public double[][] getErrorCovariance() { + return errorCovariance.getData(); + } + + /** + * Returns a copy of the current error covariance matrix. + * + * @return the error covariance matrix + */ + public RealMatrix getErrorCovarianceMatrix() { + return errorCovariance.copy(); + } + + /** + * Predict the internal state estimation one time step ahead. + */ + public void predict() { + predict((RealVector) null); + } + + /** + * Predict the internal state estimation one time step ahead. + * + * @param u + * the control vector + * @throws DimensionMismatchException + * if the dimension of the control vector does not fit + */ + public void predict(final double[] u) throws DimensionMismatchException { + predict(new ArrayRealVector(u, false)); + } + + /** + * Predict the internal state estimation one time step ahead. + * + * @param u + * the control vector + * @throws DimensionMismatchException + * if the dimension of the control vector does not match + */ + public void predict(final RealVector u) throws DimensionMismatchException { + // sanity checks + if (u != null && + u.getDimension() != controlMatrix.getColumnDimension()) { + throw new DimensionMismatchException(u.getDimension(), + controlMatrix.getColumnDimension()); + } + + // project the state estimation ahead (a priori state) + // xHat(k)- = A * xHat(k-1) + B * u(k-1) + stateEstimation = transitionMatrix.operate(stateEstimation); + + // add control input if it is available + if (u != null) { + stateEstimation = stateEstimation.add(controlMatrix.operate(u)); + } + + // project the error covariance ahead + // P(k)- = A * P(k-1) * A' + Q + errorCovariance = transitionMatrix.multiply(errorCovariance) + .multiply(transitionMatrixT) + .add(processModel.getProcessNoise()); + } + + /** + * Correct the current state estimate with an actual measurement. + * + * @param z + * the measurement vector + * @throws NullArgumentException + * if the measurement vector is {@code null} + * @throws DimensionMismatchException + * if the dimension of the measurement vector does not fit + * @throws SingularMatrixException + * if the covariance matrix could not be inverted + */ + public void correct(final double[] z) + throws NullArgumentException, DimensionMismatchException, SingularMatrixException { + correct(new ArrayRealVector(z, false)); + } + + /** + * Correct the current state estimate with an actual measurement. + * + * @param z + * the measurement vector + * @throws NullArgumentException + * if the measurement vector is {@code null} + * @throws DimensionMismatchException + * if the dimension of the measurement vector does not fit + * @throws SingularMatrixException + * if the covariance matrix could not be inverted + */ + public void correct(final RealVector z) + throws NullArgumentException, DimensionMismatchException, SingularMatrixException { + + // sanity checks + MathUtils.checkNotNull(z); + if (z.getDimension() != measurementMatrix.getRowDimension()) { + throw new DimensionMismatchException(z.getDimension(), + measurementMatrix.getRowDimension()); + } + + // S = H * P(k) * H' + R + RealMatrix s = measurementMatrix.multiply(errorCovariance) + .multiply(measurementMatrixT) + .add(measurementModel.getMeasurementNoise()); + + // Inn = z(k) - H * xHat(k)- + RealVector innovation = z.subtract(measurementMatrix.operate(stateEstimation)); + + // calculate gain matrix + // K(k) = P(k)- * H' * (H * P(k)- * H' + R)^-1 + // K(k) = P(k)- * H' * S^-1 + + // instead of calculating the inverse of S we can rearrange the formula, + // and then solve the linear equation A x X = B with A = S', X = K' and B = (H * P)' + + // K(k) * S = P(k)- * H' + // S' * K(k)' = H * P(k)-' + RealMatrix kalmanGain = new CholeskyDecomposition(s).getSolver() + .solve(measurementMatrix.multiply(errorCovariance.transpose())) + .transpose(); + + // update estimate with measurement z(k) + // xHat(k) = xHat(k)- + K * Inn + stateEstimation = stateEstimation.add(kalmanGain.operate(innovation)); + + // update covariance of prediction error + // P(k) = (I - K * H) * P(k)- + RealMatrix identity = MatrixUtils.createRealIdentityMatrix(kalmanGain.getRowDimension()); + errorCovariance = identity.subtract(kalmanGain.multiply(measurementMatrix)).multiply(errorCovariance); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/MeasurementModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/MeasurementModel.java new file mode 100644 index 000000000..b6219a95f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/MeasurementModel.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 com.fr.third.org.apache.commons.math3.filter; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * Defines the measurement model for the use with a {@link KalmanFilter}. + * + * @since 3.0 + */ +public interface MeasurementModel { + /** + * Returns the measurement matrix. + * + * @return the measurement matrix + */ + RealMatrix getMeasurementMatrix(); + + /** + * Returns the measurement noise matrix. This method is called by the {@link KalmanFilter} every + * correction step, so implementations of this interface may return a modified measurement noise + * depending on the current iteration step. + * + * @return the measurement noise matrix + * @see KalmanFilter#correct(double[]) + * @see KalmanFilter#correct(RealVector) + */ + RealMatrix getMeasurementNoise(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/ProcessModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/ProcessModel.java new file mode 100644 index 000000000..fc5660a7f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/ProcessModel.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.filter; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * Defines the process dynamics model for the use with a {@link KalmanFilter}. + * + * @since 3.0 + */ +public interface ProcessModel { + /** + * Returns the state transition matrix. + * + * @return the state transition matrix + */ + RealMatrix getStateTransitionMatrix(); + + /** + * Returns the control matrix. + * + * @return the control matrix + */ + RealMatrix getControlMatrix(); + + /** + * Returns the process noise matrix. This method is called by the {@link KalmanFilter} every + * prediction step, so implementations of this interface may return a modified process noise + * depending on the current iteration step. + * + * @return the process noise matrix + * @see KalmanFilter#predict() + * @see KalmanFilter#predict(double[]) + * @see KalmanFilter#predict(RealVector) + */ + RealMatrix getProcessNoise(); + + /** + * Returns the initial state estimation vector. + *

+ * Note: if the return value is zero, the Kalman filter will initialize the + * state estimation with a zero vector. + * + * @return the initial state estimation vector + */ + RealVector getInitialStateEstimate(); + + /** + * Returns the initial error covariance matrix. + *

+ * Note: if the return value is zero, the Kalman filter will initialize the + * error covariance with the process noise matrix. + * + * @return the initial error covariance matrix + */ + RealMatrix getInitialErrorCovariance(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/package-info.java new file mode 100644 index 000000000..36bdeccea --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/filter/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Implementations of common discrete-time linear filters. + */ +package com.fr.third.org.apache.commons.math3.filter; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/AbstractCurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/AbstractCurveFitter.java new file mode 100644 index 000000000..880fab4ad --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/AbstractCurveFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer; + +/** + * Base class that contains common code for fitting parametric univariate + * real functions y = f(pi;x), where {@code x} is + * the independent variable and the pi are the + * parameters. + *
+ * A fitter will find the optimal values of the parameters by + * fitting the curve so it remains very close to a set of + * {@code N} observed points (xk, yk), + * {@code 0 <= k < N}. + *
+ * An algorithm usually performs the fit by finding the parameter + * values that minimizes the objective function + *


+ *  ∑yk - f(xk)2,
+ * 
+ * which is actually a least-squares problem. + * This class contains boilerplate code for calling the + * {@link #fit(Collection)} method for obtaining the parameters. + * The problem setup, such as the choice of optimization algorithm + * for fitting a specific function is delegated to subclasses. + * + * @since 3.3 + */ +public abstract class AbstractCurveFitter { + /** + * Fits a curve. + * This method computes the coefficients of the curve that best + * fit the sample of observed points. + * + * @param points Observations. + * @return the fitted parameters. + */ + public double[] fit(Collection points) { + // Perform the fit. + return getOptimizer().optimize(getProblem(points)).getPoint().toArray(); + } + + /** + * Creates an optimizer set up to fit the appropriate curve. + *

+ * The default implementation uses a {@link LevenbergMarquardtOptimizer + * Levenberg-Marquardt} optimizer. + *

+ * @return the optimizer to use for fitting the curve to the + * given {@code points}. + */ + protected LeastSquaresOptimizer getOptimizer() { + return new LevenbergMarquardtOptimizer(); + } + + /** + * Creates a least squares problem corresponding to the appropriate curve. + * + * @param points Sample points. + * @return the least squares problem to use for fitting the curve to the + * given {@code points}. + */ + protected abstract LeastSquaresProblem getProblem(Collection points); + + /** + * Vector function for computing function theoretical values. + */ + protected static class TheoreticalValuesFunction { + /** Function to fit. */ + private final ParametricUnivariateFunction f; + /** Observations. */ + private final double[] points; + + /** + * @param f function to fit. + * @param observations Observations. + */ + public TheoreticalValuesFunction(final ParametricUnivariateFunction f, + final Collection observations) { + this.f = f; + + final int len = observations.size(); + this.points = new double[len]; + int i = 0; + for (WeightedObservedPoint obs : observations) { + this.points[i++] = obs.getX(); + } + } + + /** + * @return the model function values. + */ + public MultivariateVectorFunction getModelFunction() { + return new MultivariateVectorFunction() { + /** {@inheritDoc} */ + public double[] value(double[] p) { + final int len = points.length; + final double[] values = new double[len]; + for (int i = 0; i < len; i++) { + values[i] = f.value(points[i], p); + } + + return values; + } + }; + } + + /** + * @return the model function Jacobian. + */ + public MultivariateMatrixFunction getModelFunctionJacobian() { + return new MultivariateMatrixFunction() { + /** {@inheritDoc} */ + public double[][] value(double[] p) { + final int len = points.length; + final double[][] jacobian = new double[len][]; + for (int i = 0; i < len; i++) { + jacobian[i] = f.gradient(points[i], p); + } + return jacobian; + } + }; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/CurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/CurveFitter.java new file mode 100644 index 000000000..f1a159b70 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/CurveFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.MaxEval; +import com.fr.third.org.apache.commons.math3.optim.InitialGuess; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.ModelFunction; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.ModelFunctionJacobian; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.Target; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.Weight; + +/** + * Fitter for parametric univariate real functions y = f(x). + *
+ * When a univariate real function y = f(x) does depend on some + * unknown parameters p0, p1 ... pn-1, + * this class can be used to find these parameters. It does this + * by fitting the curve so it remains very close to a set of + * observed points (x0, y0), (x1, + * y1) ... (xk-1, yk-1). This fitting + * is done by finding the parameters values that minimizes the objective + * function ∑(yi-f(xi))2. This is + * really a least squares problem. + * + * @param Function to use for the fit. + * + * @since 2.0 + * @deprecated As of 3.3. Please use {@link AbstractCurveFitter} and + * {@link WeightedObservedPoints} instead. + */ +@Deprecated +public class CurveFitter { + /** Optimizer to use for the fitting. */ + private final MultivariateVectorOptimizer optimizer; + /** Observed points. */ + private final List observations; + + /** + * Simple constructor. + * + * @param optimizer Optimizer to use for the fitting. + * @since 3.1 + */ + public CurveFitter(final MultivariateVectorOptimizer optimizer) { + this.optimizer = optimizer; + observations = new ArrayList(); + } + + /** Add an observed (x,y) point to the sample with unit weight. + *

Calling this method is equivalent to call + * {@code addObservedPoint(1.0, x, y)}.

+ * @param x abscissa of the point + * @param y observed value of the point at x, after fitting we should + * have f(x) as close as possible to this value + * @see #addObservedPoint(double, double, double) + * @see #addObservedPoint(WeightedObservedPoint) + * @see #getObservations() + */ + public void addObservedPoint(double x, double y) { + addObservedPoint(1.0, x, y); + } + + /** Add an observed weighted (x,y) point to the sample. + * @param weight weight of the observed point in the fit + * @param x abscissa of the point + * @param y observed value of the point at x, after fitting we should + * have f(x) as close as possible to this value + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(WeightedObservedPoint) + * @see #getObservations() + */ + public void addObservedPoint(double weight, double x, double y) { + observations.add(new WeightedObservedPoint(weight, x, y)); + } + + /** Add an observed weighted (x,y) point to the sample. + * @param observed observed point to add + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(double, double, double) + * @see #getObservations() + */ + public void addObservedPoint(WeightedObservedPoint observed) { + observations.add(observed); + } + + /** Get the observed points. + * @return observed points + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(double, double, double) + * @see #addObservedPoint(WeightedObservedPoint) + */ + public WeightedObservedPoint[] getObservations() { + return observations.toArray(new WeightedObservedPoint[observations.size()]); + } + + /** + * Remove all observations. + */ + public void clearObservations() { + observations.clear(); + } + + /** + * Fit a curve. + * This method compute the coefficients of the curve that best + * fit the sample of observed points previously given through calls + * to the {@link #addObservedPoint(WeightedObservedPoint) + * addObservedPoint} method. + * + * @param f parametric function to fit. + * @param initialGuess first guess of the function parameters. + * @return the fitted parameters. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + */ + public double[] fit(T f, final double[] initialGuess) { + return fit(Integer.MAX_VALUE, f, initialGuess); + } + + /** + * Fit a curve. + * This method compute the coefficients of the curve that best + * fit the sample of observed points previously given through calls + * to the {@link #addObservedPoint(WeightedObservedPoint) + * addObservedPoint} method. + * + * @param f parametric function to fit. + * @param initialGuess first guess of the function parameters. + * @param maxEval Maximum number of function evaluations. + * @return the fitted parameters. + * @throws TooManyEvaluationsException + * if the number of allowed evaluations is exceeded. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @since 3.0 + */ + public double[] fit(int maxEval, T f, + final double[] initialGuess) { + // Prepare least squares problem. + double[] target = new double[observations.size()]; + double[] weights = new double[observations.size()]; + int i = 0; + for (WeightedObservedPoint point : observations) { + target[i] = point.getY(); + weights[i] = point.getWeight(); + ++i; + } + + // Input to the optimizer: the model and its Jacobian. + final TheoreticalValuesFunction model = new TheoreticalValuesFunction(f); + + // Perform the fit. + final PointVectorValuePair optimum + = optimizer.optimize(new MaxEval(maxEval), + model.getModelFunction(), + model.getModelFunctionJacobian(), + new Target(target), + new Weight(weights), + new InitialGuess(initialGuess)); + // Extract the coefficients. + return optimum.getPointRef(); + } + + /** Vectorial function computing function theoretical values. */ + private class TheoreticalValuesFunction { + /** Function to fit. */ + private final ParametricUnivariateFunction f; + + /** + * @param f function to fit. + */ + TheoreticalValuesFunction(final ParametricUnivariateFunction f) { + this.f = f; + } + + /** + * @return the model function values. + */ + public ModelFunction getModelFunction() { + return new ModelFunction(new MultivariateVectorFunction() { + /** {@inheritDoc} */ + public double[] value(double[] point) { + // compute the residuals + final double[] values = new double[observations.size()]; + int i = 0; + for (WeightedObservedPoint observed : observations) { + values[i++] = f.value(observed.getX(), point); + } + + return values; + } + }); + } + + /** + * @return the model function Jacobian. + */ + public ModelFunctionJacobian getModelFunctionJacobian() { + return new ModelFunctionJacobian(new MultivariateMatrixFunction() { + /** {@inheritDoc} */ + public double[][] value(double[] point) { + final double[][] jacobian = new double[observations.size()][]; + int i = 0; + for (WeightedObservedPoint observed : observations) { + jacobian[i++] = f.gradient(observed.getX(), point); + } + return jacobian; + } + }); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianCurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianCurveFitter.java new file mode 100644 index 000000000..390e0ed8c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianCurveFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.analysis.function.Gaussian; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Fits points to a {@link + * Gaussian.Parametric Gaussian} + * function. + *
+ * The {@link #withStartPoint(double[]) initial guess values} must be passed + * in the following order: + *
    + *
  • Normalization
  • + *
  • Mean
  • + *
  • Sigma
  • + *
+ * The optimal values will be returned in the same order. + * + *

+ * Usage example: + *

+ *   WeightedObservedPoints obs = new WeightedObservedPoints();
+ *   obs.add(4.0254623,  531026.0);
+ *   obs.add(4.03128248, 984167.0);
+ *   obs.add(4.03839603, 1887233.0);
+ *   obs.add(4.04421621, 2687152.0);
+ *   obs.add(4.05132976, 3461228.0);
+ *   obs.add(4.05326982, 3580526.0);
+ *   obs.add(4.05779662, 3439750.0);
+ *   obs.add(4.0636168,  2877648.0);
+ *   obs.add(4.06943698, 2175960.0);
+ *   obs.add(4.07525716, 1447024.0);
+ *   obs.add(4.08237071, 717104.0);
+ *   obs.add(4.08366408, 620014.0);
+ *   double[] parameters = GaussianCurveFitter.create().fit(obs.toList());
+ * 
+ * + * @since 3.3 + */ +public class GaussianCurveFitter extends AbstractCurveFitter { + /** Parametric function to be fitted. */ + private static final Gaussian.Parametric FUNCTION = new Gaussian.Parametric() { + /** {@inheritDoc} */ + @Override + public double value(double x, double ... p) { + double v = Double.POSITIVE_INFINITY; + try { + v = super.value(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + + /** {@inheritDoc} */ + @Override + public double[] gradient(double x, double ... p) { + double[] v = { Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY }; + try { + v = super.gradient(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + }; + /** Initial guess. */ + private final double[] initialGuess; + /** Maximum number of iterations of the optimization algorithm. */ + private final int maxIter; + + /** + * Contructor used by the factory methods. + * + * @param initialGuess Initial guess. If set to {@code null}, the initial guess + * will be estimated using the {@link ParameterGuesser}. + * @param maxIter Maximum number of iterations of the optimization algorithm. + */ + private GaussianCurveFitter(double[] initialGuess, + int maxIter) { + this.initialGuess = initialGuess; + this.maxIter = maxIter; + } + + /** + * Creates a default curve fitter. + * The initial guess for the parameters will be {@link ParameterGuesser} + * computed automatically, and the maximum number of iterations of the + * optimization algorithm is set to {@link Integer#MAX_VALUE}. + * + * @return a curve fitter. + * + * @see #withStartPoint(double[]) + * @see #withMaxIterations(int) + */ + public static GaussianCurveFitter create() { + return new GaussianCurveFitter(null, Integer.MAX_VALUE); + } + + /** + * Configure the start point (initial guess). + * @param newStart new start point (initial guess) + * @return a new instance. + */ + public GaussianCurveFitter withStartPoint(double[] newStart) { + return new GaussianCurveFitter(newStart.clone(), + maxIter); + } + + /** + * Configure the maximum number of iterations. + * @param newMaxIter maximum number of iterations + * @return a new instance. + */ + public GaussianCurveFitter withMaxIterations(int newMaxIter) { + return new GaussianCurveFitter(initialGuess, + newMaxIter); + } + + /** {@inheritDoc} */ + @Override + protected LeastSquaresProblem getProblem(Collection observations) { + + // Prepare least-squares problem. + final int len = observations.size(); + final double[] target = new double[len]; + final double[] weights = new double[len]; + + int i = 0; + for (WeightedObservedPoint obs : observations) { + target[i] = obs.getY(); + weights[i] = obs.getWeight(); + ++i; + } + + final AbstractCurveFitter.TheoreticalValuesFunction model = + new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, observations); + + final double[] startPoint = initialGuess != null ? + initialGuess : + // Compute estimation. + new ParameterGuesser(observations).guess(); + + // Return a new least squares problem set up to fit a Gaussian curve to the + // observed points. + return new LeastSquaresBuilder(). + maxEvaluations(Integer.MAX_VALUE). + maxIterations(maxIter). + start(startPoint). + target(target). + weight(new DiagonalMatrix(weights)). + model(model.getModelFunction(), model.getModelFunctionJacobian()). + build(); + + } + + /** + * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma} + * of a {@link Gaussian.Parametric} + * based on the specified observed points. + */ + public static class ParameterGuesser { + /** Normalization factor. */ + private final double norm; + /** Mean. */ + private final double mean; + /** Standard deviation. */ + private final double sigma; + + /** + * Constructs instance with the specified observed points. + * + * @param observations Observed points from which to guess the + * parameters of the Gaussian. + * @throws NullArgumentException if {@code observations} is + * {@code null}. + * @throws NumberIsTooSmallException if there are less than 3 + * observations. + */ + public ParameterGuesser(Collection observations) { + if (observations == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + if (observations.size() < 3) { + throw new NumberIsTooSmallException(observations.size(), 3, true); + } + + final List sorted = sortObservations(observations); + final double[] params = basicGuess(sorted.toArray(new WeightedObservedPoint[0])); + + norm = params[0]; + mean = params[1]; + sigma = params[2]; + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
    + *
  • Normalization factor
  • + *
  • Mean
  • + *
  • Standard deviation
  • + *
+ */ + public double[] guess() { + return new double[] { norm, mean, sigma }; + } + + /** + * Sort the observations. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private List sortObservations(Collection unsorted) { + final List observations = new ArrayList(unsorted); + + final Comparator cmp = new Comparator() { + /** {@inheritDoc} */ + public int compare(WeightedObservedPoint p1, + WeightedObservedPoint p2) { + if (p1 == null && p2 == null) { + return 0; + } + if (p1 == null) { + return -1; + } + if (p2 == null) { + return 1; + } + final int cmpX = Double.compare(p1.getX(), p2.getX()); + if (cmpX < 0) { + return -1; + } + if (cmpX > 0) { + return 1; + } + final int cmpY = Double.compare(p1.getY(), p2.getY()); + if (cmpY < 0) { + return -1; + } + if (cmpY > 0) { + return 1; + } + final int cmpW = Double.compare(p1.getWeight(), p2.getWeight()); + if (cmpW < 0) { + return -1; + } + if (cmpW > 0) { + return 1; + } + return 0; + } + }; + + Collections.sort(observations, cmp); + return observations; + } + + /** + * Guesses the parameters based on the specified observed points. + * + * @param points Observed points, sorted. + * @return the guessed parameters (normalization factor, mean and + * sigma). + */ + private double[] basicGuess(WeightedObservedPoint[] points) { + final int maxYIdx = findMaxY(points); + final double n = points[maxYIdx].getY(); + final double m = points[maxYIdx].getX(); + + double fwhmApprox; + try { + final double halfY = n + ((m - n) / 2); + final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY); + final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY); + fwhmApprox = fwhmX2 - fwhmX1; + } catch (OutOfRangeException e) { + // TODO: Exceptions should not be used for flow control. + fwhmApprox = points[points.length - 1].getX() - points[0].getX(); + } + final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2))); + + return new double[] { n, m, s }; + } + + /** + * Finds index of point in specified points with the largest Y. + * + * @param points Points to search. + * @return the index in specified points array. + */ + private int findMaxY(WeightedObservedPoint[] points) { + int maxYIdx = 0; + for (int i = 1; i < points.length; i++) { + if (points[i].getY() > points[maxYIdx].getY()) { + maxYIdx = i; + } + } + return maxYIdx; + } + + /** + * Interpolates using the specified points to determine X at the + * specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start the search for + * interpolation bounds points. + * @param idxStep Index step for searching interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the value of X for the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private double interpolateXAtY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + final WeightedObservedPoint[] twoPoints + = getInterpolationPointsForY(points, startIdx, idxStep, y); + final WeightedObservedPoint p1 = twoPoints[0]; + final WeightedObservedPoint p2 = twoPoints[1]; + if (p1.getY() == y) { + return p1.getX(); + } + if (p2.getY() == y) { + return p2.getX(); + } + return p1.getX() + (((y - p1.getY()) * (p2.getX() - p1.getX())) / + (p2.getY() - p1.getY())); + } + + /** + * Gets the two bounding interpolation points from the specified points + * suitable for determining X at the specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start search for + * interpolation bounds points. + * @param idxStep Index step for search for interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the array containing two points suitable for determining X at + * the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private WeightedObservedPoint[] getInterpolationPointsForY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + for (int i = startIdx; + idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length; + i += idxStep) { + final WeightedObservedPoint p1 = points[i]; + final WeightedObservedPoint p2 = points[i + idxStep]; + if (isBetween(y, p1.getY(), p2.getY())) { + if (idxStep < 0) { + return new WeightedObservedPoint[] { p2, p1 }; + } else { + return new WeightedObservedPoint[] { p1, p2 }; + } + } + } + + // Boundaries are replaced by dummy values because the raised + // exception is caught and the message never displayed. + // TODO: Exceptions should not be used for flow control. + throw new OutOfRangeException(y, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY); + } + + /** + * Determines whether a value is between two other values. + * + * @param value Value to test whether it is between {@code boundary1} + * and {@code boundary2}. + * @param boundary1 One end of the range. + * @param boundary2 Other end of the range. + * @return {@code true} if {@code value} is between {@code boundary1} and + * {@code boundary2} (inclusive), {@code false} otherwise. + */ + private boolean isBetween(double value, + double boundary1, + double boundary2) { + return (value >= boundary1 && value <= boundary2) || + (value >= boundary2 && value <= boundary1); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianFitter.java new file mode 100644 index 000000000..2b30b6945 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/GaussianFitter.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.function.Gaussian; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Fits points to a {@link + * Gaussian.Parametric Gaussian} function. + *

+ * Usage example: + *

+ *   GaussianFitter fitter = new GaussianFitter(
+ *     new LevenbergMarquardtOptimizer());
+ *   fitter.addObservedPoint(4.0254623,  531026.0);
+ *   fitter.addObservedPoint(4.03128248, 984167.0);
+ *   fitter.addObservedPoint(4.03839603, 1887233.0);
+ *   fitter.addObservedPoint(4.04421621, 2687152.0);
+ *   fitter.addObservedPoint(4.05132976, 3461228.0);
+ *   fitter.addObservedPoint(4.05326982, 3580526.0);
+ *   fitter.addObservedPoint(4.05779662, 3439750.0);
+ *   fitter.addObservedPoint(4.0636168,  2877648.0);
+ *   fitter.addObservedPoint(4.06943698, 2175960.0);
+ *   fitter.addObservedPoint(4.07525716, 1447024.0);
+ *   fitter.addObservedPoint(4.08237071, 717104.0);
+ *   fitter.addObservedPoint(4.08366408, 620014.0);
+ *   double[] parameters = fitter.fit();
+ * 
+ * + * @since 2.2 + * @deprecated As of 3.3. Please use {@link GaussianCurveFitter} and + * {@link WeightedObservedPoints} instead. + */ +@Deprecated +public class GaussianFitter extends CurveFitter { + /** + * Constructs an instance using the specified optimizer. + * + * @param optimizer Optimizer to use for the fitting. + */ + public GaussianFitter(MultivariateVectorOptimizer optimizer) { + super(optimizer); + } + + /** + * Fits a Gaussian function to the observed points. + * + * @param initialGuess First guess values in the following order: + *
    + *
  • Norm
  • + *
  • Mean
  • + *
  • Sigma
  • + *
+ * @return the parameters of the Gaussian function that best fits the + * observed points (in the same order as above). + * @since 3.0 + */ + public double[] fit(double[] initialGuess) { + final Gaussian.Parametric f = new Gaussian.Parametric() { + /** {@inheritDoc} */ + @Override + public double value(double x, double ... p) { + double v = Double.POSITIVE_INFINITY; + try { + v = super.value(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + + /** {@inheritDoc} */ + @Override + public double[] gradient(double x, double ... p) { + double[] v = { Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY }; + try { + v = super.gradient(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + }; + + return fit(f, initialGuess); + } + + /** + * Fits a Gaussian function to the observed points. + * + * @return the parameters of the Gaussian function that best fits the + * observed points (in the same order as above). + */ + public double[] fit() { + final double[] guess = (new ParameterGuesser(getObservations())).guess(); + return fit(guess); + } + + /** + * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma} + * of a {@link Gaussian.Parametric} + * based on the specified observed points. + */ + public static class ParameterGuesser { + /** Normalization factor. */ + private final double norm; + /** Mean. */ + private final double mean; + /** Standard deviation. */ + private final double sigma; + + /** + * Constructs instance with the specified observed points. + * + * @param observations Observed points from which to guess the + * parameters of the Gaussian. + * @throws NullArgumentException if {@code observations} is + * {@code null}. + * @throws NumberIsTooSmallException if there are less than 3 + * observations. + */ + public ParameterGuesser(WeightedObservedPoint[] observations) { + if (observations == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + if (observations.length < 3) { + throw new NumberIsTooSmallException(observations.length, 3, true); + } + + final WeightedObservedPoint[] sorted = sortObservations(observations); + final double[] params = basicGuess(sorted); + + norm = params[0]; + mean = params[1]; + sigma = params[2]; + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
    + *
  • Normalization factor
  • + *
  • Mean
  • + *
  • Standard deviation
  • + *
+ */ + public double[] guess() { + return new double[] { norm, mean, sigma }; + } + + /** + * Sort the observations. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) { + final WeightedObservedPoint[] observations = unsorted.clone(); + final Comparator cmp + = new Comparator() { + /** {@inheritDoc} */ + public int compare(WeightedObservedPoint p1, + WeightedObservedPoint p2) { + if (p1 == null && p2 == null) { + return 0; + } + if (p1 == null) { + return -1; + } + if (p2 == null) { + return 1; + } + final int cmpX = Double.compare(p1.getX(), p2.getX()); + if (cmpX < 0) { + return -1; + } + if (cmpX > 0) { + return 1; + } + final int cmpY = Double.compare(p1.getY(), p2.getY()); + if (cmpY < 0) { + return -1; + } + if (cmpY > 0) { + return 1; + } + final int cmpW = Double.compare(p1.getWeight(), p2.getWeight()); + if (cmpW < 0) { + return -1; + } + if (cmpW > 0) { + return 1; + } + return 0; + } + }; + + Arrays.sort(observations, cmp); + return observations; + } + + /** + * Guesses the parameters based on the specified observed points. + * + * @param points Observed points, sorted. + * @return the guessed parameters (normalization factor, mean and + * sigma). + */ + private double[] basicGuess(WeightedObservedPoint[] points) { + final int maxYIdx = findMaxY(points); + final double n = points[maxYIdx].getY(); + final double m = points[maxYIdx].getX(); + + double fwhmApprox; + try { + final double halfY = n + ((m - n) / 2); + final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY); + final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY); + fwhmApprox = fwhmX2 - fwhmX1; + } catch (OutOfRangeException e) { + // TODO: Exceptions should not be used for flow control. + fwhmApprox = points[points.length - 1].getX() - points[0].getX(); + } + final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2))); + + return new double[] { n, m, s }; + } + + /** + * Finds index of point in specified points with the largest Y. + * + * @param points Points to search. + * @return the index in specified points array. + */ + private int findMaxY(WeightedObservedPoint[] points) { + int maxYIdx = 0; + for (int i = 1; i < points.length; i++) { + if (points[i].getY() > points[maxYIdx].getY()) { + maxYIdx = i; + } + } + return maxYIdx; + } + + /** + * Interpolates using the specified points to determine X at the + * specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start the search for + * interpolation bounds points. + * @param idxStep Index step for searching interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the value of X for the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private double interpolateXAtY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + final WeightedObservedPoint[] twoPoints + = getInterpolationPointsForY(points, startIdx, idxStep, y); + final WeightedObservedPoint p1 = twoPoints[0]; + final WeightedObservedPoint p2 = twoPoints[1]; + if (p1.getY() == y) { + return p1.getX(); + } + if (p2.getY() == y) { + return p2.getX(); + } + return p1.getX() + (((y - p1.getY()) * (p2.getX() - p1.getX())) / + (p2.getY() - p1.getY())); + } + + /** + * Gets the two bounding interpolation points from the specified points + * suitable for determining X at the specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start search for + * interpolation bounds points. + * @param idxStep Index step for search for interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the array containing two points suitable for determining X at + * the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private WeightedObservedPoint[] getInterpolationPointsForY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + for (int i = startIdx; + idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length; + i += idxStep) { + final WeightedObservedPoint p1 = points[i]; + final WeightedObservedPoint p2 = points[i + idxStep]; + if (isBetween(y, p1.getY(), p2.getY())) { + if (idxStep < 0) { + return new WeightedObservedPoint[] { p2, p1 }; + } else { + return new WeightedObservedPoint[] { p1, p2 }; + } + } + } + + // Boundaries are replaced by dummy values because the raised + // exception is caught and the message never displayed. + // TODO: Exceptions should not be used for flow control. + throw new OutOfRangeException(y, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY); + } + + /** + * Determines whether a value is between two other values. + * + * @param value Value to test whether it is between {@code boundary1} + * and {@code boundary2}. + * @param boundary1 One end of the range. + * @param boundary2 Other end of the range. + * @return {@code true} if {@code value} is between {@code boundary1} and + * {@code boundary2} (inclusive), {@code false} otherwise. + */ + private boolean isBetween(double value, + double boundary1, + double boundary2) { + return (value >= boundary1 && value <= boundary2) || + (value >= boundary2 && value <= boundary1); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicCurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicCurveFitter.java new file mode 100644 index 000000000..b3e2870be --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicCurveFitter.java @@ -0,0 +1,445 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.analysis.function.HarmonicOscillator; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Fits points to a {@link + * HarmonicOscillator.Parametric harmonic oscillator} + * function. + *
+ * The {@link #withStartPoint(double[]) initial guess values} must be passed + * in the following order: + *
    + *
  • Amplitude
  • + *
  • Angular frequency
  • + *
  • phase
  • + *
+ * The optimal values will be returned in the same order. + * + * @since 3.3 + */ +public class HarmonicCurveFitter extends AbstractCurveFitter { + /** Parametric function to be fitted. */ + private static final HarmonicOscillator.Parametric FUNCTION = new HarmonicOscillator.Parametric(); + /** Initial guess. */ + private final double[] initialGuess; + /** Maximum number of iterations of the optimization algorithm. */ + private final int maxIter; + + /** + * Contructor used by the factory methods. + * + * @param initialGuess Initial guess. If set to {@code null}, the initial guess + * will be estimated using the {@link ParameterGuesser}. + * @param maxIter Maximum number of iterations of the optimization algorithm. + */ + private HarmonicCurveFitter(double[] initialGuess, + int maxIter) { + this.initialGuess = initialGuess; + this.maxIter = maxIter; + } + + /** + * Creates a default curve fitter. + * The initial guess for the parameters will be {@link ParameterGuesser} + * computed automatically, and the maximum number of iterations of the + * optimization algorithm is set to {@link Integer#MAX_VALUE}. + * + * @return a curve fitter. + * + * @see #withStartPoint(double[]) + * @see #withMaxIterations(int) + */ + public static HarmonicCurveFitter create() { + return new HarmonicCurveFitter(null, Integer.MAX_VALUE); + } + + /** + * Configure the start point (initial guess). + * @param newStart new start point (initial guess) + * @return a new instance. + */ + public HarmonicCurveFitter withStartPoint(double[] newStart) { + return new HarmonicCurveFitter(newStart.clone(), + maxIter); + } + + /** + * Configure the maximum number of iterations. + * @param newMaxIter maximum number of iterations + * @return a new instance. + */ + public HarmonicCurveFitter withMaxIterations(int newMaxIter) { + return new HarmonicCurveFitter(initialGuess, + newMaxIter); + } + + /** {@inheritDoc} */ + @Override + protected LeastSquaresProblem getProblem(Collection observations) { + // Prepare least-squares problem. + final int len = observations.size(); + final double[] target = new double[len]; + final double[] weights = new double[len]; + + int i = 0; + for (WeightedObservedPoint obs : observations) { + target[i] = obs.getY(); + weights[i] = obs.getWeight(); + ++i; + } + + final AbstractCurveFitter.TheoreticalValuesFunction model + = new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, + observations); + + final double[] startPoint = initialGuess != null ? + initialGuess : + // Compute estimation. + new ParameterGuesser(observations).guess(); + + // Return a new optimizer set up to fit a Gaussian curve to the + // observed points. + return new LeastSquaresBuilder(). + maxEvaluations(Integer.MAX_VALUE). + maxIterations(maxIter). + start(startPoint). + target(target). + weight(new DiagonalMatrix(weights)). + model(model.getModelFunction(), model.getModelFunctionJacobian()). + build(); + + } + + /** + * This class guesses harmonic coefficients from a sample. + *

The algorithm used to guess the coefficients is as follows:

+ * + *

We know \( f(t) \) at some sampling points \( t_i \) and want + * to find \( a \), \( \omega \) and \( \phi \) such that + * \( f(t) = a \cos (\omega t + \phi) \). + *

+ * + *

From the analytical expression, we can compute two primitives : + * \[ + * If2(t) = \int f^2 dt = a^2 (t + S(t)) / 2 + * \] + * \[ + * If'2(t) = \int f'^2 dt = a^2 \omega^2 (t - S(t)) / 2 + * \] + * where \(S(t) = \frac{\sin(2 (\omega t + \phi))}{2\omega}\) + *

+ * + *

We can remove \(S\) between these expressions : + * \[ + * If'2(t) = a^2 \omega^2 t - \omega^2 If2(t) + * \] + *

+ * + *

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) + * \] + *

+ * + *

From the primitive, we can deduce the same form for definite + * integrals between \(t_1\) and \(t_i\) for each \(t_i\) : + * \[ + * If2(t_i) - If2(t_1) = A (t_i - t_1) + B (If2 (t_i) - If2(t_1)) + * \] + *

+ * + *

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. + *

+ * + *

For a bilinear expression \(z(x_i, y_i) = A x_i + B y_i\), the + * coefficients \(A\) and \(B\) that minimize a least-squares criterion + * \(\sum (z_i - z(x_i, y_i))^2\) are given by these expressions:

+ * \[ + * A = \frac{\sum y_i y_i \sum x_i z_i - \sum x_i y_i \sum y_i z_i} + * {\sum x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i} + * \] + * \[ + * B = \frac{\sum x_i x_i \sum y_i z_i - \sum x_i y_i \sum x_i z_i} + * {\sum x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i} + * + * \] + * + *

In fact, we can assume that both \(a\) and \(\omega\) are positive and + * compute them directly, knowing that \(A = a^2 \omega^2\) and that + * \(B = -\omega^2\). The complete algorithm is therefore:

+ * + * For each \(t_i\) from \(t_1\) to \(t_{n-1}\), compute: + * \[ f(t_i) \] + * \[ f'(t_i) = \frac{f (t_{i+1}) - f(t_{i-1})}{t_{i+1} - t_{i-1}} \] + * \[ x_i = t_i - t_1 \] + * \[ y_i = \int_{t_1}^{t_i} f^2(t) dt \] + * \[ z_i = \int_{t_1}^{t_i} f'^2(t) dt \] + * and update the sums: + * \[ \sum x_i x_i, \sum y_i y_i, \sum x_i y_i, \sum x_i z_i, \sum y_i z_i \] + * + * Then: + * \[ + * a = \sqrt{\frac{\sum y_i y_i \sum x_i z_i - \sum x_i y_i \sum y_i z_i } + * {\sum x_i y_i \sum x_i z_i - \sum x_i x_i \sum y_i z_i }} + * \] + * \[ + * \omega = \sqrt{\frac{\sum x_i y_i \sum x_i z_i - \sum x_i x_i \sum y_i z_i} + * {\sum x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i}} + * \] + * + *

Once we know \(\omega\) we can compute: + * \[ + * fc = \omega f(t) \cos(\omega t) - f'(t) \sin(\omega t) + * \] + * \[ + * fs = \omega f(t) \sin(\omega t) + f'(t) \cos(\omega t) + * \] + *

+ * + *

It appears that \(fc = a \omega \cos(\phi)\) and + * \(fs = -a \omega \sin(\phi)\), so we can use these + * expressions to compute \(\phi\). The best estimate over the sample is + * given by averaging these expressions. + *

+ * + *

Since integrals and means are involved in the preceding + * estimations, these operations run in \(O(n)\) time, where \(n\) is the + * number of measurements.

+ */ + public static class ParameterGuesser { + /** Amplitude. */ + private final double a; + /** Angular frequency. */ + private final double omega; + /** Phase. */ + private final double phi; + + /** + * Simple constructor. + * + * @param observations Sampled observations. + * @throws NumberIsTooSmallException if the sample is too short. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + */ + public ParameterGuesser(Collection observations) { + if (observations.size() < 4) { + throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, + observations.size(), 4, true); + } + + final WeightedObservedPoint[] sorted + = sortObservations(observations).toArray(new WeightedObservedPoint[0]); + + final double aOmega[] = guessAOmega(sorted); + a = aOmega[0]; + omega = aOmega[1]; + + phi = guessPhi(sorted); + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
    + *
  • Amplitude
  • + *
  • Angular frequency
  • + *
  • Phase
  • + *
+ */ + public double[] guess() { + return new double[] { a, omega, phi }; + } + + /** + * Sort the observations with respect to the abscissa. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private List sortObservations(Collection unsorted) { + final List observations = new ArrayList(unsorted); + + // Since the samples are almost always already sorted, this + // method is implemented as an insertion sort that reorders the + // elements in place. Insertion sort is very efficient in this case. + WeightedObservedPoint curr = observations.get(0); + final int len = observations.size(); + for (int j = 1; j < len; j++) { + WeightedObservedPoint prec = curr; + curr = observations.get(j); + if (curr.getX() < prec.getX()) { + // the current element should be inserted closer to the beginning + int i = j - 1; + WeightedObservedPoint mI = observations.get(i); + while ((i >= 0) && (curr.getX() < mI.getX())) { + observations.set(i + 1, mI); + if (i-- != 0) { + mI = observations.get(i); + } + } + observations.set(i + 1, curr); + curr = observations.get(j); + } + } + + return observations; + } + + /** + * Estimate a first guess of the amplitude and angular frequency. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + * @return the guessed amplitude (at index 0) and circular frequency + * (at index 1). + */ + private double[] guessAOmega(WeightedObservedPoint[] observations) { + final double[] aOmega = new double[2]; + + // initialize the sums for the linear model between the two integrals + double sx2 = 0; + double sy2 = 0; + double sxy = 0; + double sxz = 0; + double syz = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + double f2Integral = 0; + double fPrime2Integral = 0; + final double startX = currentX; + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + + // update the integrals of f2 and f'2 + // considering a linear model for f (and therefore constant f') + final double dx = currentX - previousX; + final double dy = currentY - previousY; + final double f2StepIntegral = + dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3; + final double fPrime2StepIntegral = dy * dy / dx; + + final double x = currentX - startX; + f2Integral += f2StepIntegral; + fPrime2Integral += fPrime2StepIntegral; + + sx2 += x * x; + sy2 += f2Integral * f2Integral; + sxy += x * f2Integral; + sxz += x * fPrime2Integral; + syz += f2Integral * fPrime2Integral; + } + + // compute the amplitude and pulsation coefficients + double c1 = sy2 * sxz - sxy * syz; + double c2 = sxy * sxz - sx2 * syz; + double c3 = sx2 * sy2 - sxy * sxy; + if ((c1 / c2 < 0) || (c2 / c3 < 0)) { + final int last = observations.length - 1; + // Range of the observations, assuming that the + // observations are sorted. + final double xRange = observations[last].getX() - observations[0].getX(); + if (xRange == 0) { + throw new ZeroException(); + } + aOmega[1] = 2 * Math.PI / xRange; + + double yMin = Double.POSITIVE_INFINITY; + double yMax = Double.NEGATIVE_INFINITY; + for (int i = 1; i < observations.length; ++i) { + final double y = observations[i].getY(); + if (y < yMin) { + yMin = y; + } + if (y > yMax) { + yMax = y; + } + } + aOmega[0] = 0.5 * (yMax - yMin); + } else { + if (c2 == 0) { + // In some ill-conditioned cases (cf. MATH-844), the guesser + // procedure cannot produce sensible results. + throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR); + } + + aOmega[0] = FastMath.sqrt(c1 / c2); + aOmega[1] = FastMath.sqrt(c2 / c3); + } + + return aOmega; + } + + /** + * Estimate a first guess of the phase. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @return the guessed phase. + */ + private double guessPhi(WeightedObservedPoint[] observations) { + // initialize the means + double fcMean = 0; + double fsMean = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + final double currentYPrime = (currentY - previousY) / (currentX - previousX); + + double omegaX = omega * currentX; + double cosine = FastMath.cos(omegaX); + double sine = FastMath.sin(omegaX); + fcMean += omega * currentY * cosine - currentYPrime * sine; + fsMean += omega * currentY * sine + currentYPrime * cosine; + } + + return FastMath.atan2(-fsMean, fcMean); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicFitter.java new file mode 100644 index 000000000..e3351295f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/HarmonicFitter.java @@ -0,0 +1,384 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting; + +import com.fr.third.org.apache.commons.math3.analysis.function.HarmonicOscillator; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Class that implements a curve fitting specialized for sinusoids. + * + * Harmonic fitting is a very simple case of curve fitting. The + * estimated coefficients are the amplitude a, the pulsation ω and + * the phase φ: f (t) = a cos (ω t + φ). They are + * searched by a least square estimator initialized with a rough guess + * based on integrals. + * + * @since 2.0 + * @deprecated As of 3.3. Please use {@link HarmonicCurveFitter} and + * {@link WeightedObservedPoints} instead. + */ +@Deprecated +public class HarmonicFitter extends CurveFitter { + /** + * Simple constructor. + * @param optimizer Optimizer to use for the fitting. + */ + public HarmonicFitter(final MultivariateVectorOptimizer optimizer) { + super(optimizer); + } + + /** + * Fit an harmonic function to the observed points. + * + * @param initialGuess First guess values in the following order: + *
    + *
  • Amplitude
  • + *
  • Angular frequency
  • + *
  • Phase
  • + *
+ * @return the parameters of the harmonic function that best fits the + * observed points (in the same order as above). + */ + public double[] fit(double[] initialGuess) { + return fit(new HarmonicOscillator.Parametric(), initialGuess); + } + + /** + * Fit an harmonic function to the observed points. + * An initial guess will be automatically computed. + * + * @return the parameters of the harmonic function that best fits the + * observed points (see the other {@link #fit(double[]) fit} method. + * @throws NumberIsTooSmallException if the sample is too short for the + * the first guess to be computed. + * @throws ZeroException if the first guess cannot be computed because + * the abscissa range is zero. + */ + public double[] fit() { + return fit((new ParameterGuesser(getObservations())).guess()); + } + + /** + * This class guesses harmonic coefficients from a sample. + *

The algorithm used to guess the coefficients is as follows:

+ * + *

We know f (t) at some sampling points ti and want to find a, + * ω and φ such that f (t) = a cos (ω t + φ). + *

+ * + *

From the analytical expression, we can compute two primitives : + *

+     *     If2  (t) = ∫ f2  = a2 × [t + S (t)] / 2
+     *     If'2 (t) = ∫ f'2 = a2 ω2 × [t - S (t)] / 2
+     *     where S (t) = sin (2 (ω t + φ)) / (2 ω)
+     * 
+ *

+ * + *

We can remove S between these expressions : + *

+     *     If'2 (t) = a2 ω2 t - ω2 If2 (t)
+     * 
+ *

+ * + *

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) + *

+ * + *

From the primitive, we can deduce the same form for definite + * integrals between t1 and ti for each ti : + *

+     *   If2 (ti) - If2 (t1) = A × (ti - t1) + B × (If2 (ti) - If2 (t1))
+     * 
+ *

+ * + *

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. + *

+ * + *

For a bilinear expression z (xi, yi) = A × xi + B × yi, the + * coefficients A and B that minimize a least square criterion + * ∑ (zi - z (xi, yi))2 are given by these expressions:

+ *
+     *
+     *         ∑yiyi ∑xizi - ∑xiyi ∑yizi
+     *     A = ------------------------
+     *         ∑xixi ∑yiyi - ∑xiyi ∑xiyi
+     *
+     *         ∑xixi ∑yizi - ∑xiyi ∑xizi
+     *     B = ------------------------
+     *         ∑xixi ∑yiyi - ∑xiyi ∑xiyi
+     * 
+ *

+ * + * + *

In fact, we can assume both a and ω are positive and + * compute them directly, knowing that A = a2 ω2 and that + * B = - ω2. The complete algorithm is therefore:

+ *
+     *
+     * for each ti from t1 to tn-1, compute:
+     *   f  (ti)
+     *   f' (ti) = (f (ti+1) - f(ti-1)) / (ti+1 - ti-1)
+     *   xi = ti - t1
+     *   yi = ∫ f2 from t1 to ti
+     *   zi = ∫ f'2 from t1 to ti
+     *   update the sums ∑xixi, ∑yiyi, ∑xiyi, ∑xizi and ∑yizi
+     * end for
+     *
+     *            |--------------------------
+     *         \  | ∑yiyi ∑xizi - ∑xiyi ∑yizi
+     * a     =  \ | ------------------------
+     *           \| ∑xiyi ∑xizi - ∑xixi ∑yizi
+     *
+     *
+     *            |--------------------------
+     *         \  | ∑xiyi ∑xizi - ∑xixi ∑yizi
+     * ω     =  \ | ------------------------
+     *           \| ∑xixi ∑yiyi - ∑xiyi ∑xiyi
+     *
+     * 
+ *

+ * + *

Once we know ω, we can compute: + *

+     *    fc = ω f (t) cos (ω t) - f' (t) sin (ω t)
+     *    fs = ω f (t) sin (ω t) + f' (t) cos (ω t)
+     * 
+ *

+ * + *

It appears that fc = a ω cos (φ) and + * fs = -a ω sin (φ), so we can use these + * expressions to compute φ. The best estimate over the sample is + * given by averaging these expressions. + *

+ * + *

Since integrals and means are involved in the preceding + * estimations, these operations run in O(n) time, where n is the + * number of measurements.

+ */ + public static class ParameterGuesser { + /** Amplitude. */ + private final double a; + /** Angular frequency. */ + private final double omega; + /** Phase. */ + private final double phi; + + /** + * Simple constructor. + * + * @param observations Sampled observations. + * @throws NumberIsTooSmallException if the sample is too short. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + */ + public ParameterGuesser(WeightedObservedPoint[] observations) { + if (observations.length < 4) { + throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, + observations.length, 4, true); + } + + final WeightedObservedPoint[] sorted = sortObservations(observations); + + final double aOmega[] = guessAOmega(sorted); + a = aOmega[0]; + omega = aOmega[1]; + + phi = guessPhi(sorted); + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
    + *
  • Amplitude
  • + *
  • Angular frequency
  • + *
  • Phase
  • + *
+ */ + public double[] guess() { + return new double[] { a, omega, phi }; + } + + /** + * Sort the observations with respect to the abscissa. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) { + final WeightedObservedPoint[] observations = unsorted.clone(); + + // Since the samples are almost always already sorted, this + // method is implemented as an insertion sort that reorders the + // elements in place. Insertion sort is very efficient in this case. + WeightedObservedPoint curr = observations[0]; + for (int j = 1; j < observations.length; ++j) { + WeightedObservedPoint prec = curr; + curr = observations[j]; + if (curr.getX() < prec.getX()) { + // the current element should be inserted closer to the beginning + int i = j - 1; + WeightedObservedPoint mI = observations[i]; + while ((i >= 0) && (curr.getX() < mI.getX())) { + observations[i + 1] = mI; + if (i-- != 0) { + mI = observations[i]; + } + } + observations[i + 1] = curr; + curr = observations[j]; + } + } + + return observations; + } + + /** + * Estimate a first guess of the amplitude and angular frequency. + * This method assumes that the {@link #sortObservations(WeightedObservedPoint[])} method + * has been called previously. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + * @return the guessed amplitude (at index 0) and circular frequency + * (at index 1). + */ + private double[] guessAOmega(WeightedObservedPoint[] observations) { + final double[] aOmega = new double[2]; + + // initialize the sums for the linear model between the two integrals + double sx2 = 0; + double sy2 = 0; + double sxy = 0; + double sxz = 0; + double syz = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + double f2Integral = 0; + double fPrime2Integral = 0; + final double startX = currentX; + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + + // update the integrals of f2 and f'2 + // considering a linear model for f (and therefore constant f') + final double dx = currentX - previousX; + final double dy = currentY - previousY; + final double f2StepIntegral = + dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3; + final double fPrime2StepIntegral = dy * dy / dx; + + final double x = currentX - startX; + f2Integral += f2StepIntegral; + fPrime2Integral += fPrime2StepIntegral; + + sx2 += x * x; + sy2 += f2Integral * f2Integral; + sxy += x * f2Integral; + sxz += x * fPrime2Integral; + syz += f2Integral * fPrime2Integral; + } + + // compute the amplitude and pulsation coefficients + double c1 = sy2 * sxz - sxy * syz; + double c2 = sxy * sxz - sx2 * syz; + double c3 = sx2 * sy2 - sxy * sxy; + if ((c1 / c2 < 0) || (c2 / c3 < 0)) { + final int last = observations.length - 1; + // Range of the observations, assuming that the + // observations are sorted. + final double xRange = observations[last].getX() - observations[0].getX(); + if (xRange == 0) { + throw new ZeroException(); + } + aOmega[1] = 2 * Math.PI / xRange; + + double yMin = Double.POSITIVE_INFINITY; + double yMax = Double.NEGATIVE_INFINITY; + for (int i = 1; i < observations.length; ++i) { + final double y = observations[i].getY(); + if (y < yMin) { + yMin = y; + } + if (y > yMax) { + yMax = y; + } + } + aOmega[0] = 0.5 * (yMax - yMin); + } else { + if (c2 == 0) { + // In some ill-conditioned cases (cf. MATH-844), the guesser + // procedure cannot produce sensible results. + throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR); + } + + aOmega[0] = FastMath.sqrt(c1 / c2); + aOmega[1] = FastMath.sqrt(c2 / c3); + } + + return aOmega; + } + + /** + * Estimate a first guess of the phase. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @return the guessed phase. + */ + private double guessPhi(WeightedObservedPoint[] observations) { + // initialize the means + double fcMean = 0; + double fsMean = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + final double currentYPrime = (currentY - previousY) / (currentX - previousX); + + double omegaX = omega * currentX; + double cosine = FastMath.cos(omegaX); + double sine = FastMath.sin(omegaX); + fcMean += omega * currentY * cosine - currentYPrime * sine; + fsMean += omega * currentY * sine + currentYPrime * cosine; + } + + return FastMath.atan2(-fsMean, fcMean); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialCurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialCurveFitter.java new file mode 100644 index 000000000..be085c4b3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialCurveFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; + +/** + * Fits points to a {@link + * PolynomialFunction.Parametric polynomial} + * function. + *
+ * The size of the {@link #withStartPoint(double[]) initial guess} array defines the + * degree of the polynomial to be fitted. + * They must be sorted in increasing order of the polynomial's degree. + * The optimal values of the coefficients will be returned in the same order. + * + * @since 3.3 + */ +public class PolynomialCurveFitter extends AbstractCurveFitter { + /** Parametric function to be fitted. */ + private static final PolynomialFunction.Parametric FUNCTION = new PolynomialFunction.Parametric(); + /** Initial guess. */ + private final double[] initialGuess; + /** Maximum number of iterations of the optimization algorithm. */ + private final int maxIter; + + /** + * Contructor used by the factory methods. + * + * @param initialGuess Initial guess. + * @param maxIter Maximum number of iterations of the optimization algorithm. + * @throws MathInternalError if {@code initialGuess} is {@code null}. + */ + private PolynomialCurveFitter(double[] initialGuess, + int maxIter) { + this.initialGuess = initialGuess; + this.maxIter = maxIter; + } + + /** + * Creates a default curve fitter. + * Zero will be used as initial guess for the coefficients, and the maximum + * number of iterations of the optimization algorithm is set to + * {@link Integer#MAX_VALUE}. + * + * @param degree Degree of the polynomial to be fitted. + * @return a curve fitter. + * + * @see #withStartPoint(double[]) + * @see #withMaxIterations(int) + */ + public static PolynomialCurveFitter create(int degree) { + return new PolynomialCurveFitter(new double[degree + 1], Integer.MAX_VALUE); + } + + /** + * Configure the start point (initial guess). + * @param newStart new start point (initial guess) + * @return a new instance. + */ + public PolynomialCurveFitter withStartPoint(double[] newStart) { + return new PolynomialCurveFitter(newStart.clone(), + maxIter); + } + + /** + * Configure the maximum number of iterations. + * @param newMaxIter maximum number of iterations + * @return a new instance. + */ + public PolynomialCurveFitter withMaxIterations(int newMaxIter) { + return new PolynomialCurveFitter(initialGuess, + newMaxIter); + } + + /** {@inheritDoc} */ + @Override + protected LeastSquaresProblem getProblem(Collection observations) { + // Prepare least-squares problem. + final int len = observations.size(); + final double[] target = new double[len]; + final double[] weights = new double[len]; + + int i = 0; + for (WeightedObservedPoint obs : observations) { + target[i] = obs.getY(); + weights[i] = obs.getWeight(); + ++i; + } + + final AbstractCurveFitter.TheoreticalValuesFunction model = + new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, observations); + + if (initialGuess == null) { + throw new MathInternalError(); + } + + // Return a new least squares problem set up to fit a polynomial curve to the + // observed points. + return new LeastSquaresBuilder(). + maxEvaluations(Integer.MAX_VALUE). + maxIterations(maxIter). + start(initialGuess). + target(target). + weight(new DiagonalMatrix(weights)). + model(model.getModelFunction(), model.getModelFunctionJacobian()). + build(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialFitter.java new file mode 100644 index 000000000..5bafc1ad2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/PolynomialFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer; + +/** + * Polynomial fitting is a very simple case of {@link CurveFitter curve fitting}. + * The estimated coefficients are the polynomial coefficients (see the + * {@link #fit(double[]) fit} method). + * + * @since 2.0 + * @deprecated As of 3.3. Please use {@link PolynomialCurveFitter} and + * {@link WeightedObservedPoints} instead. + */ +@Deprecated +public class PolynomialFitter extends CurveFitter { + /** + * Simple constructor. + * + * @param optimizer Optimizer to use for the fitting. + */ + public PolynomialFitter(MultivariateVectorOptimizer optimizer) { + super(optimizer); + } + + /** + * Get the coefficients of the polynomial fitting the weighted data points. + * The degree of the fitting polynomial is {@code guess.length - 1}. + * + * @param guess First guess for the coefficients. They must be sorted in + * increasing order of the polynomial's degree. + * @param maxEval Maximum number of evaluations of the polynomial. + * @return the coefficients of the polynomial that best fits the observed points. + * @throws TooManyEvaluationsException if + * the number of evaluations exceeds {@code maxEval}. + * @throws ConvergenceException + * if the algorithm failed to converge. + */ + public double[] fit(int maxEval, double[] guess) { + return fit(maxEval, new PolynomialFunction.Parametric(), guess); + } + + /** + * Get the coefficients of the polynomial fitting the weighted data points. + * The degree of the fitting polynomial is {@code guess.length - 1}. + * + * @param guess First guess for the coefficients. They must be sorted in + * increasing order of the polynomial's degree. + * @return the coefficients of the polynomial that best fits the observed points. + * @throws ConvergenceException + * if the algorithm failed to converge. + */ + public double[] fit(double[] guess) { + return fit(new PolynomialFunction.Parametric(), guess); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/SimpleCurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/SimpleCurveFitter.java new file mode 100644 index 000000000..a9f1d97c4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/SimpleCurveFitter.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; + +/** + * Fits points to a user-defined {@link ParametricUnivariateFunction function}. + * + * @since 3.4 + */ +public class SimpleCurveFitter extends AbstractCurveFitter { + /** Function to fit. */ + private final ParametricUnivariateFunction function; + /** Initial guess for the parameters. */ + private final double[] initialGuess; + /** Maximum number of iterations of the optimization algorithm. */ + private final int maxIter; + + /** + * Contructor used by the factory methods. + * + * @param function Function to fit. + * @param initialGuess Initial guess. Cannot be {@code null}. Its length must + * be consistent with the number of parameters of the {@code function} to fit. + * @param maxIter Maximum number of iterations of the optimization algorithm. + */ + private SimpleCurveFitter(ParametricUnivariateFunction function, + double[] initialGuess, + int maxIter) { + this.function = function; + this.initialGuess = initialGuess; + this.maxIter = maxIter; + } + + /** + * Creates a curve fitter. + * The maximum number of iterations of the optimization algorithm is set + * to {@link Integer#MAX_VALUE}. + * + * @param f Function to fit. + * @param start Initial guess for the parameters. Cannot be {@code null}. + * Its length must be consistent with the number of parameters of the + * function to fit. + * @return a curve fitter. + * + * @see #withStartPoint(double[]) + * @see #withMaxIterations(int) + */ + public static SimpleCurveFitter create(ParametricUnivariateFunction f, + double[] start) { + return new SimpleCurveFitter(f, start, Integer.MAX_VALUE); + } + + /** + * Configure the start point (initial guess). + * @param newStart new start point (initial guess) + * @return a new instance. + */ + public SimpleCurveFitter withStartPoint(double[] newStart) { + return new SimpleCurveFitter(function, + newStart.clone(), + maxIter); + } + + /** + * Configure the maximum number of iterations. + * @param newMaxIter maximum number of iterations + * @return a new instance. + */ + public SimpleCurveFitter withMaxIterations(int newMaxIter) { + return new SimpleCurveFitter(function, + initialGuess, + newMaxIter); + } + + /** {@inheritDoc} */ + @Override + protected LeastSquaresProblem getProblem(Collection observations) { + // Prepare least-squares problem. + final int len = observations.size(); + final double[] target = new double[len]; + final double[] weights = new double[len]; + + int count = 0; + for (WeightedObservedPoint obs : observations) { + target[count] = obs.getY(); + weights[count] = obs.getWeight(); + ++count; + } + + final AbstractCurveFitter.TheoreticalValuesFunction model + = new AbstractCurveFitter.TheoreticalValuesFunction(function, + observations); + + // Create an optimizer for fitting the curve to the observed points. + return new LeastSquaresBuilder(). + maxEvaluations(Integer.MAX_VALUE). + maxIterations(maxIter). + start(initialGuess). + target(target). + weight(new DiagonalMatrix(weights)). + model(model.getModelFunction(), model.getModelFunctionJacobian()). + build(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoint.java new file mode 100644 index 000000000..2287e8692 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoint.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.io.Serializable; + +/** + * This class is a simple container for weighted observed point in + * {@link CurveFitter curve fitting}. + *

Instances of this class are guaranteed to be immutable.

+ * @since 2.0 + */ +public class WeightedObservedPoint implements Serializable { + /** Serializable version id. */ + private static final long serialVersionUID = 5306874947404636157L; + /** Weight of the measurement in the fitting process. */ + private final double weight; + /** Abscissa of the point. */ + private final double x; + /** Observed value of the function at x. */ + private final double y; + + /** + * Simple constructor. + * + * @param weight Weight of the measurement in the fitting process. + * @param x Abscissa of the measurement. + * @param y Ordinate of the measurement. + */ + public WeightedObservedPoint(final double weight, final double x, final double y) { + this.weight = weight; + this.x = x; + this.y = y; + } + + /** + * Gets the weight of the measurement in the fitting process. + * + * @return the weight of the measurement in the fitting process. + */ + public double getWeight() { + return weight; + } + + /** + * Gets the abscissa of the point. + * + * @return the abscissa of the point. + */ + public double getX() { + return x; + } + + /** + * Gets the observed value of the function at x. + * + * @return the observed value of the function at x. + */ + public double getY() { + return y; + } + +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoints.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoints.java new file mode 100644 index 000000000..c4db74c25 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/WeightedObservedPoints.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 com.fr.third.org.apache.commons.math3.fitting; + +import java.util.List; +import java.util.ArrayList; +import java.io.Serializable; + +/** + * Simple container for weighted observed points used + * in {@link AbstractCurveFitter curve fitting} algorithms. + * + * @since 3.3 + */ +public class WeightedObservedPoints implements Serializable { + /** Serializable version id. */ + private static final long serialVersionUID = 20130813L; + + /** Observed points. */ + private final List observations + = new ArrayList(); + + /** + * Adds a point to the sample. + * Calling this method is equivalent to calling + * {@code add(1.0, x, y)}. + * + * @param x Abscissa of the point. + * @param y Observed value at {@code x}. After fitting we should + * have {@code f(x)} as close as possible to this value. + * + * @see #add(double, double, double) + * @see #add(WeightedObservedPoint) + * @see #toList() + */ + public void add(double x, double y) { + add(1d, x, y); + } + + /** + * Adds a point to the sample. + * + * @param weight Weight of the observed point. + * @param x Abscissa of the point. + * @param y Observed value at {@code x}. After fitting we should + * have {@code f(x)} as close as possible to this value. + * + * @see #add(double, double) + * @see #add(WeightedObservedPoint) + * @see #toList() + */ + public void add(double weight, double x, double y) { + observations.add(new WeightedObservedPoint(weight, x, y)); + } + + /** + * Adds a point to the sample. + * + * @param observed Observed point to add. + * + * @see #add(double, double) + * @see #add(double, double, double) + * @see #toList() + */ + public void add(WeightedObservedPoint observed) { + observations.add(observed); + } + + /** + * Gets a snapshot of the observed points. + * The list of stored points is copied in order to ensure that + * modification of the returned instance does not affect this + * container. + * Conversely, further modification of this container (through + * the {@code add} or {@code clear} methods) will not affect the + * returned list. + * + * @return the observed points, in the order they were added to this + * container. + * + * @see #add(double, double) + * @see #add(double, double, double) + * @see #add(WeightedObservedPoint) + */ + public List toList() { + // The copy is necessary to ensure thread-safety because of the + // "clear" method (which otherwise would be able to empty the + // list of points while it is being used by another thread). + return new ArrayList(observations); + } + + /** + * Removes all observations from this container. + */ + public void clear() { + observations.clear(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java new file mode 100644 index 000000000..11f0a7bc1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.DecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * An implementation of {@link Evaluation} that is designed for extension. All of the + * methods implemented here use the methods that are left unimplemented. + *

+ * TODO cache results? + * + * @since 3.3 + */ +public abstract class AbstractEvaluation implements Evaluation { + + /** number of observations */ + private final int observationSize; + + /** + * Constructor. + * + * @param observationSize the number of observation. Needed for {@link + * #getRMS()}. + */ + AbstractEvaluation(final int observationSize) { + this.observationSize = observationSize; + } + + /** {@inheritDoc} */ + public RealMatrix getCovariances(double threshold) { + // Set up the Jacobian. + final RealMatrix j = this.getJacobian(); + + // Compute transpose(J)J. + final RealMatrix jTj = j.transpose().multiply(j); + + // Compute the covariances matrix. + final DecompositionSolver solver + = new QRDecomposition(jTj, threshold).getSolver(); + return solver.getInverse(); + } + + /** {@inheritDoc} */ + public RealVector getSigma(double covarianceSingularityThreshold) { + final RealMatrix cov = this.getCovariances(covarianceSingularityThreshold); + final int nC = cov.getColumnDimension(); + final RealVector sig = new ArrayRealVector(nC); + for (int i = 0; i < nC; ++i) { + sig.setEntry(i, FastMath.sqrt(cov.getEntry(i,i))); + } + return sig; + } + + /** {@inheritDoc} */ + public double getRMS() { + final double cost = this.getCost(); + return FastMath.sqrt(cost * cost / this.observationSize); + } + + /** {@inheritDoc} */ + public double getCost() { + final ArrayRealVector r = new ArrayRealVector(this.getResiduals()); + return FastMath.sqrt(r.dotProduct(r)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.java new file mode 100644 index 000000000..6b88f63f2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.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 com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * Applies a dense weight matrix to an evaluation. + * + * @since 3.3 + */ +class DenseWeightedEvaluation extends AbstractEvaluation { + + /** the unweighted evaluation */ + private final LeastSquaresProblem.Evaluation unweighted; + /** reference to the weight square root matrix */ + private final RealMatrix weightSqrt; + + /** + * Create a weighted evaluation from an unweighted one. + * + * @param unweighted the evalutation before weights are applied + * @param weightSqrt the matrix square root of the weight matrix + */ + DenseWeightedEvaluation(final LeastSquaresProblem.Evaluation unweighted, + final RealMatrix weightSqrt) { + // weight square root is square, nR=nC=number of observations + super(weightSqrt.getColumnDimension()); + this.unweighted = unweighted; + this.weightSqrt = weightSqrt; + } + + /* apply weights */ + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return weightSqrt.multiply(this.unweighted.getJacobian()); + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return this.weightSqrt.operate(this.unweighted.getResiduals()); + } + + /* delegate */ + + /** {@inheritDoc} */ + public RealVector getPoint() { + return unweighted.getPoint(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.java new file mode 100644 index 000000000..66c57096f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.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 com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Check if an optimization has converged based on the change in computed RMS. + * + * @since 3.4 + */ +public class EvaluationRmsChecker implements ConvergenceChecker { + + /** relative tolerance for comparisons. */ + private final double relTol; + /** absolute tolerance for comparisons. */ + private final double absTol; + + /** + * Create a convergence checker for the RMS with the same relative and absolute + * tolerance. + * + *

Convenience constructor for when the relative and absolute tolerances are the + * same. Same as {@code new EvaluationRmsChecker(tol, tol)}. + * + * @param tol the relative and absolute tolerance. + * @see #EvaluationRmsChecker(double, double) + */ + public EvaluationRmsChecker(final double tol) { + this(tol, tol); + } + + /** + * Create a convergence checker for the RMS with a relative and absolute tolerance. + * + *

The optimization has converged when the RMS of consecutive evaluations are equal + * to within the given relative tolerance or absolute tolerance. + * + * @param relTol the relative tolerance. + * @param absTol the absolute tolerance. + * @see Precision#equals(double, double, double) + * @see Precision#equalsWithRelativeTolerance(double, double, double) + */ + public EvaluationRmsChecker(final double relTol, final double absTol) { + this.relTol = relTol; + this.absTol = absTol; + } + + /** {@inheritDoc} */ + public boolean converged(final int iteration, + final LeastSquaresProblem.Evaluation previous, + final LeastSquaresProblem.Evaluation current) { + final double prevRms = previous.getRMS(); + final double currRms = current.getRMS(); + return Precision.equals(prevRms, currRms, this.absTol) || + Precision.equalsWithRelativeTolerance(prevRms, currRms, this.relTol); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java new file mode 100644 index 000000000..fc5870735 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.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 com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.CholeskyDecomposition; +import com.fr.third.org.apache.commons.math3.linear.LUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.linear.SingularValueDecomposition; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Gauss-Newton least-squares solver. + *

This class solve a least-square problem by + * solving the normal equations of the linearized problem at each iteration. Either LU + * decomposition or Cholesky decomposition can be used to solve the normal equations, + * or QR decomposition or SVD decomposition can be used to solve the linear system. LU + * decomposition is faster but QR decomposition is more robust for difficult problems, + * and SVD can compute a solution for rank-deficient problems. + *

+ * + * @since 3.3 + */ +public class GaussNewtonOptimizer implements LeastSquaresOptimizer { + + /** The decomposition algorithm to use to solve the normal equations. */ + //TODO move to linear package and expand options? + public enum Decomposition { + /** + * Solve by forming the normal equations (JTJx=JTr) and + * using the {@link LUDecomposition}. + * + *

Theoretically this method takes mn2/2 operations to compute the + * normal matrix and n3/3 operations (m > n) to solve the system using + * the LU decomposition.

+ */ + LU { + @Override + protected RealVector solve(final RealMatrix jacobian, + final RealVector residuals) { + try { + final Pair normalEquation = + computeNormalMatrix(jacobian, residuals); + final RealMatrix normal = normalEquation.getFirst(); + final RealVector jTr = normalEquation.getSecond(); + return new LUDecomposition(normal, SINGULARITY_THRESHOLD) + .getSolver() + .solve(jTr); + } catch (SingularMatrixException e) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e); + } + } + }, + /** + * Solve the linear least squares problem (Jx=r) using the {@link + * QRDecomposition}. + * + *

Theoretically this method takes mn2 - n3/3 operations + * (m > n) and has better numerical accuracy than any method that forms the normal + * equations.

+ */ + QR { + @Override + protected RealVector solve(final RealMatrix jacobian, + final RealVector residuals) { + try { + return new QRDecomposition(jacobian, SINGULARITY_THRESHOLD) + .getSolver() + .solve(residuals); + } catch (SingularMatrixException e) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e); + } + } + }, + /** + * Solve by forming the normal equations (JTJx=JTr) and + * using the {@link CholeskyDecomposition}. + * + *

Theoretically this method takes mn2/2 operations to compute the + * normal matrix and n3/6 operations (m > n) to solve the system using + * the Cholesky decomposition.

+ */ + CHOLESKY { + @Override + protected RealVector solve(final RealMatrix jacobian, + final RealVector residuals) { + try { + final Pair normalEquation = + computeNormalMatrix(jacobian, residuals); + final RealMatrix normal = normalEquation.getFirst(); + final RealVector jTr = normalEquation.getSecond(); + return new CholeskyDecomposition( + normal, SINGULARITY_THRESHOLD, SINGULARITY_THRESHOLD) + .getSolver() + .solve(jTr); + } catch (NonPositiveDefiniteMatrixException e) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e); + } + } + }, + /** + * Solve the linear least squares problem using the {@link + * SingularValueDecomposition}. + * + *

This method is slower, but can provide a solution for rank deficient and + * nearly singular systems. + */ + SVD { + @Override + protected RealVector solve(final RealMatrix jacobian, + final RealVector residuals) { + return new SingularValueDecomposition(jacobian) + .getSolver() + .solve(residuals); + } + }; + + /** + * Solve the linear least squares problem Jx=r. + * + * @param jacobian the Jacobian matrix, J. the number of rows >= the number or + * columns. + * @param residuals the computed residuals, r. + * @return the solution x, to the linear least squares problem Jx=r. + * @throws ConvergenceException if the matrix properties (e.g. singular) do not + * permit a solution. + */ + protected abstract RealVector solve(RealMatrix jacobian, + RealVector residuals); + } + + /** + * The singularity threshold for matrix decompositions. Determines when a {@link + * ConvergenceException} is thrown. The current value was the default value for {@link + * LUDecomposition}. + */ + private static final double SINGULARITY_THRESHOLD = 1e-11; + + /** Indicator for using LU decomposition. */ + private final Decomposition decomposition; + + /** + * Creates a Gauss Newton optimizer. + *

+ * The default for the algorithm is to solve the normal equations using QR + * decomposition. + */ + public GaussNewtonOptimizer() { + this(Decomposition.QR); + } + + /** + * Create a Gauss Newton optimizer that uses the given decomposition algorithm to + * solve the normal equations. + * + * @param decomposition the {@link Decomposition} algorithm. + */ + public GaussNewtonOptimizer(final Decomposition decomposition) { + this.decomposition = decomposition; + } + + /** + * Get the matrix decomposition algorithm used to solve the normal equations. + * + * @return the matrix {@link Decomposition} algoritm. + */ + public Decomposition getDecomposition() { + return this.decomposition; + } + + /** + * Configure the decomposition algorithm. + * + * @param newDecomposition the {@link Decomposition} algorithm to use. + * @return a new instance. + */ + public GaussNewtonOptimizer withDecomposition(final Decomposition newDecomposition) { + return new GaussNewtonOptimizer(newDecomposition); + } + + /** {@inheritDoc} */ + public Optimum optimize(final LeastSquaresProblem lsp) { + //create local evaluation and iteration counts + final Incrementor evaluationCounter = lsp.getEvaluationCounter(); + final Incrementor iterationCounter = lsp.getIterationCounter(); + final ConvergenceChecker checker + = lsp.getConvergenceChecker(); + + // Computation will be useless without a checker (see "for-loop"). + if (checker == null) { + throw new NullArgumentException(); + } + + RealVector currentPoint = lsp.getStart(); + + // iterate until convergence is reached + LeastSquaresProblem.Evaluation current = null; + while (true) { + iterationCounter.incrementCount(); + + // evaluate the objective function and its jacobian + LeastSquaresProblem.Evaluation previous = current; + // Value of the objective function at "currentPoint". + evaluationCounter.incrementCount(); + current = lsp.evaluate(currentPoint); + final RealVector currentResiduals = current.getResiduals(); + final RealMatrix weightedJacobian = current.getJacobian(); + currentPoint = current.getPoint(); + + // Check convergence. + if (previous != null && + checker.converged(iterationCounter.getCount(), previous, current)) { + return new OptimumImpl(current, + evaluationCounter.getCount(), + iterationCounter.getCount()); + } + + // solve the linearized least squares problem + final RealVector dX = this.decomposition.solve(weightedJacobian, currentResiduals); + // update the estimated parameters + currentPoint = currentPoint.add(dX); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "GaussNewtonOptimizer{" + + "decomposition=" + decomposition + + '}'; + } + + /** + * Compute the normal matrix, JTJ. + * + * @param jacobian the m by n jacobian matrix, J. Input. + * @param residuals the m by 1 residual vector, r. Input. + * @return the n by n normal matrix and the n by 1 JTr vector. + */ + private static Pair computeNormalMatrix(final RealMatrix jacobian, + final RealVector residuals) { + //since the normal matrix is symmetric, we only need to compute half of it. + final int nR = jacobian.getRowDimension(); + final int nC = jacobian.getColumnDimension(); + //allocate space for return values + final RealMatrix normal = MatrixUtils.createRealMatrix(nC, nC); + final RealVector jTr = new ArrayRealVector(nC); + //for each measurement + for (int i = 0; i < nR; ++i) { + //compute JTr for measurement i + for (int j = 0; j < nC; j++) { + jTr.setEntry(j, jTr.getEntry(j) + + residuals.getEntry(i) * jacobian.getEntry(i, j)); + } + + // add the the contribution to the normal matrix for measurement i + for (int k = 0; k < nC; ++k) { + //only compute the upper triangular part + for (int l = k; l < nC; ++l) { + normal.setEntry(k, l, normal.getEntry(k, l) + + jacobian.getEntry(i, k) * jacobian.getEntry(i, l)); + } + } + } + //copy the upper triangular part to the lower triangular part. + for (int i = 0; i < nC; i++) { + for (int j = 0; j < i; j++) { + normal.setEntry(i, j, normal.getEntry(j, i)); + } + } + return new Pair(normal, jTr); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java new file mode 100644 index 000000000..0f38150aa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.Incrementor; + +/** + * An adapter that delegates to another implementation of {@link LeastSquaresProblem}. + * + * @since 3.3 + */ +public class LeastSquaresAdapter implements LeastSquaresProblem { + + /** the delegate problem */ + private final LeastSquaresProblem problem; + + /** + * Delegate the {@link LeastSquaresProblem} interface to the given implementation. + * + * @param problem the delegate + */ + public LeastSquaresAdapter(final LeastSquaresProblem problem) { + this.problem = problem; + } + + /** {@inheritDoc} */ + public RealVector getStart() { + return problem.getStart(); + } + + /** {@inheritDoc} */ + public int getObservationSize() { + return problem.getObservationSize(); + } + + /** {@inheritDoc} */ + public int getParameterSize() { + return problem.getParameterSize(); + } + + /** {@inheritDoc} + * @param point*/ + public Evaluation evaluate(final RealVector point) { + return problem.evaluate(point); + } + + /** {@inheritDoc} */ + public Incrementor getEvaluationCounter() { + return problem.getEvaluationCounter(); + } + + /** {@inheritDoc} */ + public Incrementor getIterationCounter() { + return problem.getIterationCounter(); + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return problem.getConvergenceChecker(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java new file mode 100644 index 000000000..54e5e69cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * A mutable builder for {@link LeastSquaresProblem}s. + * + * @see LeastSquaresFactory + * @since 3.3 + */ +public class LeastSquaresBuilder { + + /** max evaluations */ + private int maxEvaluations; + /** max iterations */ + private int maxIterations; + /** convergence checker */ + private ConvergenceChecker checker; + /** model function */ + private MultivariateJacobianFunction model; + /** observed values */ + private RealVector target; + /** initial guess */ + private RealVector start; + /** weight matrix */ + private RealMatrix weight; + /** + * Lazy evaluation. + * + * @since 3.4 + */ + private boolean lazyEvaluation; + /** Validator. + * + * @since 3.4 + */ + private ParameterValidator paramValidator; + + + /** + * Construct a {@link LeastSquaresProblem} from the data in this builder. + * + * @return a new {@link LeastSquaresProblem}. + */ + public LeastSquaresProblem build() { + return LeastSquaresFactory.create(model, + target, + start, + weight, + checker, + maxEvaluations, + maxIterations, + lazyEvaluation, + paramValidator); + } + + /** + * Configure the max evaluations. + * + * @param newMaxEvaluations the maximum number of evaluations permitted. + * @return this + */ + public LeastSquaresBuilder maxEvaluations(final int newMaxEvaluations) { + this.maxEvaluations = newMaxEvaluations; + return this; + } + + /** + * Configure the max iterations. + * + * @param newMaxIterations the maximum number of iterations permitted. + * @return this + */ + public LeastSquaresBuilder maxIterations(final int newMaxIterations) { + this.maxIterations = newMaxIterations; + return this; + } + + /** + * Configure the convergence checker. + * + * @param newChecker the convergence checker. + * @return this + */ + public LeastSquaresBuilder checker(final ConvergenceChecker newChecker) { + this.checker = newChecker; + return this; + } + + /** + * Configure the convergence checker. + *

+ * This function is an overloaded version of {@link #checker(ConvergenceChecker)}. + * + * @param newChecker the convergence checker. + * @return this + */ + public LeastSquaresBuilder checkerPair(final ConvergenceChecker newChecker) { + return this.checker(LeastSquaresFactory.evaluationChecker(newChecker)); + } + + /** + * Configure the model function. + * + * @param value the model function value + * @param jacobian the Jacobian of {@code value} + * @return this + */ + public LeastSquaresBuilder model(final MultivariateVectorFunction value, + final MultivariateMatrixFunction jacobian) { + return model(LeastSquaresFactory.model(value, jacobian)); + } + + /** + * Configure the model function. + * + * @param newModel the model function value and Jacobian + * @return this + */ + public LeastSquaresBuilder model(final MultivariateJacobianFunction newModel) { + this.model = newModel; + return this; + } + + /** + * Configure the observed data. + * + * @param newTarget the observed data. + * @return this + */ + public LeastSquaresBuilder target(final RealVector newTarget) { + this.target = newTarget; + return this; + } + + /** + * Configure the observed data. + * + * @param newTarget the observed data. + * @return this + */ + public LeastSquaresBuilder target(final double[] newTarget) { + return target(new ArrayRealVector(newTarget, false)); + } + + /** + * Configure the initial guess. + * + * @param newStart the initial guess. + * @return this + */ + public LeastSquaresBuilder start(final RealVector newStart) { + this.start = newStart; + return this; + } + + /** + * Configure the initial guess. + * + * @param newStart the initial guess. + * @return this + */ + public LeastSquaresBuilder start(final double[] newStart) { + return start(new ArrayRealVector(newStart, false)); + } + + /** + * Configure the weight matrix. + * + * @param newWeight the weight matrix + * @return this + */ + public LeastSquaresBuilder weight(final RealMatrix newWeight) { + this.weight = newWeight; + return this; + } + + /** + * Configure whether evaluation will be lazy or not. + * + * @param newValue Whether to perform lazy evaluation. + * @return this object. + * + * @since 3.4 + */ + public LeastSquaresBuilder lazyEvaluation(final boolean newValue) { + lazyEvaluation = newValue; + return this; + } + + /** + * Configure the validator of the model parameters. + * + * @param newValidator Parameter validator. + * @return this object. + * + * @since 3.4 + */ + public LeastSquaresBuilder parameterValidator(final ParameterValidator newValidator) { + paramValidator = newValidator; + return this; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java new file mode 100644 index 000000000..0bcb022ad --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java @@ -0,0 +1,532 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.AbstractOptimizationProblem; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * A Factory for creating {@link LeastSquaresProblem}s. + * + * @since 3.3 + */ +public class LeastSquaresFactory { + + /** Prevent instantiation. */ + private LeastSquaresFactory() {} + + /** + * Create a {@link LeastSquaresProblem} + * from the given elements. There will be no weights applied (unit weights). + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)} + * will defer the evaluation until access to the value is requested. + * @param paramValidator Model parameters validator. + * @return the specified General Least Squares problem. + * + * @since 3.4 + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final RealMatrix weight, + final ConvergenceChecker checker, + final int maxEvaluations, + final int maxIterations, + final boolean lazyEvaluation, + final ParameterValidator paramValidator) { + final LeastSquaresProblem p = new LocalLeastSquaresProblem(model, + observed, + start, + checker, + maxEvaluations, + maxIterations, + lazyEvaluation, + paramValidator); + if (weight != null) { + return weightMatrix(p, weight); + } else { + return p; + } + } + + /** + * Create a {@link LeastSquaresProblem} + * from the given elements. There will be no weights applied (unit weights). + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final ConvergenceChecker checker, + final int maxEvaluations, + final int maxIterations) { + return create(model, + observed, + start, + null, + checker, + maxEvaluations, + maxIterations, + false, + null); + } + + /** + * Create a {@link LeastSquaresProblem} + * from the given elements. + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final RealMatrix weight, + final ConvergenceChecker checker, + final int maxEvaluations, + final int maxIterations) { + return weightMatrix(create(model, + observed, + start, + checker, + maxEvaluations, + maxIterations), + weight); + } + + /** + * Create a {@link LeastSquaresProblem} + * from the given elements. + *

+ * This factory method is provided for continuity with previous interfaces. Newer + * applications should use {@link #create(MultivariateJacobianFunction, RealVector, + * RealVector, ConvergenceChecker, int, int)}, or {@link #create(MultivariateJacobianFunction, + * RealVector, RealVector, RealMatrix, ConvergenceChecker, int, int)}. + * + * @param model the model function. Produces the computed values. + * @param jacobian the jacobian of the model with respect to the parameters + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateVectorFunction model, + final MultivariateMatrixFunction jacobian, + final double[] observed, + final double[] start, + final RealMatrix weight, + final ConvergenceChecker checker, + final int maxEvaluations, + final int maxIterations) { + return create(model(model, jacobian), + new ArrayRealVector(observed, false), + new ArrayRealVector(start, false), + weight, + checker, + maxEvaluations, + maxIterations); + } + + /** + * Apply a dense weight matrix to the {@link LeastSquaresProblem}. + * + * @param problem the unweighted problem + * @param weights the matrix of weights + * @return a new {@link LeastSquaresProblem} with the weights applied. The original + * {@code problem} is not modified. + */ + public static LeastSquaresProblem weightMatrix(final LeastSquaresProblem problem, + final RealMatrix weights) { + final RealMatrix weightSquareRoot = squareRoot(weights); + return new LeastSquaresAdapter(problem) { + /** {@inheritDoc} */ + @Override + public Evaluation evaluate(final RealVector point) { + return new DenseWeightedEvaluation(super.evaluate(point), weightSquareRoot); + } + }; + } + + /** + * Apply a diagonal weight matrix to the {@link LeastSquaresProblem}. + * + * @param problem the unweighted problem + * @param weights the diagonal of the weight matrix + * @return a new {@link LeastSquaresProblem} with the weights applied. The original + * {@code problem} is not modified. + */ + public static LeastSquaresProblem weightDiagonal(final LeastSquaresProblem problem, + final RealVector weights) { + // TODO more efficient implementation + return weightMatrix(problem, new DiagonalMatrix(weights.toArray())); + } + + /** + * Count the evaluations of a particular problem. The {@code counter} will be + * incremented every time {@link LeastSquaresProblem#evaluate(RealVector)} is called on + * the returned problem. + * + * @param problem the problem to track. + * @param counter the counter to increment. + * @return a least squares problem that tracks evaluations + */ + public static LeastSquaresProblem countEvaluations(final LeastSquaresProblem problem, + final Incrementor counter) { + return new LeastSquaresAdapter(problem) { + + /** {@inheritDoc} */ + @Override + public Evaluation evaluate(final RealVector point) { + counter.incrementCount(); + return super.evaluate(point); + } + + // Delegate the rest. + }; + } + + /** + * View a convergence checker specified for a {@link PointVectorValuePair} as one + * specified for an {@link Evaluation}. + * + * @param checker the convergence checker to adapt. + * @return a convergence checker that delegates to {@code checker}. + */ + public static ConvergenceChecker evaluationChecker(final ConvergenceChecker checker) { + return new ConvergenceChecker() { + /** {@inheritDoc} */ + public boolean converged(final int iteration, + final Evaluation previous, + final Evaluation current) { + return checker.converged( + iteration, + new PointVectorValuePair( + previous.getPoint().toArray(), + previous.getResiduals().toArray(), + false), + new PointVectorValuePair( + current.getPoint().toArray(), + current.getResiduals().toArray(), + false) + ); + } + }; + } + + /** + * Computes the square-root of the weight matrix. + * + * @param m Symmetric, positive-definite (weight) matrix. + * @return the square-root of the weight matrix. + */ + private static RealMatrix squareRoot(final RealMatrix m) { + if (m instanceof DiagonalMatrix) { + final int dim = m.getRowDimension(); + final RealMatrix sqrtM = new DiagonalMatrix(dim); + for (int i = 0; i < dim; i++) { + sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i))); + } + return sqrtM; + } else { + final EigenDecomposition dec = new EigenDecomposition(m); + return dec.getSquareRoot(); + } + } + + /** + * Combine a {@link MultivariateVectorFunction} with a {@link + * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}. + * + * @param value the vector value function + * @param jacobian the Jacobian function + * @return a function that computes both at the same time + */ + public static MultivariateJacobianFunction model(final MultivariateVectorFunction value, + final MultivariateMatrixFunction jacobian) { + return new LocalValueAndJacobianFunction(value, jacobian); + } + + /** + * Combine a {@link MultivariateVectorFunction} with a {@link + * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}. + * + * @param value the vector value function + * @param jacobian the Jacobian function + * @return a function that computes both at the same time + */ + private static class LocalValueAndJacobianFunction + implements ValueAndJacobianFunction { + /** Model. */ + private final MultivariateVectorFunction value; + /** Model's Jacobian. */ + private final MultivariateMatrixFunction jacobian; + + /** + * @param value Model function. + * @param jacobian Model's Jacobian function. + */ + LocalValueAndJacobianFunction(final MultivariateVectorFunction value, + final MultivariateMatrixFunction jacobian) { + this.value = value; + this.jacobian = jacobian; + } + + /** {@inheritDoc} */ + public Pair value(final RealVector point) { + //TODO get array from RealVector without copying? + final double[] p = point.toArray(); + + // Evaluate. + return new Pair(computeValue(p), + computeJacobian(p)); + } + + /** {@inheritDoc} */ + public RealVector computeValue(final double[] params) { + return new ArrayRealVector(value.value(params), false); + } + + /** {@inheritDoc} */ + public RealMatrix computeJacobian(final double[] params) { + return new Array2DRowRealMatrix(jacobian.value(params), false); + } + } + + + /** + * A private, "field" immutable (not "real" immutable) implementation of {@link + * LeastSquaresProblem}. + * @since 3.3 + */ + private static class LocalLeastSquaresProblem + extends AbstractOptimizationProblem + implements LeastSquaresProblem { + + /** Target values for the model function at optimum. */ + private final RealVector target; + /** Model function. */ + private final MultivariateJacobianFunction model; + /** Initial guess. */ + private final RealVector start; + /** Whether to use lazy evaluation. */ + private final boolean lazyEvaluation; + /** Model parameters validator. */ + private final ParameterValidator paramValidator; + + /** + * Create a {@link LeastSquaresProblem} from the given data. + * + * @param model the model function + * @param target the observed data + * @param start the initial guess + * @param checker the convergence checker + * @param maxEvaluations the allowed evaluations + * @param maxIterations the allowed iterations + * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)} + * will defer the evaluation until access to the value is requested. + * @param paramValidator Model parameters validator. + */ + LocalLeastSquaresProblem(final MultivariateJacobianFunction model, + final RealVector target, + final RealVector start, + final ConvergenceChecker checker, + final int maxEvaluations, + final int maxIterations, + final boolean lazyEvaluation, + final ParameterValidator paramValidator) { + super(maxEvaluations, maxIterations, checker); + this.target = target; + this.model = model; + this.start = start; + this.lazyEvaluation = lazyEvaluation; + this.paramValidator = paramValidator; + + if (lazyEvaluation && + !(model instanceof ValueAndJacobianFunction)) { + // Lazy evaluation requires that value and Jacobian + // can be computed separately. + throw new MathIllegalStateException(LocalizedFormats.INVALID_IMPLEMENTATION, + model.getClass().getName()); + } + } + + /** {@inheritDoc} */ + public int getObservationSize() { + return target.getDimension(); + } + + /** {@inheritDoc} */ + public int getParameterSize() { + return start.getDimension(); + } + + /** {@inheritDoc} */ + public RealVector getStart() { + return start == null ? null : start.copy(); + } + + /** {@inheritDoc} */ + public Evaluation evaluate(final RealVector point) { + // Copy so optimizer can change point without changing our instance. + final RealVector p = paramValidator == null ? + point.copy() : + paramValidator.validate(point.copy()); + + if (lazyEvaluation) { + return new LazyUnweightedEvaluation((ValueAndJacobianFunction) model, + target, + p); + } else { + // Evaluate value and jacobian in one function call. + final Pair value = model.value(p); + return new UnweightedEvaluation(value.getFirst(), + value.getSecond(), + target, + p); + } + } + + /** + * Container with the model evaluation at a particular point. + */ + private static class UnweightedEvaluation extends AbstractEvaluation { + /** Point of evaluation. */ + private final RealVector point; + /** Derivative at point. */ + private final RealMatrix jacobian; + /** Computed residuals. */ + private final RealVector residuals; + + /** + * Create an {@link Evaluation} with no weights. + * + * @param values the computed function values + * @param jacobian the computed function Jacobian + * @param target the observed values + * @param point the abscissa + */ + private UnweightedEvaluation(final RealVector values, + final RealMatrix jacobian, + final RealVector target, + final RealVector point) { + super(target.getDimension()); + this.jacobian = jacobian; + this.point = point; + this.residuals = target.subtract(values); + } + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return jacobian; + } + + /** {@inheritDoc} */ + public RealVector getPoint() { + return point; + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return residuals; + } + } + + /** + * Container with the model lazy evaluation at a particular point. + */ + private static class LazyUnweightedEvaluation extends AbstractEvaluation { + /** Point of evaluation. */ + private final RealVector point; + /** Model and Jacobian functions. */ + private final ValueAndJacobianFunction model; + /** Target values for the model function at optimum. */ + private final RealVector target; + + /** + * Create an {@link Evaluation} with no weights. + * + * @param model the model function + * @param target the observed values + * @param point the abscissa + */ + private LazyUnweightedEvaluation(final ValueAndJacobianFunction model, + final RealVector target, + final RealVector point) { + super(target.getDimension()); + // Safe to cast as long as we control usage of this class. + this.model = model; + this.point = point; + this.target = target; + } + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return model.computeJacobian(point.toArray()); + } + + /** {@inheritDoc} */ + public RealVector getPoint() { + return point; + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return target.subtract(model.computeValue(point.toArray())); + } + } + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java new file mode 100644 index 000000000..d2d5ba979 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +/** + * An algorithm that can be applied to a non-linear least squares problem. + * + * @since 3.3 + */ +public interface LeastSquaresOptimizer { + + /** + * Solve the non-linear least squares problem. + * + * + * @param leastSquaresProblem the problem definition, including model function and + * convergence criteria. + * @return The optimum. + */ + Optimum optimize(LeastSquaresProblem leastSquaresProblem); + + /** + * The optimum found by the optimizer. This object contains the point, its value, and + * some metadata. + */ + //TODO Solution? + interface Optimum extends LeastSquaresProblem.Evaluation { + + /** + * Get the number of times the model was evaluated in order to produce this + * optimum. + * + * @return the number of model (objective) function evaluations + */ + int getEvaluations(); + + /** + * Get the number of times the algorithm iterated in order to produce this + * optimum. In general least squares it is common to have one {@link + * #getEvaluations() evaluation} per iterations. + * + * @return the number of iterations + */ + int getIterations(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.java new file mode 100644 index 000000000..7c5f2a7a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.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 com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.optim.OptimizationProblem; + +/** + * The data necessary to define a non-linear least squares problem. + *

+ * Includes the observed values, computed model function, and + * convergence/divergence criteria. Weights are implicit in {@link + * Evaluation#getResiduals()} and {@link Evaluation#getJacobian()}. + *

+ *

+ * Instances are typically either created progressively using a {@link + * LeastSquaresBuilder builder} or created at once using a {@link LeastSquaresFactory + * factory}. + *

+ * @see LeastSquaresBuilder + * @see LeastSquaresFactory + * @see LeastSquaresAdapter + * + * @since 3.3 + */ +public interface LeastSquaresProblem extends OptimizationProblem { + + /** + * Gets the initial guess. + * + * @return the initial guess values. + */ + RealVector getStart(); + + /** + * Get the number of observations (rows in the Jacobian) in this problem. + * + * @return the number of scalar observations + */ + int getObservationSize(); + + /** + * Get the number of parameters (columns in the Jacobian) in this problem. + * + * @return the number of scalar parameters + */ + int getParameterSize(); + + /** + * Evaluate the model at the specified point. + * + * + * @param point the parameter values. + * @return the model's value and derivative at the given point. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations (of the model vector function) is + * exceeded. + */ + Evaluation evaluate(RealVector point); + + /** + * An evaluation of a {@link LeastSquaresProblem} at a particular point. This class + * also computes several quantities derived from the value and its Jacobian. + */ + public interface Evaluation { + + /** + * Get the covariance matrix of the optimized parameters.
Note that this + * operation involves the inversion of the JTJ matrix, + * where {@code J} is the Jacobian matrix. The {@code threshold} parameter is a + * way for the caller to specify that the result of this computation should be + * considered meaningless, and thus trigger an exception. + * + * + * @param threshold Singularity threshold. + * @return the covariance matrix. + * @throws SingularMatrixException + * if the covariance matrix cannot be computed (singular problem). + */ + RealMatrix getCovariances(double threshold); + + /** + * Get an estimate of the standard deviation of the parameters. The returned + * values are the square root of the diagonal coefficients of the covariance + * matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]} is the optimized + * value of the {@code i}-th parameter, and {@code C} is the covariance matrix. + * + * + * @param covarianceSingularityThreshold Singularity threshold (see {@link + * #getCovariances(double) computeCovariances}). + * @return an estimate of the standard deviation of the optimized parameters + * @throws SingularMatrixException + * if the covariance matrix cannot be computed. + */ + RealVector getSigma(double covarianceSingularityThreshold); + + /** + * Get the normalized cost. It is the square-root of the sum of squared of + * the residuals, divided by the number of measurements. + * + * @return the cost. + */ + double getRMS(); + + /** + * Get the weighted Jacobian matrix. + * + * @return the weighted Jacobian: W1/2 J. + * @throws DimensionMismatchException + * if the Jacobian dimension does not match problem dimension. + */ + RealMatrix getJacobian(); + + /** + * Get the cost. + * + * @return the cost. + * @see #getResiduals() + */ + double getCost(); + + /** + * Get the weighted residuals. The residual is the difference between the + * observed (target) values and the model (objective function) value. There is one + * residual for each element of the vector-valued function. The raw residuals are + * then multiplied by the square root of the weight matrix. + * + * @return the weighted residuals: W1/2 K. + * @throws DimensionMismatchException + * if the residuals have the wrong length. + */ + RealVector getResiduals(); + + /** + * Get the abscissa (independent variables) of this evaluation. + * + * @return the point provided to {@link #evaluate(RealVector)}. + */ + RealVector getPoint(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java new file mode 100644 index 000000000..9f25debf8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java @@ -0,0 +1,1042 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class solves a least-squares problem using the Levenberg-Marquardt + * algorithm. + * + *

This implementation should 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.

+ * + *

The resolution engine is a simple translation of the MINPACK lmder 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 Analyse numérique matricielle + * appliquée à l'art de l'ingénieur, Masson 1986.

+ *

The authors of the original fortran version are: + *

    + *
  • Argonne National Laboratory. MINPACK project. March 1980
  • + *
  • Burton S. Garbow
  • + *
  • Kenneth E. Hillstrom
  • + *
  • Jorge J. More
  • + *
+ * The redistribution policy for MINPACK is available here, for convenience, it + * is reproduced below.

+ * + * + * + * + *
+ * 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. + *
  3. 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.
  4. + *
  5. 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.
  6. + *
  7. 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.
  8. + *
  9. 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.
  10. + *
    + * + * @since 3.3 + */ +public class LevenbergMarquardtOptimizer implements LeastSquaresOptimizer { + + /** Twice the "epsilon machine". */ + private static final double TWO_EPS = 2 * Precision.EPSILON; + + /* configuration parameters */ + /** Positive input variable used in determining the initial step bound. */ + private final double initialStepBoundFactor; + /** Desired relative error in the sum of squares. */ + private final double costRelativeTolerance; + /** Desired relative error in the approximate solution parameters. */ + private final double parRelativeTolerance; + /** Desired max cosine on the orthogonality between the function vector + * and the columns of the jacobian. */ + private final double orthoTolerance; + /** Threshold for QR ranking. */ + private final double qrRankingThreshold; + + /** Default constructor. + *

    + * The default values for the algorithm settings are: + *

      + *
    • Initial step bound factor: 100
    • + *
    • Cost relative tolerance: 1e-10
    • + *
    • Parameters relative tolerance: 1e-10
    • + *
    • Orthogonality tolerance: 1e-10
    • + *
    • QR ranking threshold: {@link Precision#SAFE_MIN}
    • + *
    + **/ + public LevenbergMarquardtOptimizer() { + this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN); + } + + /** + * Construct an instance with all parameters specified. + * + * @param initialStepBoundFactor initial step bound factor + * @param costRelativeTolerance cost relative tolerance + * @param parRelativeTolerance parameters relative tolerance + * @param orthoTolerance orthogonality tolerance + * @param qrRankingThreshold threshold in the QR decomposition. Columns with a 2 + * norm less than this threshold are considered to be + * all 0s. + */ + public LevenbergMarquardtOptimizer( + final double initialStepBoundFactor, + final double costRelativeTolerance, + final double parRelativeTolerance, + final double orthoTolerance, + final double qrRankingThreshold) { + this.initialStepBoundFactor = initialStepBoundFactor; + this.costRelativeTolerance = costRelativeTolerance; + this.parRelativeTolerance = parRelativeTolerance; + this.orthoTolerance = orthoTolerance; + this.qrRankingThreshold = qrRankingThreshold; + } + + /** + * @param newInitialStepBoundFactor Positive input variable used in + * determining the initial step bound. This bound is set to the + * product of initialStepBoundFactor and the euclidean norm of + * {@code diag * x} if non-zero, or else to {@code newInitialStepBoundFactor} + * itself. In most cases factor should lie in the interval + * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value. + * of the matrix is reduced. + * @return a new instance. + */ + public LevenbergMarquardtOptimizer withInitialStepBoundFactor(double newInitialStepBoundFactor) { + return new LevenbergMarquardtOptimizer( + newInitialStepBoundFactor, + costRelativeTolerance, + parRelativeTolerance, + orthoTolerance, + qrRankingThreshold); + } + + /** + * @param newCostRelativeTolerance Desired relative error in the sum of squares. + * @return a new instance. + */ + public LevenbergMarquardtOptimizer withCostRelativeTolerance(double newCostRelativeTolerance) { + return new LevenbergMarquardtOptimizer( + initialStepBoundFactor, + newCostRelativeTolerance, + parRelativeTolerance, + orthoTolerance, + qrRankingThreshold); + } + + /** + * @param newParRelativeTolerance Desired relative error in the approximate solution + * parameters. + * @return a new instance. + */ + public LevenbergMarquardtOptimizer withParameterRelativeTolerance(double newParRelativeTolerance) { + return new LevenbergMarquardtOptimizer( + initialStepBoundFactor, + costRelativeTolerance, + newParRelativeTolerance, + orthoTolerance, + qrRankingThreshold); + } + + /** + * Modifies the given parameter. + * + * @param newOrthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + * @return a new instance. + */ + public LevenbergMarquardtOptimizer withOrthoTolerance(double newOrthoTolerance) { + return new LevenbergMarquardtOptimizer( + initialStepBoundFactor, + costRelativeTolerance, + parRelativeTolerance, + newOrthoTolerance, + qrRankingThreshold); + } + + /** + * @param newQRRankingThreshold Desired threshold for QR ranking. + * If the squared norm of a column vector is smaller or equal to this + * threshold during QR decomposition, it is considered to be a zero vector + * and hence the rank of the matrix is reduced. + * @return a new instance. + */ + public LevenbergMarquardtOptimizer withRankingThreshold(double newQRRankingThreshold) { + return new LevenbergMarquardtOptimizer( + initialStepBoundFactor, + costRelativeTolerance, + parRelativeTolerance, + orthoTolerance, + newQRRankingThreshold); + } + + /** + * Gets the value of a tuning parameter. + * @see #withInitialStepBoundFactor(double) + * + * @return the parameter's value. + */ + public double getInitialStepBoundFactor() { + return initialStepBoundFactor; + } + + /** + * Gets the value of a tuning parameter. + * @see #withCostRelativeTolerance(double) + * + * @return the parameter's value. + */ + public double getCostRelativeTolerance() { + return costRelativeTolerance; + } + + /** + * Gets the value of a tuning parameter. + * @see #withParameterRelativeTolerance(double) + * + * @return the parameter's value. + */ + public double getParameterRelativeTolerance() { + return parRelativeTolerance; + } + + /** + * Gets the value of a tuning parameter. + * @see #withOrthoTolerance(double) + * + * @return the parameter's value. + */ + public double getOrthoTolerance() { + return orthoTolerance; + } + + /** + * Gets the value of a tuning parameter. + * @see #withRankingThreshold(double) + * + * @return the parameter's value. + */ + public double getRankingThreshold() { + return qrRankingThreshold; + } + + /** {@inheritDoc} */ + public Optimum optimize(final LeastSquaresProblem problem) { + // Pull in relevant data from the problem as locals. + final int nR = problem.getObservationSize(); // Number of observed data. + final int nC = problem.getParameterSize(); // Number of parameters. + // Counters. + final Incrementor iterationCounter = problem.getIterationCounter(); + final Incrementor evaluationCounter = problem.getEvaluationCounter(); + // Convergence criterion. + final ConvergenceChecker checker = problem.getConvergenceChecker(); + + // arrays shared with the other private methods + final int solvedCols = FastMath.min(nR, nC); + /* Parameters evolution direction associated with lmPar. */ + double[] lmDir = new double[nC]; + /* Levenberg-Marquardt parameter. */ + double lmPar = 0; + + // local point + double delta = 0; + double xNorm = 0; + double[] diag = new double[nC]; + double[] oldX = new double[nC]; + double[] oldRes = new double[nR]; + double[] qtf = new double[nR]; + double[] work1 = new double[nC]; + double[] work2 = new double[nC]; + double[] work3 = new double[nC]; + + + // Evaluate the function at the starting point and calculate its norm. + evaluationCounter.incrementCount(); + //value will be reassigned in the loop + Evaluation current = problem.evaluate(problem.getStart()); + double[] currentResiduals = current.getResiduals().toArray(); + double currentCost = current.getCost(); + double[] currentPoint = current.getPoint().toArray(); + + // Outer loop. + boolean firstIteration = true; + while (true) { + iterationCounter.incrementCount(); + + final Evaluation previous = current; + + // QR decomposition of the jacobian matrix + final InternalData internalData + = qrDecomposition(current.getJacobian(), solvedCols); + final double[][] weightedJacobian = internalData.weightedJacobian; + final int[] permutation = internalData.permutation; + final double[] diagR = internalData.diagR; + final double[] jacNorm = internalData.jacNorm; + + //residuals already have weights applied + double[] weightedResidual = currentResiduals; + for (int i = 0; i < nR; i++) { + qtf[i] = weightedResidual[i]; + } + + // compute Qt.res + qTy(qtf, internalData); + + // now we don't need Q anymore, + // so let jacobian contain the R matrix with its diagonal elements + for (int k = 0; k < solvedCols; ++k) { + int pk = permutation[k]; + weightedJacobian[k][pk] = diagR[pk]; + } + + if (firstIteration) { + // scale the point according to the norms of the columns + // of the initial jacobian + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double dk = jacNorm[k]; + if (dk == 0) { + dk = 1.0; + } + double xk = dk * currentPoint[k]; + xNorm += xk * xk; + diag[k] = dk; + } + xNorm = FastMath.sqrt(xNorm); + + // initialize the step bound delta + delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm); + } + + // check orthogonality between function vector and jacobian columns + double maxCosine = 0; + if (currentCost != 0) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = jacNorm[pj]; + if (s != 0) { + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qtf[i]; + } + maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost)); + } + } + } + if (maxCosine <= orthoTolerance) { + // Convergence has been reached. + return new OptimumImpl( + current, + evaluationCounter.getCount(), + iterationCounter.getCount()); + } + + // rescale if necessary + for (int j = 0; j < nC; ++j) { + diag[j] = FastMath.max(diag[j], jacNorm[j]); + } + + // Inner loop. + for (double ratio = 0; ratio < 1.0e-4;) { + + // save the state + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + oldX[pj] = currentPoint[pj]; + } + final double previousCost = currentCost; + double[] tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + + // determine the Levenberg-Marquardt parameter + lmPar = determineLMParameter(qtf, delta, diag, + internalData, solvedCols, + work1, work2, work3, lmDir, lmPar); + + // compute the new point and the norm of the evolution direction + double lmNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + lmDir[pj] = -lmDir[pj]; + currentPoint[pj] = oldX[pj] + lmDir[pj]; + double s = diag[pj] * lmDir[pj]; + lmNorm += s * s; + } + lmNorm = FastMath.sqrt(lmNorm); + // on the first iteration, adjust the initial step bound. + if (firstIteration) { + delta = FastMath.min(delta, lmNorm); + } + + // Evaluate the function at x + p and calculate its norm. + evaluationCounter.incrementCount(); + current = problem.evaluate(new ArrayRealVector(currentPoint)); + currentResiduals = current.getResiduals().toArray(); + currentCost = current.getCost(); + currentPoint = current.getPoint().toArray(); + + // compute the scaled actual reduction + double actRed = -1.0; + if (0.1 * currentCost < previousCost) { + double r = currentCost / previousCost; + actRed = 1.0 - r * r; + } + + // compute the scaled predicted reduction + // and the scaled directional derivative + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double dirJ = lmDir[pj]; + work1[j] = 0; + for (int i = 0; i <= j; ++i) { + work1[i] += weightedJacobian[i][pj] * dirJ; + } + } + double coeff1 = 0; + for (int j = 0; j < solvedCols; ++j) { + coeff1 += work1[j] * work1[j]; + } + double pc2 = previousCost * previousCost; + coeff1 /= pc2; + double coeff2 = lmPar * lmNorm * lmNorm / pc2; + double preRed = coeff1 + 2 * coeff2; + double dirDer = -(coeff1 + coeff2); + + // ratio of the actual to the predicted reduction + ratio = (preRed == 0) ? 0 : (actRed / preRed); + + // update the step bound + if (ratio <= 0.25) { + double tmp = + (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5; + if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) { + tmp = 0.1; + } + delta = tmp * FastMath.min(delta, 10.0 * lmNorm); + lmPar /= tmp; + } else if ((lmPar == 0) || (ratio >= 0.75)) { + delta = 2 * lmNorm; + lmPar *= 0.5; + } + + // test for successful iteration. + if (ratio >= 1.0e-4) { + // successful iteration, update the norm + firstIteration = false; + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double xK = diag[k] * currentPoint[k]; + xNorm += xK * xK; + } + xNorm = FastMath.sqrt(xNorm); + + // tests for convergence. + if (checker != null && checker.converged(iterationCounter.getCount(), previous, current)) { + return new OptimumImpl(current, evaluationCounter.getCount(), iterationCounter.getCount()); + } + } else { + // failed iteration, reset the previous values + currentCost = previousCost; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + currentPoint[pj] = oldX[pj]; + } + tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + // Reset "current" to previous values. + current = previous; + } + + // Default convergence criteria. + if ((FastMath.abs(actRed) <= costRelativeTolerance && + preRed <= costRelativeTolerance && + ratio <= 2.0) || + delta <= parRelativeTolerance * xNorm) { + return new OptimumImpl(current, evaluationCounter.getCount(), iterationCounter.getCount()); + } + + // tests for termination and stringent tolerances + if (FastMath.abs(actRed) <= TWO_EPS && + preRed <= TWO_EPS && + ratio <= 2.0) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE, + costRelativeTolerance); + } else if (delta <= TWO_EPS * xNorm) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE, + parRelativeTolerance); + } else if (maxCosine <= TWO_EPS) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE, + orthoTolerance); + } + } + } + } + + /** + * Holds internal data. + * This structure was created so that all optimizer fields can be "final". + * Code should be further refactored in order to not pass around arguments + * that will modified in-place (cf. "work" arrays). + */ + private static class InternalData { + /** Weighted Jacobian. */ + private final double[][] weightedJacobian; + /** Columns permutation array. */ + private final int[] permutation; + /** Rank of the Jacobian matrix. */ + private final int rank; + /** Diagonal elements of the R matrix in the QR decomposition. */ + private final double[] diagR; + /** Norms of the columns of the jacobian matrix. */ + private final double[] jacNorm; + /** Coefficients of the Householder transforms vectors. */ + private final double[] beta; + + /** + * @param weightedJacobian Weighted Jacobian. + * @param permutation Columns permutation array. + * @param rank Rank of the Jacobian matrix. + * @param diagR Diagonal elements of the R matrix in the QR decomposition. + * @param jacNorm Norms of the columns of the jacobian matrix. + * @param beta Coefficients of the Householder transforms vectors. + */ + InternalData(double[][] weightedJacobian, + int[] permutation, + int rank, + double[] diagR, + double[] jacNorm, + double[] beta) { + this.weightedJacobian = weightedJacobian; + this.permutation = permutation; + this.rank = rank; + this.diagR = diagR; + this.jacNorm = jacNorm; + this.beta = beta; + } + } + + /** + * Determines the Levenberg-Marquardt parameter. + * + *

    This implementation is a translation in Java of the MINPACK + * lmpar + * routine.

    + *

    This method sets the lmPar and lmDir attributes.

    + *

    The authors of the original fortran function are:

    + *
      + *
    • Argonne National Laboratory. MINPACK project. March 1980
    • + *
    • Burton S. Garbow
    • + *
    • Kenneth E. Hillstrom
    • + *
    • Jorge J. More
    • + *
    + *

    Luc Maisonobe did the Java translation.

    + * + * @param qy Array containing qTy. + * @param delta Upper bound on the euclidean norm of diagR * lmDir. + * @param diag Diagonal matrix. + * @param internalData Data (modified in-place in this method). + * @param solvedCols Number of solved point. + * @param work1 work array + * @param work2 work array + * @param work3 work array + * @param lmDir the "returned" LM direction will be stored in this array. + * @param lmPar the value of the LM parameter from the previous iteration. + * @return the new LM parameter + */ + private double determineLMParameter(double[] qy, double delta, double[] diag, + InternalData internalData, int solvedCols, + double[] work1, double[] work2, double[] work3, + double[] lmDir, double lmPar) { + final double[][] weightedJacobian = internalData.weightedJacobian; + final int[] permutation = internalData.permutation; + final int rank = internalData.rank; + final double[] diagR = internalData.diagR; + + final int nC = weightedJacobian[0].length; + + // compute and store in x the gauss-newton direction, if the + // jacobian is rank-deficient, obtain a least squares solution + for (int j = 0; j < rank; ++j) { + lmDir[permutation[j]] = qy[j]; + } + for (int j = rank; j < nC; ++j) { + lmDir[permutation[j]] = 0; + } + for (int k = rank - 1; k >= 0; --k) { + int pk = permutation[k]; + double ypk = lmDir[pk] / diagR[pk]; + for (int i = 0; i < k; ++i) { + lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk]; + } + lmDir[pk] = ypk; + } + + // evaluate the function at the origin, and test + // for acceptance of the Gauss-Newton direction + double dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work1[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double fp = dxNorm - delta; + if (fp <= 0.1 * delta) { + lmPar = 0; + return lmPar; + } + + // if the jacobian is not rank deficient, the Newton step provides + // a lower bound, parl, for the zero of the function, + // otherwise set this bound to zero + double sum2; + double parl = 0; + if (rank == solvedCols) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] *= diag[pj] / dxNorm; + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i < j; ++i) { + sum += weightedJacobian[i][pj] * work1[permutation[i]]; + } + double s = (work1[pj] - sum) / diagR[pj]; + work1[pj] = s; + sum2 += s * s; + } + parl = fp / (delta * sum2); + } + + // calculate an upper bound, paru, for the zero of the function + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qy[i]; + } + sum /= diag[pj]; + sum2 += sum * sum; + } + double gNorm = FastMath.sqrt(sum2); + double paru = gNorm / delta; + if (paru == 0) { + paru = Precision.SAFE_MIN / FastMath.min(delta, 0.1); + } + + // if the input par lies outside of the interval (parl,paru), + // set par to the closer endpoint + lmPar = FastMath.min(paru, FastMath.max(lmPar, parl)); + if (lmPar == 0) { + lmPar = gNorm / dxNorm; + } + + for (int countdown = 10; countdown >= 0; --countdown) { + + // evaluate the function at the current value of lmPar + if (lmPar == 0) { + lmPar = FastMath.max(Precision.SAFE_MIN, 0.001 * paru); + } + double sPar = FastMath.sqrt(lmPar); + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = sPar * diag[pj]; + } + determineLMDirection(qy, work1, work2, internalData, solvedCols, work3, lmDir); + + dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work3[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double previousFP = fp; + fp = dxNorm - delta; + + // if the function is small enough, accept the current value + // of lmPar, also test for the exceptional cases where parl is zero + if (FastMath.abs(fp) <= 0.1 * delta || + (parl == 0 && + fp <= previousFP && + previousFP < 0)) { + return lmPar; + } + + // compute the Newton correction + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = work3[pj] * diag[pj] / dxNorm; + } + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] /= work2[j]; + double tmp = work1[pj]; + for (int i = j + 1; i < solvedCols; ++i) { + work1[permutation[i]] -= weightedJacobian[i][pj] * tmp; + } + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + double s = work1[permutation[j]]; + sum2 += s * s; + } + double correction = fp / (delta * sum2); + + // depending on the sign of the function, update parl or paru. + if (fp > 0) { + parl = FastMath.max(parl, lmPar); + } else if (fp < 0) { + paru = FastMath.min(paru, lmPar); + } + + // compute an improved estimate for lmPar + lmPar = FastMath.max(parl, lmPar + correction); + } + + return lmPar; + } + + /** + * Solve a*x = b and d*x = 0 in the least squares sense. + *

    This implementation is a translation in Java of the MINPACK + * qrsolv + * routine.

    + *

    This method sets the lmDir and lmDiag attributes.

    + *

    The authors of the original fortran function are:

    + *
      + *
    • Argonne National Laboratory. MINPACK project. March 1980
    • + *
    • Burton S. Garbow
    • + *
    • Kenneth E. Hillstrom
    • + *
    • Jorge J. More
    • + *
    + *

    Luc Maisonobe did the Java translation.

    + * + * @param qy array containing qTy + * @param diag diagonal matrix + * @param lmDiag diagonal elements associated with lmDir + * @param internalData Data (modified in-place in this method). + * @param solvedCols Number of sloved point. + * @param work work array + * @param lmDir the "returned" LM direction is stored in this array + */ + private void determineLMDirection(double[] qy, double[] diag, + double[] lmDiag, + InternalData internalData, + int solvedCols, + double[] work, + double[] lmDir) { + final int[] permutation = internalData.permutation; + final double[][] weightedJacobian = internalData.weightedJacobian; + final double[] diagR = internalData.diagR; + + // copy R and Qty to preserve input and initialize s + // in particular, save the diagonal elements of R in lmDir + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + for (int i = j + 1; i < solvedCols; ++i) { + weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]]; + } + lmDir[j] = diagR[pj]; + work[j] = qy[j]; + } + + // eliminate the diagonal matrix d using a Givens rotation + for (int j = 0; j < solvedCols; ++j) { + + // prepare the row of d to be eliminated, locating the + // diagonal element using p from the Q.R. factorization + int pj = permutation[j]; + double dpj = diag[pj]; + if (dpj != 0) { + Arrays.fill(lmDiag, j + 1, lmDiag.length, 0); + } + lmDiag[j] = dpj; + + // the transformations to eliminate the row of d + // modify only a single element of Qty + // beyond the first n, which is initially zero. + double qtbpj = 0; + for (int k = j; k < solvedCols; ++k) { + int pk = permutation[k]; + + // determine a Givens rotation which eliminates the + // appropriate element in the current row of d + if (lmDiag[k] != 0) { + + final double sin; + final double cos; + double rkk = weightedJacobian[k][pk]; + if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) { + final double cotan = rkk / lmDiag[k]; + sin = 1.0 / FastMath.sqrt(1.0 + cotan * cotan); + cos = sin * cotan; + } else { + final double tan = lmDiag[k] / rkk; + cos = 1.0 / FastMath.sqrt(1.0 + tan * tan); + sin = cos * tan; + } + + // compute the modified diagonal element of R and + // the modified element of (Qty,0) + weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k]; + final double temp = cos * work[k] + sin * qtbpj; + qtbpj = -sin * work[k] + cos * qtbpj; + work[k] = temp; + + // accumulate the tranformation in the row of s + for (int i = k + 1; i < solvedCols; ++i) { + double rik = weightedJacobian[i][pk]; + final double temp2 = cos * rik + sin * lmDiag[i]; + lmDiag[i] = -sin * rik + cos * lmDiag[i]; + weightedJacobian[i][pk] = temp2; + } + } + } + + // store the diagonal element of s and restore + // the corresponding diagonal element of R + lmDiag[j] = weightedJacobian[j][permutation[j]]; + weightedJacobian[j][permutation[j]] = lmDir[j]; + } + + // solve the triangular system for z, if the system is + // singular, then obtain a least squares solution + int nSing = solvedCols; + for (int j = 0; j < solvedCols; ++j) { + if ((lmDiag[j] == 0) && (nSing == solvedCols)) { + nSing = j; + } + if (nSing < solvedCols) { + work[j] = 0; + } + } + if (nSing > 0) { + for (int j = nSing - 1; j >= 0; --j) { + int pj = permutation[j]; + double sum = 0; + for (int i = j + 1; i < nSing; ++i) { + sum += weightedJacobian[i][pj] * work[i]; + } + work[j] = (work[j] - sum) / lmDiag[j]; + } + } + + // permute the components of z back to components of lmDir + for (int j = 0; j < lmDir.length; ++j) { + lmDir[permutation[j]] = work[j]; + } + } + + /** + * Decompose a matrix A as A.P = Q.R using Householder transforms. + *

    As suggested in the P. Lascaux and R. Theodor book + * Analyse numérique matricielle appliquée à + * l'art de l'ingénieur (Masson, 1986), instead of representing + * the Householder transforms with uk unit vectors such that: + *

    +     * Hk = I - 2uk.ukt
    +     * 
    + * we use k non-unit vectors such that: + *
    +     * Hk = I - betakvk.vkt
    +     * 
    + * where vk = ak - alphak ek. + * The betak coefficients are provided upon exit as recomputing + * them from the vk vectors would be costly.

    + *

    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.

    + * + * @param jacobian Weighted Jacobian matrix at the current point. + * @param solvedCols Number of solved point. + * @return data used in other methods of this class. + * @throws ConvergenceException if the decomposition cannot be performed. + */ + private InternalData qrDecomposition(RealMatrix jacobian, + int solvedCols) throws ConvergenceException { + // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J), + // hence the multiplication by -1. + final double[][] weightedJacobian = jacobian.scalarMultiply(-1).getData(); + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + final int[] permutation = new int[nC]; + final double[] diagR = new double[nC]; + final double[] jacNorm = new double[nC]; + final double[] beta = new double[nC]; + + // initializations + for (int k = 0; k < nC; ++k) { + permutation[k] = k; + double norm2 = 0; + for (int i = 0; i < nR; ++i) { + double akk = weightedJacobian[i][k]; + norm2 += akk * akk; + } + jacNorm[k] = FastMath.sqrt(norm2); + } + + // transform the matrix column after column + for (int k = 0; k < nC; ++k) { + + // select the column with the greatest norm on active components + int nextColumn = -1; + double ak2 = Double.NEGATIVE_INFINITY; + for (int i = k; i < nC; ++i) { + double norm2 = 0; + for (int j = k; j < nR; ++j) { + double aki = weightedJacobian[j][permutation[i]]; + norm2 += aki * aki; + } + if (Double.isInfinite(norm2) || Double.isNaN(norm2)) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN, + nR, nC); + } + if (norm2 > ak2) { + nextColumn = i; + ak2 = norm2; + } + } + if (ak2 <= qrRankingThreshold) { + return new InternalData(weightedJacobian, permutation, k, diagR, jacNorm, beta); + } + int pk = permutation[nextColumn]; + permutation[nextColumn] = permutation[k]; + permutation[k] = pk; + + // choose alpha such that Hk.u = alpha ek + double akk = weightedJacobian[k][pk]; + double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2); + double betak = 1.0 / (ak2 - akk * alpha); + beta[pk] = betak; + + // transform the current column + diagR[pk] = alpha; + weightedJacobian[k][pk] -= alpha; + + // transform the remaining columns + for (int dk = nC - 1 - k; dk > 0; --dk) { + double gamma = 0; + for (int j = k; j < nR; ++j) { + gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]]; + } + gamma *= betak; + for (int j = k; j < nR; ++j) { + weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk]; + } + } + } + + return new InternalData(weightedJacobian, permutation, solvedCols, diagR, jacNorm, beta); + } + + /** + * Compute the product Qt.y for some Q.R. decomposition. + * + * @param y vector to multiply (will be overwritten with the result) + * @param internalData Data. + */ + private void qTy(double[] y, + InternalData internalData) { + final double[][] weightedJacobian = internalData.weightedJacobian; + final int[] permutation = internalData.permutation; + final double[] beta = internalData.beta; + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + for (int k = 0; k < nC; ++k) { + int pk = permutation[k]; + double gamma = 0; + for (int i = k; i < nR; ++i) { + gamma += weightedJacobian[i][pk] * y[i]; + } + gamma *= beta[pk]; + for (int i = k; i < nR; ++i) { + y[i] -= gamma * weightedJacobian[i][pk]; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java new file mode 100644 index 000000000..70e50fe02 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * A interface for functions that compute a vector of values and can compute their + * derivatives (Jacobian). + * + * @since 3.3 + */ +public interface MultivariateJacobianFunction { + + /** + * Compute the function value and its Jacobian. + * + * @param point the abscissae + * @return the values and their Jacobian of this vector valued function. + */ + Pair value(RealVector point); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java new file mode 100644 index 000000000..c359f36de --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * A pedantic implementation of {@link LeastSquaresOptimizer.Optimum}. + * + * @since 3.3 + */ +class OptimumImpl implements LeastSquaresOptimizer.Optimum { + + /** abscissa and ordinate */ + private final Evaluation value; + /** number of evaluations to compute this optimum */ + private final int evaluations; + /** number of iterations to compute this optimum */ + private final int iterations; + + /** + * Construct an optimum from an evaluation and the values of the counters. + * + * @param value the function value + * @param evaluations number of times the function was evaluated + * @param iterations number of iterations of the algorithm + */ + OptimumImpl(final Evaluation value, final int evaluations, final int iterations) { + this.value = value; + this.evaluations = evaluations; + this.iterations = iterations; + } + + /* auto-generated implementations */ + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations; + } + + /** {@inheritDoc} */ + public int getIterations() { + return iterations; + } + + /** {@inheritDoc} */ + public RealMatrix getCovariances(double threshold) { + return value.getCovariances(threshold); + } + + /** {@inheritDoc} */ + public RealVector getSigma(double covarianceSingularityThreshold) { + return value.getSigma(covarianceSingularityThreshold); + } + + /** {@inheritDoc} */ + public double getRMS() { + return value.getRMS(); + } + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return value.getJacobian(); + } + + /** {@inheritDoc} */ + public double getCost() { + return value.getCost(); + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return value.getResiduals(); + } + + /** {@inheritDoc} */ + public RealVector getPoint() { + return value.getPoint(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java new file mode 100644 index 000000000..d8ebeafa9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * Interface for validating a set of model parameters. + * + * @since 3.4 + */ +public interface ParameterValidator { + /** + * Validates the set of parameters. + * + * @param params Input parameters. + * @return the validated values. + */ + RealVector validate(RealVector params); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java new file mode 100644 index 000000000..17b414247 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * A interface for functions that compute a vector of values and can compute their + * derivatives (Jacobian). + * + * @since 3.4 + */ +public interface ValueAndJacobianFunction extends MultivariateJacobianFunction { + /** + * Compute the value. + * + * @param params Point. + * @return the value at the given point. + */ + RealVector computeValue(final double[] params); + + /** + * Compute the Jacobian. + * + * @param params Point. + * @return the Jacobian at the given point. + */ + RealMatrix computeJacobian(final double[] params); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/package-info.java new file mode 100644 index 000000000..4b4191538 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/leastsquares/package-info.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package provides algorithms that minimize the residuals + * between observations and model values. + * The {@link com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer + * least-squares optimizers} minimize the distance (called + * cost or χ2) between model and + * observations. + * + *
    + * Algorithms in this category need access to a problem + * (represented by a {@link com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem + * LeastSquaresProblem}). + * Such a model predicts a set of values which the algorithm tries to match + * with a set of given set of observed values. + *
    + * The problem can be created progressively using a {@link + * com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder builder} or it can + * be created at once using a {@link com.fr.third.org.apache.commons.math3.fitting.leastsquares.LeastSquaresFactory + * factory}. + * @since 3.3 + */ +package com.fr.third.org.apache.commons.math3.fitting.leastsquares; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/package-info.java new file mode 100644 index 000000000..f49ea081e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fitting/package-info.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Classes to perform curve fitting. + * + * Curve fitting is a special case of a least-squares problem + * where the parameters are the coefficients of a function \( f \) + * whose graph \( y = f(x) \) should pass through sample points, and + * were the objective function is the squared sum of the residuals + * \( f(x_i) - y_i \) for observed points \( (x_i, y_i) \). + */ +package com.fr.third.org.apache.commons.math3.fitting; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/AbstractFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/AbstractFormat.java new file mode 100644 index 000000000..2efa2ba1a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/AbstractFormat.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Common part shared by both {@link FractionFormat} and {@link BigFractionFormat}. + * @since 2.0 + */ +public abstract class AbstractFormat extends NumberFormat implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -6981118387974191891L; + + /** The format used for the denominator. */ + private NumberFormat denominatorFormat; + + /** The format used for the numerator. */ + private NumberFormat numeratorFormat; + + /** + * Create an improper formatting instance with the default number format + * for the numerator and denominator. + */ + protected AbstractFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create an improper formatting instance with a custom number format for + * both the numerator and denominator. + * @param format the custom format for both the numerator and denominator. + */ + protected AbstractFormat(final NumberFormat format) { + this(format, (NumberFormat) format.clone()); + } + + /** + * Create an improper formatting instance with a custom number format for + * the numerator and a custom number format for the denominator. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + protected AbstractFormat(final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + this.numeratorFormat = numeratorFormat; + this.denominatorFormat = denominatorFormat; + } + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getNumberInstance(java.util.Locale)}. The only + * customization is the maximum number of BigFraction digits, which is set to 0. + * @return the default number format. + */ + protected static NumberFormat getDefaultNumberFormat() { + return getDefaultNumberFormat(Locale.getDefault()); + } + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getNumberInstance(java.util.Locale)}. The only + * customization is the maximum number of BigFraction digits, which is set to 0. + * @param locale the specific locale used by the format. + * @return the default number format specific to the given locale. + */ + protected static NumberFormat getDefaultNumberFormat(final Locale locale) { + final NumberFormat nf = NumberFormat.getNumberInstance(locale); + nf.setMaximumFractionDigits(0); + nf.setParseIntegerOnly(true); + return nf; + } + + /** + * Access the denominator format. + * @return the denominator format. + */ + public NumberFormat getDenominatorFormat() { + return denominatorFormat; + } + + /** + * Access the numerator format. + * @return the numerator format. + */ + public NumberFormat getNumeratorFormat() { + return numeratorFormat; + } + + /** + * Modify the denominator format. + * @param format the new denominator format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setDenominatorFormat(final NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.DENOMINATOR_FORMAT); + } + this.denominatorFormat = format; + } + + /** + * Modify the numerator format. + * @param format the new numerator format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setNumeratorFormat(final NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.NUMERATOR_FORMAT); + } + this.numeratorFormat = format; + } + + /** + * Parses source until a non-whitespace character is found. + * @param source the string to parse + * @param pos input/output parsing parameter. On output, pos + * 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 source until a non-whitespace character is found. + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the first non-whitespace character. + */ + protected static char parseNextCharacter(final String source, + final ParsePosition pos) { + int index = pos.getIndex(); + final int n = source.length(); + char ret = 0; + + if (index < n) { + char c; + do { + c = source.charAt(index++); + } while (Character.isWhitespace(c) && index < n); + pos.setIndex(index); + + if (index < n) { + ret = c; + } + } + + return ret; + } + + /** + * Formats a double value as a fraction and appends the result to a StringBuffer. + * + * @param value the double value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return a reference to the appended buffer + * @see #format(Object, StringBuffer, FieldPosition) + */ + @Override + public StringBuffer format(final double value, + final StringBuffer buffer, final FieldPosition position) { + return format(Double.valueOf(value), buffer, position); + } + + + /** + * Formats a long value as a fraction and appends the result to a StringBuffer. + * + * @param value the long value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return a reference to the appended buffer + * @see #format(Object, StringBuffer, FieldPosition) + */ + @Override + public StringBuffer format(final long value, + final StringBuffer buffer, final FieldPosition position) { + return format(Long.valueOf(value), buffer, position); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFraction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFraction.java new file mode 100644 index 000000000..73ef88b68 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFraction.java @@ -0,0 +1,1226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Representation of a rational number without any overflow. This class is + * immutable. + * + * @since 2.0 + */ +public class BigFraction + extends Number + implements FieldElement, Comparable, 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; + + /** BigInteger representation of 100. */ + private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100); + + /** The numerator. */ + private final BigInteger numerator; + + /** The denominator. */ + private final BigInteger denominator; + + /** + *

    + * Create a {@link BigFraction} equivalent to the passed {@code BigInteger}, ie + * "num / 1". + *

    + * + * @param num + * the numerator. + */ + public BigFraction(final BigInteger num) { + this(num, BigInteger.ONE); + } + + /** + * Create a {@link BigFraction} given the numerator and denominator as + * {@code BigInteger}. The {@link BigFraction} is reduced to lowest terms. + * + * @param num the numerator, must not be {@code null}. + * @param den the denominator, must not be {@code null}. + * @throws ZeroException if the denominator is zero. + * @throws NullArgumentException if either of the arguments is null + */ + public BigFraction(BigInteger num, BigInteger den) { + MathUtils.checkNotNull(num, LocalizedFormats.NUMERATOR); + MathUtils.checkNotNull(den, LocalizedFormats.DENOMINATOR); + if (den.signum() == 0) { + throw new ZeroException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (num.signum() == 0) { + numerator = BigInteger.ZERO; + denominator = BigInteger.ONE; + } else { + + // reduce numerator and denominator by greatest common denominator + final BigInteger gcd = num.gcd(den); + if (BigInteger.ONE.compareTo(gcd) < 0) { + num = num.divide(gcd); + den = den.divide(gcd); + } + + // move sign to numerator + if (den.signum() == -1) { + num = num.negate(); + den = den.negate(); + } + + // store the values in the final fields + numerator = num; + denominator = den; + + } + } + + /** + * Create a fraction given the double value. + *

    + * This constructor behaves differently from + * {@link #BigFraction(double, double, int)}. It converts the double value + * exactly, considering its internal bits representation. This works for all + * values except NaN and infinities and does not requires any loop or + * convergence threshold. + *

    + *

    + * Since this conversion is exact and since double numbers are sometimes + * approximated, the fraction created may seem strange in some cases. For example, + * calling new BigFraction(1.0 / 3.0) does not create + * the fraction 1/3, but the fraction 6004799503160661 / 18014398509481984 + * because the double number passed to the constructor is not exactly 1/3 + * (this number cannot be stored exactly in IEEE754). + *

    + * @see #BigFraction(double, double, int) + * @param value the double value to convert to a fraction. + * @exception MathIllegalArgumentException if value is NaN or infinite + */ + public BigFraction(final double value) throws MathIllegalArgumentException { + if (Double.isNaN(value)) { + throw new MathIllegalArgumentException(LocalizedFormats.NAN_VALUE_CONVERSION); + } + if (Double.isInfinite(value)) { + throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_VALUE_CONVERSION); + } + + // compute m and k such that value = m * 2^k + final long bits = Double.doubleToLongBits(value); + final long sign = bits & 0x8000000000000000L; + final long exponent = bits & 0x7ff0000000000000L; + long m = bits & 0x000fffffffffffffL; + if (exponent != 0) { + // this was a normalized number, add the implicit most significant bit + m |= 0x0010000000000000L; + } + if (sign != 0) { + m = -m; + } + int k = ((int) (exponent >> 52)) - 1075; + while (((m & 0x001ffffffffffffeL) != 0) && ((m & 0x1) == 0)) { + m >>= 1; + ++k; + } + + if (k < 0) { + numerator = BigInteger.valueOf(m); + denominator = BigInteger.ZERO.flipBit(-k); + } else { + numerator = BigInteger.valueOf(m).multiply(BigInteger.ZERO.flipBit(k)); + denominator = BigInteger.ONE; + } + + } + + /** + * Create a fraction given the double value and maximum error allowed. + *

    + * References: + *

    + *

    + * + * @param value + * the double value to convert to a fraction. + * @param epsilon + * maximum error allowed. The resulting fraction is within + * epsilon of value, 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. + *

    + * + * 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). + *

    + *

    + * + * It has been done this way so that the same code can be (re)used for both + * scenarios. However this could be confusing to users if it were part of + * the public API and this constructor should therefore remain PRIVATE. + *

    + * + * See JIRA issue ticket MATH-181 for more details: + * + * 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 + * epsilon of value, in absolute terms. + * @param maxDenominator + * maximum denominator value allowed. + * @param maxIterations + * maximum number of convergents. + * @throws FractionConversionException + * if the continued fraction failed to converge. + */ + private BigFraction(final double value, final double epsilon, + final int maxDenominator, int maxIterations) + throws FractionConversionException { + long overflow = Integer.MAX_VALUE; + double r0 = value; + long a0 = (long) FastMath.floor(r0); + + if (FastMath.abs(a0) > overflow) { + throw new FractionConversionException(value, a0, 1l); + } + + // check for (almost) integer arguments, which should not go + // to iterations. + if (FastMath.abs(a0 - value) < epsilon) { + numerator = BigInteger.valueOf(a0); + denominator = BigInteger.ONE; + return; + } + + long p0 = 1; + long q0 = 0; + long p1 = a0; + long q1 = 1; + + long p2 = 0; + long q2 = 1; + + int n = 0; + boolean stop = false; + do { + ++n; + final double r1 = 1.0 / (r0 - a0); + final long a1 = (long) FastMath.floor(r1); + p2 = (a1 * p1) + p0; + q2 = (a1 * q1) + q0; + if ((p2 > overflow) || (q2 > overflow)) { + // in maxDenominator mode, if the last fraction was very close to the actual value + // q2 may overflow in the next iteration; in this case return the last one. + if (epsilon == 0.0 && FastMath.abs(q1) < maxDenominator) { + break; + } + throw new FractionConversionException(value, p2, q2); + } + + final double convergent = (double) p2 / (double) q2; + if ((n < maxIterations) && + (FastMath.abs(convergent - value) > epsilon) && + (q2 < maxDenominator)) { + p0 = p1; + p1 = p2; + q0 = q1; + q1 = q2; + a0 = a1; + r0 = r1; + } else { + stop = true; + } + } while (!stop); + + if (n >= maxIterations) { + throw new FractionConversionException(value, maxIterations); + } + + if (q2 < maxDenominator) { + numerator = BigInteger.valueOf(p2); + denominator = BigInteger.valueOf(q2); + } else { + numerator = BigInteger.valueOf(p1); + denominator = BigInteger.valueOf(q1); + } + } + + /** + * Create a fraction given the double value and maximum denominator. + *

    + * References: + *

    + *

    + * + * @param value + * the double value to convert to a fraction. + * @param maxDenominator + * The maximum allowed value for denominator. + * @throws FractionConversionException + * if the continued fraction failed to converge. + */ + public BigFraction(final double value, final int maxDenominator) + throws FractionConversionException { + this(value, 0, maxDenominator, 100); + } + + /** + *

    + * Create a {@link BigFraction} equivalent to the passed {@code int}, ie + * "num / 1". + *

    + * + * @param num + * the numerator. + */ + public BigFraction(final int num) { + this(BigInteger.valueOf(num), BigInteger.ONE); + } + + /** + *

    + * Create a {@link BigFraction} given the numerator and denominator as simple + * {@code int}. The {@link BigFraction} is reduced to lowest terms. + *

    + * + * @param num + * the numerator. + * @param den + * the denominator. + */ + public BigFraction(final int num, final int den) { + this(BigInteger.valueOf(num), BigInteger.valueOf(den)); + } + + /** + *

    + * Create a {@link BigFraction} equivalent to the passed long, ie "num / 1". + *

    + * + * @param num + * the numerator. + */ + public BigFraction(final long num) { + this(BigInteger.valueOf(num), BigInteger.ONE); + } + + /** + *

    + * Create a {@link BigFraction} given the numerator and denominator as simple + * {@code long}. The {@link BigFraction} is reduced to lowest terms. + *

    + * + * @param num + * the numerator. + * @param den + * the denominator. + */ + public BigFraction(final long num, final long den) { + this(BigInteger.valueOf(num), BigInteger.valueOf(den)); + } + + /** + *

    + * Creates a BigFraction instance with the 2 parts of a fraction + * Y/Z. + *

    + * + *

    + * Any negative signs are resolved to be on the numerator. + *

    + * + * @param numerator + * the numerator, for example the three in 'three sevenths'. + * @param denominator + * the denominator, for example the seven in 'three sevenths'. + * @return a new fraction instance, with the numerator and denominator + * reduced. + * @throws ArithmeticException + * if the denominator is zero. + */ + public static BigFraction getReducedFraction(final int numerator, + final int denominator) { + if (numerator == 0) { + return ZERO; // normalize zero. + } + + return new BigFraction(numerator, denominator); + } + + /** + *

    + * Returns the absolute value of this {@link BigFraction}. + *

    + * + * @return the absolute value as a {@link BigFraction}. + */ + public BigFraction abs() { + return (numerator.signum() == 1) ? this : negate(); + } + + /** + *

    + * Adds the value of this fraction to the passed {@link BigInteger}, + * returning the result in reduced form. + *

    + * + * @param bg + * the {@link BigInteger} to add, must'nt be null. + * @return a BigFraction instance with the resulting values. + * @throws NullArgumentException + * if the {@link BigInteger} is null. + */ + public BigFraction add(final BigInteger bg) throws NullArgumentException { + MathUtils.checkNotNull(bg); + + if (numerator.signum() == 0) { + return new BigFraction(bg); + } + if (bg.signum() == 0) { + return this; + } + + return new BigFraction(numerator.add(denominator.multiply(bg)), denominator); + } + + /** + *

    + * Adds the value of this fraction to the passed {@code integer}, returning + * the result in reduced form. + *

    + * + * @param i + * the {@code integer} to add. + * @return a BigFraction instance with the resulting values. + */ + public BigFraction add(final int i) { + return add(BigInteger.valueOf(i)); + } + + /** + *

    + * Adds the value of this fraction to the passed {@code long}, returning + * the result in reduced form. + *

    + * + * @param l + * the {@code long} to add. + * @return a BigFraction instance with the resulting values. + */ + public BigFraction add(final long l) { + return add(BigInteger.valueOf(l)); + } + + /** + *

    + * Adds the value of this fraction to another, returning the result in + * reduced form. + *

    + * + * @param fraction + * the {@link BigFraction} to add, must not be null. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@link BigFraction} is {@code null}. + */ + public BigFraction add(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return fraction; + } + + BigInteger num = null; + BigInteger den = null; + + if (denominator.equals(fraction.denominator)) { + num = numerator.add(fraction.numerator); + den = denominator; + } else { + num = (numerator.multiply(fraction.denominator)).add((fraction.numerator).multiply(denominator)); + den = denominator.multiply(fraction.denominator); + } + + if (num.signum() == 0) { + return ZERO; + } + + return new BigFraction(num, den); + + } + + /** + *

    + * Gets the fraction as a BigDecimal. This calculates the + * fraction as the numerator divided by denominator. + *

    + * + * @return the fraction as a BigDecimal. + * @throws ArithmeticException + * if the exact quotient does not have a terminating decimal + * expansion. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue() { + return new BigDecimal(numerator).divide(new BigDecimal(denominator)); + } + + /** + *

    + * Gets the fraction as a BigDecimal following the passed + * rounding mode. This calculates the fraction as the numerator divided by + * denominator. + *

    + * + * @param roundingMode + * rounding mode to apply. see {@link BigDecimal} constants. + * @return the fraction as a BigDecimal. + * @throws IllegalArgumentException + * if {@code roundingMode} does not represent a valid rounding + * mode. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue(final int roundingMode) { + return new BigDecimal(numerator).divide(new BigDecimal(denominator), roundingMode); + } + + /** + *

    + * Gets the fraction as a BigDecimal following the passed scale + * and rounding mode. This calculates the fraction as the numerator divided + * by denominator. + *

    + * + * @param scale + * scale of the BigDecimal 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 BigDecimal. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue(final int scale, final int roundingMode) { + return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode); + } + + /** + *

    + * Compares this object to another based on size. + *

    + * + * @param object + * the object to compare to, must not be null. + * @return -1 if this is less than {@code object}, +1 if this is greater + * than {@code object}, 0 if they are equal. + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(final BigFraction object) { + int lhsSigNum = numerator.signum(); + int rhsSigNum = object.numerator.signum(); + + if (lhsSigNum != rhsSigNum) { + return (lhsSigNum > rhsSigNum) ? 1 : -1; + } + if (lhsSigNum == 0) { + return 0; + } + + BigInteger nOd = numerator.multiply(object.denominator); + BigInteger dOn = denominator.multiply(object.numerator); + return nOd.compareTo(dOn); + } + + /** + *

    + * Divide the value of this fraction by the passed {@code BigInteger}, + * ie {@code this * 1 / bg}, returning the result in reduced form. + *

    + * + * @param bg the {@code BigInteger} to divide by, must not be {@code null} + * @return a {@link BigFraction} instance with the resulting values + * @throws NullArgumentException if the {@code BigInteger} is {@code null} + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (bg.signum() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (numerator.signum() == 0) { + return ZERO; + } + return new BigFraction(numerator, denominator.multiply(bg)); + } + + /** + *

    + * Divide the value of this fraction by the passed {@code int}, ie + * {@code this * 1 / i}, returning the result in reduced form. + *

    + * + * @param i the {@code int} to divide by + * @return a {@link BigFraction} instance with the resulting values + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final int i) { + return divide(BigInteger.valueOf(i)); + } + + /** + *

    + * Divide the value of this fraction by the passed {@code long}, ie + * {@code this * 1 / l}, returning the result in reduced form. + *

    + * + * @param l the {@code long} to divide by + * @return a {@link BigFraction} instance with the resulting values + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final long l) { + return divide(BigInteger.valueOf(l)); + } + + /** + *

    + * Divide the value of this fraction by another, returning the result in + * reduced form. + *

    + * + * @param fraction Fraction to divide by, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@code fraction} is {@code null}. + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (numerator.signum() == 0) { + return ZERO; + } + + return multiply(fraction.reciprocal()); + } + + /** + *

    + * Gets the fraction as a {@code double}. This calculates the fraction as + * the numerator divided by denominator. + *

    + * + * @return the fraction as a {@code double} + * @see java.lang.Number#doubleValue() + */ + @Override + public double doubleValue() { + double result = numerator.doubleValue() / denominator.doubleValue(); + if (Double.isNaN(result)) { + // Numerator and/or denominator must be out of range: + // Calculate how far to shift them to put them in range. + int shift = FastMath.max(numerator.bitLength(), + denominator.bitLength()) - FastMath.getExponent(Double.MAX_VALUE); + result = numerator.shiftRight(shift).doubleValue() / + denominator.shiftRight(shift).doubleValue(); + } + return result; + } + + /** + *

    + * Test for the equality of two fractions. If the lowest term numerator and + * denominators are the same for both fractions, the two fractions are + * considered to be equal. + *

    + * + * @param other + * fraction to test for equality to this fraction, can be + * null. + * @return true if two fractions are equal, false if object is + * null, not an instance of {@link BigFraction}, or not + * equal to this fraction instance. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object other) { + boolean ret = false; + + if (this == other) { + ret = true; + } else if (other instanceof BigFraction) { + BigFraction rhs = ((BigFraction) other).reduce(); + BigFraction thisOne = this.reduce(); + ret = thisOne.numerator.equals(rhs.numerator) && thisOne.denominator.equals(rhs.denominator); + } + + return ret; + } + + /** + *

    + * Gets the fraction as a {@code float}. This calculates the fraction as + * the numerator divided by denominator. + *

    + * + * @return the fraction as a {@code float}. + * @see java.lang.Number#floatValue() + */ + @Override + public float floatValue() { + float result = numerator.floatValue() / denominator.floatValue(); + if (Double.isNaN(result)) { + // Numerator and/or denominator must be out of range: + // Calculate how far to shift them to put them in range. + int shift = FastMath.max(numerator.bitLength(), + denominator.bitLength()) - FastMath.getExponent(Float.MAX_VALUE); + result = numerator.shiftRight(shift).floatValue() / + denominator.shiftRight(shift).floatValue(); + } + return result; + } + + /** + *

    + * Access the denominator as a BigInteger. + *

    + * + * @return the denominator as a BigInteger. + */ + public BigInteger getDenominator() { + return denominator; + } + + /** + *

    + * Access the denominator as a {@code int}. + *

    + * + * @return the denominator as a {@code int}. + */ + public int getDenominatorAsInt() { + return denominator.intValue(); + } + + /** + *

    + * Access the denominator as a {@code long}. + *

    + * + * @return the denominator as a {@code long}. + */ + public long getDenominatorAsLong() { + return denominator.longValue(); + } + + /** + *

    + * Access the numerator as a BigInteger. + *

    + * + * @return the numerator as a BigInteger. + */ + public BigInteger getNumerator() { + return numerator; + } + + /** + *

    + * Access the numerator as a {@code int}. + *

    + * + * @return the numerator as a {@code int}. + */ + public int getNumeratorAsInt() { + return numerator.intValue(); + } + + /** + *

    + * Access the numerator as a {@code long}. + *

    + * + * @return the numerator as a {@code long}. + */ + public long getNumeratorAsLong() { + return numerator.longValue(); + } + + /** + *

    + * Gets a hashCode for the fraction. + *

    + * + * @return a hash code value for this object. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 37 * (37 * 17 + numerator.hashCode()) + denominator.hashCode(); + } + + /** + *

    + * Gets the fraction as an {@code int}. This returns the whole number part + * of the fraction. + *

    + * + * @return the whole number fraction part. + * @see java.lang.Number#intValue() + */ + @Override + public int intValue() { + return numerator.divide(denominator).intValue(); + } + + /** + *

    + * Gets the fraction as a {@code long}. This returns the whole number part + * of the fraction. + *

    + * + * @return the whole number fraction part. + * @see java.lang.Number#longValue() + */ + @Override + public long longValue() { + return numerator.divide(denominator).longValue(); + } + + /** + *

    + * Multiplies the value of this fraction by the passed + * BigInteger, returning the result in reduced form. + *

    + * + * @param bg the {@code BigInteger} to multiply by. + * @return a {@code BigFraction} instance with the resulting values. + * @throws NullArgumentException if {@code bg} is {@code null}. + */ + public BigFraction multiply(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(); + } + if (numerator.signum() == 0 || bg.signum() == 0) { + return ZERO; + } + return new BigFraction(bg.multiply(numerator), denominator); + } + + /** + *

    + * Multiply the value of this fraction by the passed {@code int}, returning + * the result in reduced form. + *

    + * + * @param i + * the {@code int} to multiply by. + * @return a {@link BigFraction} instance with the resulting values. + */ + public BigFraction multiply(final int i) { + if (i == 0 || numerator.signum() == 0) { + return ZERO; + } + + return multiply(BigInteger.valueOf(i)); + } + + /** + *

    + * Multiply the value of this fraction by the passed {@code long}, + * returning the result in reduced form. + *

    + * + * @param l + * the {@code long} to multiply by. + * @return a {@link BigFraction} instance with the resulting values. + */ + public BigFraction multiply(final long l) { + if (l == 0 || numerator.signum() == 0) { + return ZERO; + } + + return multiply(BigInteger.valueOf(l)); + } + + /** + *

    + * Multiplies the value of this fraction by another, returning the result in + * reduced form. + *

    + * + * @param fraction Fraction to multiply by, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if {@code fraction} is {@code null}. + */ + public BigFraction multiply(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (numerator.signum() == 0 || + fraction.numerator.signum() == 0) { + return ZERO; + } + return new BigFraction(numerator.multiply(fraction.numerator), + denominator.multiply(fraction.denominator)); + } + + /** + *

    + * Return the additive inverse of this fraction, returning the result in + * reduced form. + *

    + * + * @return the negation of this fraction. + */ + public BigFraction negate() { + return new BigFraction(numerator.negate(), denominator); + } + + /** + *

    + * Gets the fraction percentage as a {@code double}. This calculates the + * fraction as the numerator divided by denominator multiplied by 100. + *

    + * + * @return the fraction percentage as a {@code double}. + */ + public double percentageValue() { + return multiply(ONE_HUNDRED).doubleValue(); + } + + /** + *

    + * Returns a {@code BigFraction} whose value is + * {@code (thisexponent)}, returning the result in reduced form. + *

    + * + * @param exponent + * exponent to which this {@code BigFraction} is to be + * raised. + * @return thisexponent. + */ + public BigFraction pow(final int exponent) { + if (exponent == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent < 0) { + return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent)); + } + return new BigFraction(numerator.pow(exponent), denominator.pow(exponent)); + } + + /** + *

    + * Returns a BigFraction whose value is + * (thisexponent), returning the result in reduced form. + *

    + * + * @param exponent + * exponent to which this BigFraction is to be raised. + * @return thisexponent as a BigFraction. + */ + public BigFraction pow(final long exponent) { + if (exponent == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent < 0) { + return new BigFraction(ArithmeticUtils.pow(denominator, -exponent), + ArithmeticUtils.pow(numerator, -exponent)); + } + return new BigFraction(ArithmeticUtils.pow(numerator, exponent), + ArithmeticUtils.pow(denominator, exponent)); + } + + /** + *

    + * Returns a BigFraction whose value is + * (thisexponent), returning the result in reduced form. + *

    + * + * @param exponent + * exponent to which this BigFraction is to be raised. + * @return thisexponent as a BigFraction. + */ + public BigFraction pow(final BigInteger exponent) { + if (exponent.signum() == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent.signum() == -1) { + final BigInteger eNeg = exponent.negate(); + return new BigFraction(ArithmeticUtils.pow(denominator, eNeg), + ArithmeticUtils.pow(numerator, eNeg)); + } + return new BigFraction(ArithmeticUtils.pow(numerator, exponent), + ArithmeticUtils.pow(denominator, exponent)); + } + + /** + *

    + * Returns a double whose value is + * (thisexponent), returning the result in reduced form. + *

    + * + * @param exponent + * exponent to which this BigFraction is to be raised. + * @return thisexponent. + */ + public double pow(final double exponent) { + return FastMath.pow(numerator.doubleValue(), exponent) / + FastMath.pow(denominator.doubleValue(), exponent); + } + + /** + *

    + * Return the multiplicative inverse of this fraction. + *

    + * + * @return the reciprocal fraction. + */ + public BigFraction reciprocal() { + return new BigFraction(denominator, numerator); + } + + /** + *

    + * Reduce this BigFraction to its lowest terms. + *

    + * + * @return the reduced BigFraction. It doesn't change anything if + * the fraction can be reduced. + */ + public BigFraction reduce() { + final BigInteger gcd = numerator.gcd(denominator); + + if (BigInteger.ONE.compareTo(gcd) < 0) { + return new BigFraction(numerator.divide(gcd), denominator.divide(gcd)); + } else { + return this; + } + } + + /** + *

    + * Subtracts the value of an {@link BigInteger} from the value of this + * {@code BigFraction}, returning the result in reduced form. + *

    + * + * @param bg the {@link BigInteger} to subtract, cannot be {@code null}. + * @return a {@code BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@link BigInteger} is {@code null}. + */ + public BigFraction subtract(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(); + } + if (bg.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return new BigFraction(bg.negate()); + } + + return new BigFraction(numerator.subtract(denominator.multiply(bg)), denominator); + } + + /** + *

    + * Subtracts the value of an {@code integer} from the value of this + * {@code BigFraction}, returning the result in reduced form. + *

    + * + * @param i the {@code integer} to subtract. + * @return a {@code BigFraction} instance with the resulting values. + */ + public BigFraction subtract(final int i) { + return subtract(BigInteger.valueOf(i)); + } + + /** + *

    + * Subtracts the value of a {@code long} from the value of this + * {@code BigFraction}, returning the result in reduced form. + *

    + * + * @param l the {@code long} to subtract. + * @return a {@code BigFraction} instance with the resulting values. + */ + public BigFraction subtract(final long l) { + return subtract(BigInteger.valueOf(l)); + } + + /** + *

    + * Subtracts the value of another fraction from the value of this one, + * returning the result in reduced form. + *

    + * + * @param fraction {@link BigFraction} to subtract, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values + * @throws NullArgumentException if the {@code fraction} is {@code null}. + */ + public BigFraction subtract(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return fraction.negate(); + } + + BigInteger num = null; + BigInteger den = null; + if (denominator.equals(fraction.denominator)) { + num = numerator.subtract(fraction.numerator); + den = denominator; + } else { + num = (numerator.multiply(fraction.denominator)).subtract((fraction.numerator).multiply(denominator)); + den = denominator.multiply(fraction.denominator); + } + return new BigFraction(num, den); + + } + + /** + *

    + * Returns the String representing this fraction, ie + * "num / dem" or just "num" if the denominator is one. + *

    + * + * @return a string representation of the fraction. + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String str = null; + if (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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionField.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionField.java new file mode 100644 index 000000000..c484536db --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionField.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Representation of the fractional numbers without any overflow field. + *

    + * This class is a singleton. + *

    + * @see Fraction + * @since 2.0 + */ +public class BigFractionField implements Field, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1699294557189741703L; + + /** Private constructor for the singleton. + */ + private BigFractionField() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static BigFractionField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public BigFraction getOne() { + return BigFraction.ONE; + } + + /** {@inheritDoc} */ + public BigFraction getZero() { + return BigFraction.ZERO; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return BigFraction.class; + } + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final BigFractionField INSTANCE = new BigFractionField(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionFormat.java new file mode 100644 index 000000000..37c45a97c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/BigFractionFormat.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 com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Formats a BigFraction number in proper format or improper format. + *

    + * The number format for each of the whole number, numerator and, + * denominator can be configured. + *

    + * + * @since 2.0 + */ +public class BigFractionFormat extends AbstractFormat implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -2932167925527338976L; + + /** + * Create an improper formatting instance with the default number format + * for the numerator and denominator. + */ + public BigFractionFormat() { + } + + /** + * Create an improper formatting instance with a custom number format for + * both the numerator and denominator. + * @param format the custom format for both the numerator and denominator. + */ + public BigFractionFormat(final NumberFormat format) { + super(format); + } + + /** + * Create an improper formatting instance with a custom number format for + * the numerator and a custom number format for the denominator. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public BigFractionFormat(final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + } + + /** + * Get the set of locales for which complex formats are available. This + * is the same set as the {@link NumberFormat} set. + * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * This static method calls formatBigFraction() on a default instance of + * BigFractionFormat. + * + * @param f BigFraction object to format + * @return A formatted BigFraction in proper form. + */ + public static String formatBigFraction(final BigFraction f) { + return getImproperInstance().format(f); + } + + /** + * Returns the default complex format for the current locale. + * @return the default complex format. + */ + public static BigFractionFormat getImproperInstance() { + return getImproperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getImproperInstance(final Locale locale) { + return new BigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Returns the default complex format for the current locale. + * @return the default complex format. + */ + public static BigFractionFormat getProperInstance() { + return getProperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getProperInstance(final Locale locale) { + return new ProperBigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction is + * output in improper format. + * + * @param BigFraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(final BigFraction BigFraction, + final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + getNumeratorFormat().format(BigFraction.getNumerator(), toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(BigFraction.getDenominator(), toAppendTo, pos); + + return toAppendTo; + } + + /** + * Formats an object and appends the result to a StringBuffer. + * obj must be either a {@link BigFraction} object or a + * {@link BigInteger} object or a {@link Number} object. Any other type of + * object will result in an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) + * @throws MathIllegalArgumentException if obj is not a valid type. + */ + @Override + public StringBuffer format(final Object obj, + final StringBuffer toAppendTo, final FieldPosition pos) { + + final StringBuffer ret; + if (obj instanceof BigFraction) { + ret = format((BigFraction) obj, toAppendTo, pos); + } else if (obj instanceof BigInteger) { + ret = format(new BigFraction((BigInteger) obj), toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new BigFraction(((Number) obj).doubleValue()), + toAppendTo, pos); + } else { + throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION); + } + + return ret; + } + + /** + * Parses a string to produce a {@link BigFraction} object. + * @param source the string to parse + * @return the parsed {@link BigFraction} object. + * @exception MathParseException if the beginning of the specified string + * cannot be parsed. + */ + @Override + public BigFraction parse(final String source) throws MathParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final BigFraction result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, parsePosition.getErrorIndex(), BigFraction.class); + } + return result; + } + + /** + * Parses a string to produce a {@link BigFraction} object. + * This method expects the string to be formatted as an improper BigFraction. + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the parsed {@link BigFraction} object. + */ + @Override + public BigFraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + final BigInteger num = parseNextBigInteger(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0 : + // no '/' + // return num as a BigFraction + return new BigFraction(num); + case '/' : + // found '/', continue parsing denominator + break; + default : + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final BigInteger den = parseNextBigInteger(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + return new BigFraction(num, den); + } + + /** + * Parses a string to produce a BigInteger. + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return a parsed BigInteger 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/Fraction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/Fraction.java new file mode 100644 index 000000000..69ff6f4b6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/Fraction.java @@ -0,0 +1,673 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; +import java.math.BigInteger; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Representation of a rational number. + * + * implements Serializable since 2.0 + * + * @since 1.1 + */ +public class Fraction + extends Number + implements FieldElement, Comparable, Serializable { + + /** A fraction representing "2 / 1". */ + public static final Fraction TWO = new Fraction(2, 1); + + /** A fraction representing "1". */ + public static final Fraction ONE = new Fraction(1, 1); + + /** A fraction representing "0". */ + public static final Fraction ZERO = new Fraction(0, 1); + + /** A fraction representing "4/5". */ + public static final Fraction FOUR_FIFTHS = new Fraction(4, 5); + + /** A fraction representing "1/5". */ + public static final Fraction ONE_FIFTH = new Fraction(1, 5); + + /** A fraction representing "1/2". */ + public static final Fraction ONE_HALF = new Fraction(1, 2); + + /** A fraction representing "1/4". */ + public static final Fraction ONE_QUARTER = new Fraction(1, 4); + + /** A fraction representing "1/3". */ + public static final Fraction ONE_THIRD = new Fraction(1, 3); + + /** A fraction representing "3/5". */ + public static final Fraction THREE_FIFTHS = new Fraction(3, 5); + + /** A fraction representing "3/4". */ + public static final Fraction THREE_QUARTERS = new Fraction(3, 4); + + /** A fraction representing "2/5". */ + public static final Fraction TWO_FIFTHS = new Fraction(2, 5); + + /** A fraction representing "2/4". */ + public static final Fraction TWO_QUARTERS = new Fraction(2, 4); + + /** A fraction representing "2/3". */ + public static final Fraction TWO_THIRDS = new Fraction(2, 3); + + /** A fraction representing "-1 / 1". */ + public static final Fraction MINUS_ONE = new Fraction(-1, 1); + + /** Serializable version identifier */ + private static final long serialVersionUID = 3698073679419233275L; + + /** The default epsilon used for convergence. */ + private static final double DEFAULT_EPSILON = 1e-5; + + /** The denominator. */ + private final int denominator; + + /** The numerator. */ + private final int numerator; + + /** + * Create a fraction given the double value. + * @param value the double value to convert to a fraction. + * @throws FractionConversionException if the continued fraction failed to + * converge. + */ + public Fraction(double value) throws FractionConversionException { + this(value, DEFAULT_EPSILON, 100); + } + + /** + * Create a fraction given the double value and maximum error allowed. + *

    + * References: + *

    + *

    + * @param value the double value to convert to a fraction. + * @param epsilon maximum error allowed. The resulting fraction is within + * {@code epsilon} of {@code value}, in absolute terms. + * @param maxIterations maximum number of convergents + * @throws FractionConversionException if the continued fraction failed to + * converge. + */ + public Fraction(double value, double epsilon, int maxIterations) + throws FractionConversionException + { + this(value, epsilon, Integer.MAX_VALUE, maxIterations); + } + + /** + * Create a fraction given the double value and maximum denominator. + *

    + * References: + *

    + *

    + * @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. + *

    + * + * 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). + *

    + * + * It has been done this way so that the same code can be (re)used for both + * scenarios. However this could be confusing to users if it were part of + * the public API and this constructor should therefore remain PRIVATE. + *

    + * + * See JIRA issue ticket MATH-181 for more details: + * + * https://issues.apache.org/jira/browse/MATH-181 + * + * @param value the double value to convert to a fraction. + * @param epsilon maximum error allowed. The resulting fraction is within + * {@code epsilon} of {@code value}, in absolute terms. + * @param maxDenominator maximum denominator value allowed. + * @param maxIterations maximum number of convergents + * @throws FractionConversionException if the continued fraction failed to + * converge. + */ + private Fraction(double value, double epsilon, int maxDenominator, int maxIterations) + throws FractionConversionException + { + long overflow = Integer.MAX_VALUE; + double r0 = value; + long a0 = (long)FastMath.floor(r0); + if (FastMath.abs(a0) > overflow) { + throw new FractionConversionException(value, a0, 1l); + } + + // check for (almost) integer arguments, which should not go to iterations. + if (FastMath.abs(a0 - value) < epsilon) { + this.numerator = (int) a0; + this.denominator = 1; + return; + } + + long p0 = 1; + long q0 = 0; + long p1 = a0; + long q1 = 1; + + long p2 = 0; + long q2 = 1; + + int n = 0; + boolean stop = false; + do { + ++n; + double r1 = 1.0 / (r0 - a0); + long a1 = (long)FastMath.floor(r1); + p2 = (a1 * p1) + p0; + q2 = (a1 * q1) + q0; + + if ((FastMath.abs(p2) > overflow) || (FastMath.abs(q2) > overflow)) { + // in maxDenominator mode, if the last fraction was very close to the actual value + // q2 may overflow in the next iteration; in this case return the last one. + if (epsilon == 0.0 && FastMath.abs(q1) < maxDenominator) { + break; + } + throw new FractionConversionException(value, p2, q2); + } + + double convergent = (double)p2 / (double)q2; + if (n < maxIterations && FastMath.abs(convergent - value) > epsilon && q2 < maxDenominator) { + p0 = p1; + p1 = p2; + q0 = q1; + q1 = q2; + a0 = a1; + r0 = r1; + } else { + stop = true; + } + } while (!stop); + + if (n >= maxIterations) { + throw new FractionConversionException(value, maxIterations); + } + + if (q2 < maxDenominator) { + this.numerator = (int) p2; + this.denominator = (int) q2; + } else { + this.numerator = (int) p1; + this.denominator = (int) q1; + } + + } + + /** + * Create a fraction from an int. + * The fraction is num / 1. + * @param num the numerator. + */ + public Fraction(int num) { + this(num, 1); + } + + /** + * Create a fraction given the numerator and denominator. The fraction is + * reduced to lowest terms. + * @param num the numerator. + * @param den the denominator. + * @throws MathArithmeticException if the denominator is {@code zero} + */ + public Fraction(int num, int den) { + if (den == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, + num, den); + } + if (den < 0) { + if (num == Integer.MIN_VALUE || + den == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_FRACTION, + num, den); + } + num = -num; + den = -den; + } + // reduce numerator and denominator by greatest common denominator. + final int d = ArithmeticUtils.gcd(num, den); + if (d > 1) { + num /= d; + den /= d; + } + + // move sign to numerator. + if (den < 0) { + num = -num; + den = -den; + } + this.numerator = num; + this.denominator = den; + } + + /** + * Returns the absolute value of this fraction. + * @return the absolute value. + */ + public Fraction abs() { + Fraction ret; + if (numerator >= 0) { + ret = this; + } else { + ret = negate(); + } + return ret; + } + + /** + * Compares this object to another based on size. + * @param object the object to compare to + * @return -1 if this is less than {@code object}, +1 if this is greater + * than {@code object}, 0 if they are equal. + */ + public int compareTo(Fraction object) { + long nOd = ((long) numerator) * object.denominator; + long dOn = ((long) denominator) * object.numerator; + return (nOd < dOn) ? -1 : ((nOd > dOn) ? +1 : 0); + } + + /** + * Gets the fraction as a {@code double}. This calculates the fraction as + * the numerator divided by denominator. + * @return the fraction as a {@code double} + */ + @Override + public double doubleValue() { + return (double)numerator / (double)denominator; + } + + /** + * Test for the equality of two fractions. If the lowest term + * numerator and denominators are the same for both fractions, the two + * fractions are considered to be equal. + * @param other fraction to test for equality to this fraction + * @return true if two fractions are equal, false if object is + * {@code null}, not an instance of {@link Fraction}, or not equal + * to this fraction instance. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Fraction) { + // since fractions are always in lowest terms, numerators and + // denominators can be compared directly for equality. + Fraction rhs = (Fraction)other; + return (numerator == rhs.numerator) && + (denominator == rhs.denominator); + } + return false; + } + + /** + * Gets the fraction as a {@code float}. This calculates the fraction as + * the numerator divided by denominator. + * @return the fraction as a {@code float} + */ + @Override + public float floatValue() { + return (float)doubleValue(); + } + + /** + * Access the denominator. + * @return the denominator. + */ + public int getDenominator() { + return denominator; + } + + /** + * Access the numerator. + * @return the numerator. + */ + public int getNumerator() { + return numerator; + } + + /** + * Gets a hashCode for the fraction. + * @return a hash code value for this object + */ + @Override + public int hashCode() { + return 37 * (37 * 17 + numerator) + denominator; + } + + /** + * Gets the fraction as an {@code int}. This returns the whole number part + * of the fraction. + * @return the whole number fraction part + */ + @Override + public int intValue() { + return (int)doubleValue(); + } + + /** + * Gets the fraction as a {@code long}. This returns the whole number part + * of the fraction. + * @return the whole number fraction part + */ + @Override + public long longValue() { + return (long)doubleValue(); + } + + /** + * Return the additive inverse of this fraction. + * @return the negation of this fraction. + */ + public Fraction negate() { + if (numerator==Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator); + } + return new Fraction(-numerator, denominator); + } + + /** + * Return the multiplicative inverse of this fraction. + * @return the reciprocal fraction + */ + public Fraction reciprocal() { + return new Fraction(denominator, numerator); + } + + /** + *

    Adds the value of this fraction to another, returning the result in reduced form. + * The algorithm follows Knuth, 4.5.1.

    + * + * @param fraction the fraction to add, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction add(Fraction fraction) { + return addSub(fraction, true /* add */); + } + + /** + * Add an integer to the fraction. + * @param i the {@code integer} to add. + * @return this + i + */ + public Fraction add(final int i) { + return new Fraction(numerator + i * denominator, denominator); + } + + /** + *

    Subtracts the value of another fraction from the value of this one, + * returning the result in reduced form.

    + * + * @param fraction the fraction to subtract, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator + * cannot be represented in an {@code int}. + */ + public Fraction subtract(Fraction fraction) { + return addSub(fraction, false /* subtract */); + } + + /** + * Subtract an integer from the fraction. + * @param i the {@code integer} to subtract. + * @return this - i + */ + public Fraction subtract(final int i) { + return new Fraction(numerator - i * denominator, denominator); + } + + /** + * Implement add and subtract using algorithm described in Knuth 4.5.1. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @param isAdd true to add, false to subtract + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator + * cannot be represented in an {@code int}. + */ + private Fraction addSub(Fraction fraction, boolean isAdd) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + // zero is identity for addition. + if (numerator == 0) { + return isAdd ? fraction : fraction.negate(); + } + if (fraction.numerator == 0) { + return this; + } + // if denominators are randomly distributed, d1 will be 1 about 61% + // of the time. + int d1 = ArithmeticUtils.gcd(denominator, fraction.denominator); + if (d1==1) { + // result is ( (u*v' +/- u'v) / u'v') + int uvp = ArithmeticUtils.mulAndCheck(numerator, fraction.denominator); + int upv = ArithmeticUtils.mulAndCheck(fraction.numerator, denominator); + return new Fraction + (isAdd ? ArithmeticUtils.addAndCheck(uvp, upv) : + ArithmeticUtils.subAndCheck(uvp, upv), + ArithmeticUtils.mulAndCheck(denominator, fraction.denominator)); + } + // the quantity 't' requires 65 bits of precision; see knuth 4.5.1 + // exercise 7. we're going to use a BigInteger. + // t = u(v'/d1) +/- v(u'/d1) + BigInteger uvp = BigInteger.valueOf(numerator) + .multiply(BigInteger.valueOf(fraction.denominator/d1)); + BigInteger upv = BigInteger.valueOf(fraction.numerator) + .multiply(BigInteger.valueOf(denominator/d1)); + BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv); + // but d2 doesn't need extra precision because + // d2 = gcd(t,d1) = gcd(t mod d1, d1) + int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue(); + int d2 = (tmodd1==0)?d1:ArithmeticUtils.gcd(tmodd1, d1); + + // result is (t/d2) / (u'/d1)(v'/d2) + BigInteger w = t.divide(BigInteger.valueOf(d2)); + if (w.bitLength() > 31) { + throw new MathArithmeticException(LocalizedFormats.NUMERATOR_OVERFLOW_AFTER_MULTIPLY, + w); + } + return new Fraction (w.intValue(), + ArithmeticUtils.mulAndCheck(denominator/d1, + fraction.denominator/d2)); + } + + /** + *

    Multiplies the value of this fraction by another, returning the + * result in reduced form.

    + * + * @param fraction the fraction to multiply by, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction multiply(Fraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (numerator == 0 || fraction.numerator == 0) { + return ZERO; + } + // knuth 4.5.1 + // make sure we don't overflow unless the result *must* overflow. + int d1 = ArithmeticUtils.gcd(numerator, fraction.denominator); + int d2 = ArithmeticUtils.gcd(fraction.numerator, denominator); + return getReducedFraction + (ArithmeticUtils.mulAndCheck(numerator/d1, fraction.numerator/d2), + ArithmeticUtils.mulAndCheck(denominator/d2, fraction.denominator/d1)); + } + + /** + * Multiply the fraction by an integer. + * @param i the {@code integer} to multiply by. + * @return this * i + */ + public Fraction multiply(final int i) { + return multiply(new Fraction(i)); + } + + /** + *

    Divide the value of this fraction by another.

    + * + * @param fraction the fraction to divide by, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws IllegalArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the fraction to divide by is zero + * @throws MathArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction divide(Fraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_FRACTION_TO_DIVIDE_BY, + fraction.numerator, fraction.denominator); + } + return multiply(fraction.reciprocal()); + } + + /** + * Divide the fraction by an integer. + * @param i the {@code integer} to divide by. + * @return this * i + */ + public Fraction divide(final int i) { + return divide(new Fraction(i)); + } + + /** + *

    + * Gets the fraction percentage as a {@code double}. This calculates the + * fraction as the numerator divided by denominator multiplied by 100. + *

    + * + * @return the fraction percentage as a {@code double}. + */ + public double percentageValue() { + return 100 * doubleValue(); + } + + /** + *

    Creates a {@code Fraction} instance with the 2 parts + * of a fraction Y/Z.

    + * + *

    Any negative signs are resolved to be on the numerator.

    + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws MathArithmeticException if the denominator is {@code zero} + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, + numerator, denominator); + } + if (numerator==0) { + return ZERO; // normalize zero. + } + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator==Integer.MIN_VALUE && (numerator&1)==0) { + numerator/=2; denominator/=2; + } + if (denominator < 0) { + if (numerator==Integer.MIN_VALUE || + denominator==Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_FRACTION, + numerator, denominator); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + int gcd = ArithmeticUtils.gcd(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + *

    + * Returns the {@code String} representing this fraction, ie + * "num / dem" or just "num" if the denominator is one. + *

    + * + * @return a string representation of the fraction. + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String str = null; + if (denominator == 1) { + str = Integer.toString(numerator); + } else if (numerator == 0) { + str = "0"; + } else { + str = numerator + " / " + denominator; + } + return str; + } + + /** {@inheritDoc} */ + public FractionField getField() { + return FractionField.getInstance(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionConversionException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionConversionException.java new file mode 100644 index 000000000..6ea3e6b00 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionConversionException.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 com.fr.third.org.apache.commons.math3.fraction; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Error thrown when a double value cannot be converted to a fraction + * in the allowed number of iterations. + * + * @since 1.2 + */ +public class FractionConversionException extends ConvergenceException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -4661812640132576263L; + + /** + * Constructs an exception with specified formatted detail message. + * Message formatting is delegated to {@link java.text.MessageFormat}. + * @param value double value to convert + * @param maxIterations maximal number of iterations allowed + */ + public FractionConversionException(double value, int maxIterations) { + super(LocalizedFormats.FAILED_FRACTION_CONVERSION, value, maxIterations); + } + + /** + * Constructs an exception with specified formatted detail message. + * Message formatting is delegated to {@link java.text.MessageFormat}. + * @param value double value to convert + * @param p current numerator + * @param q current denominator + */ + public FractionConversionException(double value, long p, long q) { + super(LocalizedFormats.FRACTION_CONVERSION_OVERFLOW, value, p, q); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionField.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionField.java new file mode 100644 index 000000000..0037e07a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionField.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.fraction; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Representation of the fractional numbers field. + *

    + * This class is a singleton. + *

    + * @see Fraction + * @since 2.0 + */ +public class FractionField implements Field, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1257768487499119313L; + + /** Private constructor for the singleton. + */ + private FractionField() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static FractionField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public Fraction getOne() { + return Fraction.ONE; + } + + /** {@inheritDoc} */ + public Fraction getZero() { + return Fraction.ZERO; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return Fraction.class; + } + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final FractionField INSTANCE = new FractionField(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionFormat.java new file mode 100644 index 000000000..a0ea1d15b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/FractionFormat.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.fraction; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.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 + */ +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. obj must be either a + * {@link Fraction} object or a {@link Number} object. Any other type of + * object will result in an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) + * @throws FractionConversionException if the number cannot be converted to a fraction + * @throws MathIllegalArgumentException if obj is not a valid type. + */ + @Override + public StringBuffer format(final Object obj, + final StringBuffer toAppendTo, final FieldPosition pos) + throws FractionConversionException, MathIllegalArgumentException { + StringBuffer ret = null; + + if (obj instanceof Fraction) { + ret = format((Fraction) obj, toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new Fraction(((Number) obj).doubleValue()), toAppendTo, pos); + } else { + throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION); + } + + return ret; + } + + /** + * Parses a string to produce a {@link Fraction} object. + * @param source the string to parse + * @return the parsed {@link Fraction} object. + * @exception MathParseException if the beginning of the specified string + * cannot be parsed. + */ + @Override + public Fraction parse(final String source) throws MathParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final Fraction result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, parsePosition.getErrorIndex(), Fraction.class); + } + return result; + } + + /** + * Parses a string to produce a {@link Fraction} object. This method + * expects the string to be formatted as an improper fraction. + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the parsed {@link Fraction} object. + */ + @Override + public Fraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + final Number num = getNumeratorFormat().parse(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0 : + // no '/' + // return num as a fraction + return new Fraction(num.intValue(), 1); + case '/' : + // found '/', continue parsing denominator + break; + default : + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final Number den = getDenominatorFormat().parse(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + return new Fraction(num.intValue(), den.intValue()); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperBigFractionFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperBigFractionFormat.java new file mode 100644 index 000000000..80ce28ef5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperBigFractionFormat.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 com.fr.third.org.apache.commons.math3.fraction; + +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Formats a BigFraction number in proper format. The number format for each of + * the whole number, numerator and, denominator can be configured. + *

    + * 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 ParseException.

    + * + * @since 1.1 + */ +public class ProperBigFractionFormat extends BigFractionFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = -6337346779577272307L; + + /** The format used for the whole number. */ + private NumberFormat wholeFormat; + + /** + * Create a proper formatting instance with the default number format for + * the whole, numerator, and denominator. + */ + public ProperBigFractionFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create a proper formatting instance with a custom number format for the + * whole, numerator, and denominator. + * @param format the custom format for the whole, numerator, and + * denominator. + */ + public ProperBigFractionFormat(final NumberFormat format) { + this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone()); + } + + /** + * Create a proper formatting instance with a custom number format for each + * of the whole, numerator, and denominator. + * @param wholeFormat the custom format for the whole. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public ProperBigFractionFormat(final NumberFormat wholeFormat, + final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + setWholeFormat(wholeFormat); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction + * is output in proper format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + @Override + public StringBuffer format(final BigFraction fraction, + final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + BigInteger num = fraction.getNumerator(); + BigInteger den = fraction.getDenominator(); + BigInteger whole = num.divide(den); + num = num.remainder(den); + + if (!BigInteger.ZERO.equals(whole)) { + getWholeFormat().format(whole, toAppendTo, pos); + toAppendTo.append(' '); + if (num.compareTo(BigInteger.ZERO) < 0) { + num = num.negate(); + } + } + getNumeratorFormat().format(num, toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(den, toAppendTo, pos); + + return toAppendTo; + } + + /** + * Access the whole format. + * @return the whole format. + */ + public NumberFormat getWholeFormat() { + return wholeFormat; + } + + /** + * Parses a string to produce a {@link BigFraction} object. This method + * expects the string to be formatted as a proper BigFraction. + *

    + * 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 ParseException.

    + * + * @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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperFractionFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperFractionFormat.java new file mode 100644 index 000000000..6ce3188a9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/ProperFractionFormat.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.fraction; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Formats a Fraction number in proper format. The number format for each of + * the whole number, numerator and, denominator can be configured. + *

    + * 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 ParseException.

    + * + * @since 1.1 + */ +public class ProperFractionFormat extends FractionFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = 760934726031766749L; + + /** The format used for the whole number. */ + private NumberFormat wholeFormat; + + /** + * Create a proper formatting instance with the default number format for + * the whole, numerator, and denominator. + */ + public ProperFractionFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create a proper formatting instance with a custom number format for the + * whole, numerator, and denominator. + * @param format the custom format for the whole, numerator, and + * denominator. + */ + public ProperFractionFormat(NumberFormat format) { + this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone()); + } + + /** + * Create a proper formatting instance with a custom number format for each + * of the whole, numerator, and denominator. + * @param wholeFormat the custom format for the whole. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public ProperFractionFormat(NumberFormat wholeFormat, + NumberFormat numeratorFormat, + NumberFormat denominatorFormat) + { + super(numeratorFormat, denominatorFormat); + setWholeFormat(wholeFormat); + } + + /** + * Formats a {@link Fraction} object to produce a string. The fraction + * is output in proper format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + @Override + public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, + FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + int num = fraction.getNumerator(); + int den = fraction.getDenominator(); + int whole = num / den; + num %= den; + + if (whole != 0) { + getWholeFormat().format(whole, toAppendTo, pos); + toAppendTo.append(' '); + num = FastMath.abs(num); + } + getNumeratorFormat().format(num, toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(den, toAppendTo, pos); + + return toAppendTo; + } + + /** + * Access the whole format. + * @return the whole format. + */ + public NumberFormat getWholeFormat() { + return wholeFormat; + } + + /** + * Parses a string to produce a {@link Fraction} object. This method + * expects the string to be formatted as a proper fraction. + *

    + * 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 ParseException.

    + * + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link Fraction} object. + */ + @Override + public Fraction parse(String source, ParsePosition pos) { + // try to parse improper fraction + Fraction ret = super.parse(source, pos); + if (ret != null) { + return ret; + } + + int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse whole + Number whole = getWholeFormat().parse(source, pos); + if (whole == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + Number num = getNumeratorFormat().parse(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (num.intValue() < 0) { + // minus signs should be leading, invalid expression + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + int startIndex = pos.getIndex(); + char c = parseNextCharacter(source, pos); + switch (c) { + case 0 : + // no '/' + // return num as a fraction + return new Fraction(num.intValue(), 1); + case '/' : + // found '/', continue parsing denominator + break; + default : + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + Number den = getDenominatorFormat().parse(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (den.intValue() < 0) { + // minus signs must be leading, invalid + pos.setIndex(initialIndex); + return null; + } + + int w = whole.intValue(); + int n = num.intValue(); + int d = den.intValue(); + return new Fraction(((FastMath.abs(w) * d) + n) * MathUtils.copySign(1, w), d); + } + + /** + * Modify the whole format. + * @param format The new whole format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setWholeFormat(NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT); + } + this.wholeFormat = format; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/package-info.java new file mode 100644 index 000000000..04661b268 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/fraction/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Fraction number type and fraction number formatting. + * + */ +package com.fr.third.org.apache.commons.math3.fraction; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/AbstractListChromosome.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/AbstractListChromosome.java new file mode 100644 index 000000000..06510a5fe --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/AbstractListChromosome.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Chromosome represented by an immutable list of a fixed length. + * + * @param type of the representation list + * @since 2.0 + */ +public abstract class AbstractListChromosome extends Chromosome { + + /** List representing the chromosome */ + private final List representation; + + /** + * Constructor, copying the input representation. + * @param representation inner representation of the chromosome + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public AbstractListChromosome(final List representation) throws InvalidRepresentationException { + this(representation, true); + } + + /** + * Constructor, copying the input representation. + * @param representation inner representation of the chromosome + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public AbstractListChromosome(final T[] representation) throws InvalidRepresentationException { + this(Arrays.asList(representation)); + } + + /** + * Constructor. + * @param representation inner representation of the chromosome + * @param copyList if {@code true}, the representation will be copied, otherwise it will be referenced. + * @since 3.3 + */ + public AbstractListChromosome(final List representation, final boolean copyList) { + checkValidity(representation); + this.representation = + Collections.unmodifiableList(copyList ? new ArrayList(representation) : representation); + } + + /** + * Asserts that representation can represent a valid chromosome. + * + * @param chromosomeRepresentation representation of the chromosome + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + protected abstract void checkValidity(List chromosomeRepresentation) throws InvalidRepresentationException; + + /** + * Returns the (immutable) inner representation of the chromosome. + * @return the representation of the chromosome + */ + protected List 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 this is, with a given arrayRepresentation. + * 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 newFixedLengthChromosome(final List chromosomeRepresentation); + + /** {@inheritDoc} */ + @Override + public String toString() { + return String.format("(f=%s %s)", getFitness(), getRepresentation()); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/BinaryChromosome.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/BinaryChromosome.java new file mode 100644 index 000000000..c925ce692 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/BinaryChromosome.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + + +/** + * Chromosome represented by a vector of 0s and 1s. + * + * @since 2.0 + */ +public abstract class BinaryChromosome extends AbstractListChromosome { + + /** + * Constructor. + * @param representation list of {0,1} values representing the chromosome + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public BinaryChromosome(List representation) throws InvalidRepresentationException { + super(representation); + } + + /** + * Constructor. + * @param representation array of {0,1} values representing the chromosome + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public BinaryChromosome(Integer[] representation) throws InvalidRepresentationException { + super(representation); + } + + /** + * {@inheritDoc} + */ + @Override + protected void checkValidity(List chromosomeRepresentation) throws InvalidRepresentationException { + for (int i : chromosomeRepresentation) { + if (i < 0 || i >1) { + throw new InvalidRepresentationException(LocalizedFormats.INVALID_BINARY_DIGIT, + i); + } + } + } + + /** + * Returns a representation of a random binary array of length length. + * @param length length of the array + * @return a random binary array of length length + */ + public static List randomBinaryRepresentation(int length) { + // random binary list + List rList= new ArrayList (length); + for (int j=0; joriginal is not an instance of {@link BinaryChromosome}. + */ + public Chromosome mutate(Chromosome original) throws MathIllegalArgumentException { + if (!(original instanceof BinaryChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_BINARY_CHROMOSOME); + } + + BinaryChromosome origChrom = (BinaryChromosome) original; + List newRepr = new ArrayList(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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Chromosome.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Chromosome.java new file mode 100644 index 000000000..a7a00af0e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Chromosome.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 com.fr.third.org.apache.commons.math3.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 + */ +public abstract class Chromosome implements Comparable,Fitness { + /** Value assigned when no fitness has been computed yet. */ + private static final double NO_FITNESS = Double.NEGATIVE_INFINITY; + + /** Cached value of the fitness of this chromosome. */ + private double fitness = NO_FITNESS; + + /** + * Access the fitness of this chromosome. The bigger the fitness, the better the chromosome. + *

    + * Computation of fitness is usually very time-consuming task, therefore the fitness is cached. + * + * @return the fitness + */ + public double getFitness() { + if (this.fitness == NO_FITNESS) { + // no cache - compute the fitness + this.fitness = fitness(); + } + return this.fitness; + } + + /** + * Compares two chromosomes based on their fitness. The bigger the fitness, the better the chromosome. + * + * @param another another chromosome to compare + * @return + *

      + *
    • -1 if another is better than this
    • + *
    • 1 if another is worse than this
    • + *
    • 0 if the two chromosomes have the same fitness
    • + *
    + */ + public int compareTo(final Chromosome another) { + return Double.compare(getFitness(), another.getFitness()); + } + + /** + * Returns true iff another 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 another is equivalent to this chromosome + */ + protected boolean isSame(final Chromosome another) { + return false; + } + + /** + * Searches the population for another chromosome with the same representation. If such chromosome is + * found, it is returned, if no such chromosome exists, returns null. + * + * @param population Population to search + * @return Chromosome with the same representation, or null if no such chromosome exists. + */ + protected Chromosome findSameChromosome(final Population population) { + for (Chromosome anotherChr : population) { + if (this.isSame(anotherChr)) { + return anotherChr; + } + } + return null; + } + + /** + * Searches the population for a chromosome representing the same solution, and if it finds one, + * updates the fitness to its value. + * + * @param population Population to search + */ + public void searchForFitnessUpdate(final Population population) { + Chromosome sameChromosome = findSameChromosome(population); + if (sameChromosome != null) { + fitness = sameChromosome.getFitness(); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ChromosomePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ChromosomePair.java new file mode 100644 index 000000000..ec244bdba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ChromosomePair.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +/** + * A pair of {@link Chromosome} objects. + * @since 2.0 + * + */ +public class ChromosomePair { + /** the first chromosome in the pair. */ + private final Chromosome first; + + /** the second chromosome in the pair. */ + private final Chromosome second; + + /** + * Create a chromosome pair. + * + * @param c1 the first chromosome. + * @param c2 the second chromosome. + */ + public ChromosomePair(final Chromosome c1, final Chromosome c2) { + super(); + first = c1; + second = c2; + } + + /** + * Access the first chromosome. + * + * @return the first chromosome. + */ + public Chromosome getFirst() { + return first; + } + + /** + * Access the second chromosome. + * + * @return the second chromosome. + */ + public Chromosome getSecond() { + return second; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return String.format("(%s,%s)", getFirst(), getSecond()); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CrossoverPolicy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CrossoverPolicy.java new file mode 100644 index 000000000..1bdddc4cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CrossoverPolicy.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Policy used to create a pair of new chromosomes by performing a crossover + * operation on a source pair of chromosomes. + * + * @since 2.0 + */ +public interface CrossoverPolicy { + + /** + * Perform a crossover operation on the given chromosomes. + * + * @param first the first chromosome. + * @param second the second chromosome. + * @return the pair of new chromosomes that resulted from the crossover. + * @throws MathIllegalArgumentException if the given chromosomes are not compatible with this {@link CrossoverPolicy} + */ + ChromosomePair crossover(Chromosome first, Chromosome second) throws MathIllegalArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CycleCrossover.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CycleCrossover.java new file mode 100644 index 000000000..d2662bdf5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/CycleCrossover.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Cycle Crossover [CX] builds offspring from ordered chromosomes by identifying cycles + * between two parent chromosomes. To form the children, the cycles are copied from the + * respective parents. + *

    + * To form a cycle the following procedure is applied: + *

      + *
    1. start with the first gene of parent 1
    2. + *
    3. look at the gene at the same position of parent 2
    4. + *
    5. go to the position with the same gene in parent 1
    6. + *
    7. add this gene index to the cycle
    8. + *
    9. repeat the steps 2-5 until we arrive at the starting gene of this cycle
    10. + *
    + * The indices that form a cycle are then used to form the children in alternating order, i.e. + * in cycle 1, the genes of parent 1 are copied to child 1, while in cycle 2 the genes of parent 1 + * are copied to child 2, and so forth ... + *

    + * + * Example (zero-start cycle): + *
    + * p1 = (8 4 7 3 6 2 5 1 9 0)    X   c1 = (8 1 2 3 4 5 6 7 9 0)
    + * p2 = (0 1 2 3 4 5 6 7 8 9)    X   c2 = (0 4 7 3 6 2 5 1 8 9)
    + *
    + * cycle 1: 8 0 9
    + * cycle 2: 4 1 7 2 5 6
    + * cycle 3: 3
    + * 
    + * + * This policy works only on {@link AbstractListChromosome}, and therefore it + * is parameterized by T. Moreover, the chromosomes must have same lengths. + * + * @see + * Cycle Crossover Operator + * + * @param generic type of the {@link AbstractListChromosome}s for crossover + * @since 3.1 + */ +public class CycleCrossover implements CrossoverPolicy { + + /** If the start index shall be chosen randomly. */ + private final boolean randomStart; + + /** + * Creates a new {@link CycleCrossover} policy. + */ + public CycleCrossover() { + this(false); + } + + /** + * Creates a new {@link CycleCrossover} policy using the given {@code randomStart} behavior. + * + * @param randomStart whether the start index shall be chosen randomly or be set to 0 + */ + public CycleCrossover(final boolean randomStart) { + this.randomStart = randomStart; + } + + /** + * Returns whether the starting index is chosen randomly or set to zero. + * + * @return {@code true} if the starting index is chosen randomly, {@code false} otherwise + */ + public boolean isRandomStart() { + return randomStart; + } + + /** + * {@inheritDoc} + * + * @throws MathIllegalArgumentException if the chromosomes are not an instance of {@link AbstractListChromosome} + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + @SuppressWarnings("unchecked") + public ChromosomePair crossover(final Chromosome first, final Chromosome second) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (!(first instanceof AbstractListChromosome && second instanceof AbstractListChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); + } + return mate((AbstractListChromosome) first, (AbstractListChromosome) second); + } + + /** + * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. + * + * @param first the first chromosome + * @param second the second chromosome + * @return the pair of new chromosomes that resulted from the crossover + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + protected ChromosomePair mate(final AbstractListChromosome first, final AbstractListChromosome second) + throws DimensionMismatchException { + + final int length = first.getLength(); + if (length != second.getLength()) { + throw new DimensionMismatchException(second.getLength(), length); + } + + // array representations of the parents + final List parent1Rep = first.getRepresentation(); + final List parent2Rep = second.getRepresentation(); + // and of the children: do a crossover copy to simplify the later processing + final List child1Rep = new ArrayList(second.getRepresentation()); + final List child2Rep = new ArrayList(first.getRepresentation()); + + // the set of all visited indices so far + final Set visitedIndices = new HashSet(length); + // the indices of the current cycle + final List indices = new ArrayList(length); + + // determine the starting index + int idx = randomStart ? GeneticAlgorithm.getRandomGenerator().nextInt(length) : 0; + int cycle = 1; + + while (visitedIndices.size() < length) { + indices.add(idx); + + T item = parent2Rep.get(idx); + idx = parent1Rep.indexOf(item); + + while (idx != indices.get(0)) { + // add that index to the cycle indices + indices.add(idx); + // get the item in the second parent at that index + item = parent2Rep.get(idx); + // get the index of that item in the first parent + idx = parent1Rep.indexOf(item); + } + + // for even cycles: swap the child elements on the indices found in this cycle + if (cycle++ % 2 != 0) { + for (int i : indices) { + T tmp = child1Rep.get(i); + child1Rep.set(i, child2Rep.get(i)); + child2Rep.set(i, tmp); + } + } + + visitedIndices.addAll(indices); + // find next starting index: last one + 1 until we find an unvisited index + idx = (indices.get(0) + 1) % length; + while (visitedIndices.contains(idx) && visitedIndices.size() < length) { + idx++; + if (idx >= length) { + idx = 0; + } + } + indices.clear(); + } + + return new ChromosomePair(first.newFixedLengthChromosome(child1Rep), + second.newFixedLengthChromosome(child2Rep)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ElitisticListPopulation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ElitisticListPopulation.java new file mode 100644 index 000000000..323d3207a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ElitisticListPopulation.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.Collections; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Population of chromosomes which uses elitism (certain percentage of the best + * chromosomes is directly copied to the next generation). + * + * @since 2.0 + */ +public class ElitisticListPopulation extends ListPopulation { + + /** percentage of chromosomes copied to the next generation */ + private double elitismRate = 0.9; + + /** + * Creates a new {@link ElitisticListPopulation} instance. + * + * @param chromosomes list of chromosomes in the population + * @param populationLimit maximal size of the population + * @param elitismRate how many best chromosomes will be directly transferred to the next generation [in %] + * @throws NullArgumentException if the list of chromosomes is {@code null} + * @throws NotPositiveException if the population limit is not a positive number (< 1) + * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit + * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range + */ + public ElitisticListPopulation(final List chromosomes, final int populationLimit, + final double elitismRate) + throws NullArgumentException, NotPositiveException, NumberIsTooLargeException, OutOfRangeException { + + super(chromosomes, populationLimit); + setElitismRate(elitismRate); + } + + /** + * Creates a new {@link ElitisticListPopulation} instance and initializes its inner chromosome list. + * + * @param populationLimit maximal size of the population + * @param elitismRate how many best chromosomes will be directly transferred to the next generation [in %] + * @throws NotPositiveException if the population limit is not a positive number (< 1) + * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range + */ + public ElitisticListPopulation(final int populationLimit, final double elitismRate) + throws NotPositiveException, OutOfRangeException { + + super(populationLimit); + setElitismRate(elitismRate); + } + + /** + * Start the population for the next generation. The {@link #elitismRate} + * percents of the best chromosomes are directly copied to the next generation. + * + * @return the beginnings of the next generation. + */ + public Population nextGeneration() { + // initialize a new generation with the same parameters + ElitisticListPopulation nextGeneration = + new ElitisticListPopulation(getPopulationLimit(), getElitismRate()); + + final List oldChromosomes = getChromosomeList(); + Collections.sort(oldChromosomes); + + // index of the last "not good enough" chromosome + int boundIndex = (int) FastMath.ceil((1.0 - getElitismRate()) * oldChromosomes.size()); + for (int i = boundIndex; i < oldChromosomes.size(); i++) { + nextGeneration.addChromosome(oldChromosomes.get(i)); + } + return nextGeneration; + } + + /** + * Sets the elitism rate, i.e. how many best chromosomes will be directly transferred to the next generation [in %]. + * + * @param elitismRate how many best chromosomes will be directly transferred to the next generation [in %] + * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range + */ + public void setElitismRate(final double elitismRate) throws OutOfRangeException { + if (elitismRate < 0 || elitismRate > 1) { + throw new OutOfRangeException(LocalizedFormats.ELITISM_RATE, elitismRate, 0, 1); + } + this.elitismRate = elitismRate; + } + + /** + * Access the elitism rate. + * @return the elitism rate + */ + public double getElitismRate() { + return this.elitismRate; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Fitness.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Fitness.java new file mode 100644 index 000000000..6cfae6c72 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Fitness.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +/** + * Fitness of a chromosome. + * + * @since 2.0 + */ +public interface Fitness { + + /** + * Compute the fitness. This is usually very time-consuming, so the value should be cached. + * @return fitness + */ + double fitness(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedElapsedTime.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedElapsedTime.java new file mode 100644 index 000000000..bb4cbe2d4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedElapsedTime.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.concurrent.TimeUnit; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; + +/** + * Stops after a fixed amount of time has elapsed. + *

    + * The first time {@link #isSatisfied(Population)} is invoked, the end time of the evolution is determined based on the + * provided maxTime value. Once the elapsed time reaches the configured maxTime value, + * {@link #isSatisfied(Population)} returns true. + * + * @since 3.1 + */ +public class FixedElapsedTime implements StoppingCondition { + /** Maximum allowed time period (in nanoseconds). */ + private final long maxTimePeriod; + + /** The predetermined termination time (stopping condition). */ + private long endTime = -1; + + /** + * Create a new {@link FixedElapsedTime} instance. + * + * @param maxTime maximum number of seconds generations are allowed to evolve + * @throws NumberIsTooSmallException if the provided time is < 0 + */ + public FixedElapsedTime(final long maxTime) throws NumberIsTooSmallException { + this(maxTime, TimeUnit.SECONDS); + } + + /** + * Create a new {@link FixedElapsedTime} instance. + * + * @param maxTime maximum time generations are allowed to evolve + * @param unit {@link TimeUnit} of the maxTime argument + * @throws NumberIsTooSmallException if the provided time is < 0 + */ + public FixedElapsedTime(final long maxTime, final TimeUnit unit) throws NumberIsTooSmallException { + if (maxTime < 0) { + throw new NumberIsTooSmallException(maxTime, 0, true); + } + maxTimePeriod = unit.toNanos(maxTime); + } + + /** + * Determine whether or not the maximum allowed time has passed. + * The termination time is determined after the first generation. + * + * @param population ignored (no impact on result) + * @return true IFF the maximum allowed time period has elapsed + */ + public boolean isSatisfied(final Population population) { + if (endTime < 0) { + endTime = System.nanoTime() + maxTimePeriod; + } + + return System.nanoTime() >= endTime; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedGenerationCount.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedGenerationCount.java new file mode 100644 index 000000000..dddb44ffe --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/FixedGenerationCount.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; + +/** + * Stops after a fixed number of generations. Each time {@link #isSatisfied(Population)} is invoked, a generation + * counter is incremented. Once the counter reaches the configured maxGenerations value, + * {@link #isSatisfied(Population)} returns true. + * + * @since 2.0 + */ +public class FixedGenerationCount implements StoppingCondition { + /** Number of generations that have passed */ + private int numGenerations = 0; + + /** Maximum number of generations (stopping criteria) */ + private final int maxGenerations; + + /** + * Create a new FixedGenerationCount instance. + * + * @param maxGenerations number of generations to evolve + * @throws NumberIsTooSmallException if the number of generations is < 1 + */ + public FixedGenerationCount(final int maxGenerations) throws NumberIsTooSmallException { + if (maxGenerations <= 0) { + throw new NumberIsTooSmallException(maxGenerations, 1, true); + } + this.maxGenerations = maxGenerations; + } + + /** + * Determine whether or not the given number of generations have passed. Increments the number of generations + * counter if the maximum has not been reached. + * + * @param population ignored (no impact on result) + * @return true IFF the maximum number of generations has been exceeded + */ + public boolean isSatisfied(final Population population) { + if (this.numGenerations < this.maxGenerations) { + numGenerations++; + return false; + } + return true; + } + + /** + * Returns the number of generations that have already passed. + * @return the number of generations that have passed + */ + public int getNumGenerations() { + return numGenerations; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/GeneticAlgorithm.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/GeneticAlgorithm.java new file mode 100644 index 000000000..f3e66fa48 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/GeneticAlgorithm.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.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 + */ +public class GeneticAlgorithm { + + /** + * Static random number generator shared by GA implementation classes. Set the randomGenerator seed to get + * reproducible results. Use {@link #setRandomGenerator(RandomGenerator)} to supply an alternative to the default + * JDK-provided PRNG. + */ + //@GuardedBy("this") + private static RandomGenerator randomGenerator = new JDKRandomGenerator(); + + /** the crossover policy used by the algorithm. */ + private final CrossoverPolicy crossoverPolicy; + + /** the rate of crossover for the algorithm. */ + private final double crossoverRate; + + /** the mutation policy used by the algorithm. */ + private final MutationPolicy mutationPolicy; + + /** the rate of mutation for the algorithm. */ + private final double mutationRate; + + /** the selection policy used by the algorithm. */ + private final SelectionPolicy selectionPolicy; + + /** the number of generations evolved to reach {@link StoppingCondition} in the last run. */ + private int generationsEvolved = 0; + + /** + * Create a new genetic algorithm. + * @param crossoverPolicy The {@link CrossoverPolicy} + * @param crossoverRate The crossover rate as a percentage (0-1 inclusive) + * @param mutationPolicy The {@link MutationPolicy} + * @param mutationRate The mutation rate as a percentage (0-1 inclusive) + * @param selectionPolicy The {@link SelectionPolicy} + * @throws OutOfRangeException if the crossover or mutation rate is outside the [0, 1] range + */ + public GeneticAlgorithm(final CrossoverPolicy crossoverPolicy, + final double crossoverRate, + final MutationPolicy mutationPolicy, + final double mutationRate, + final SelectionPolicy selectionPolicy) throws OutOfRangeException { + + if (crossoverRate < 0 || crossoverRate > 1) { + throw new OutOfRangeException(LocalizedFormats.CROSSOVER_RATE, + crossoverRate, 0, 1); + } + if (mutationRate < 0 || mutationRate > 1) { + throw new OutOfRangeException(LocalizedFormats.MUTATION_RATE, + mutationRate, 0, 1); + } + this.crossoverPolicy = crossoverPolicy; + this.crossoverRate = crossoverRate; + this.mutationPolicy = mutationPolicy; + this.mutationRate = mutationRate; + this.selectionPolicy = selectionPolicy; + } + + /** + * Set the (static) random generator. + * + * @param random random generator + */ + public static synchronized void setRandomGenerator(final RandomGenerator random) { + randomGenerator = random; + } + + /** + * Returns the (static) random generator. + * + * @return the static random generator shared by GA implementation classes + */ + public static synchronized RandomGenerator getRandomGenerator() { + return randomGenerator; + } + + /** + * Evolve the given population. Evolution stops when the stopping condition + * is satisfied. Updates the {@link #getGenerationsEvolved() generationsEvolved} + * property with the number of generations evolved before the StoppingCondition + * is satisfied. + * + * @param initial the initial, seed population. + * @param condition the stopping condition used to stop evolution. + * @return the population that satisfies the stopping condition. + */ + public Population evolve(final Population initial, final StoppingCondition condition) { + Population current = initial; + generationsEvolved = 0; + while (!condition.isSatisfied(current)) { + current = nextGeneration(current); + generationsEvolved++; + } + return current; + } + + /** + * Evolve the given population into the next generation. + *

    + *

      + *
    1. Get nextGeneration population to fill from current + * generation, using its nextGeneration method
    2. + *
    3. Loop until new generation is filled:
    4. + *
      • Apply configured SelectionPolicy to select a pair of parents + * from current
      • + *
      • With probability = {@link #getCrossoverRate()}, apply + * configured {@link CrossoverPolicy} to parents
      • + *
      • With probability = {@link #getMutationRate()}, apply + * configured {@link MutationPolicy} to each of the offspring
      • + *
      • Add offspring individually to nextGeneration, + * space permitting
      • + *
      + *
    5. Return nextGeneration
    6. + *
    + * + * @param current the current population. + * @return the population for the next generation. + */ + public Population nextGeneration(final Population current) { + Population nextGeneration = current.nextGeneration(); + + RandomGenerator randGen = getRandomGenerator(); + + while (nextGeneration.getPopulationSize() < nextGeneration.getPopulationLimit()) { + // select parent chromosomes + ChromosomePair pair = getSelectionPolicy().select(current); + + // crossover? + if (randGen.nextDouble() < getCrossoverRate()) { + // apply crossover policy to create two offspring + pair = getCrossoverPolicy().crossover(pair.getFirst(), pair.getSecond()); + } + + // mutation? + if (randGen.nextDouble() < getMutationRate()) { + // apply mutation policy to the chromosomes + pair = new ChromosomePair( + getMutationPolicy().mutate(pair.getFirst()), + getMutationPolicy().mutate(pair.getSecond())); + } + + // add the first chromosome to the population + nextGeneration.addChromosome(pair.getFirst()); + // is there still a place for the second chromosome? + if (nextGeneration.getPopulationSize() < nextGeneration.getPopulationLimit()) { + // add the second chromosome to the population + nextGeneration.addChromosome(pair.getSecond()); + } + } + + return nextGeneration; + } + + /** + * Returns the crossover policy. + * @return crossover policy + */ + public CrossoverPolicy getCrossoverPolicy() { + return crossoverPolicy; + } + + /** + * Returns the crossover rate. + * @return crossover rate + */ + public double getCrossoverRate() { + return crossoverRate; + } + + /** + * Returns the mutation policy. + * @return mutation policy + */ + public MutationPolicy getMutationPolicy() { + return mutationPolicy; + } + + /** + * Returns the mutation rate. + * @return mutation rate + */ + public double getMutationRate() { + return mutationRate; + } + + /** + * Returns the selection policy. + * @return selection policy + */ + public SelectionPolicy getSelectionPolicy() { + return selectionPolicy; + } + + /** + * Returns the number of generations evolved to reach {@link StoppingCondition} in the last run. + * + * @return number of generations evolved + * @since 2.1 + */ + public int getGenerationsEvolved() { + return generationsEvolved; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/InvalidRepresentationException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/InvalidRepresentationException.java new file mode 100644 index 000000000..25c752fbe --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/InvalidRepresentationException.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Exception indicating that the representation of a chromosome is not valid. + * + * @since 2.0 + */ +public class InvalidRepresentationException extends MathIllegalArgumentException { + + /** Serialization version id */ + private static final long serialVersionUID = 1L; + + /** + * Construct an InvalidRepresentationException with a specialized message. + * + * @param pattern Message pattern. + * @param args Arguments. + */ + public InvalidRepresentationException(Localizable pattern, Object ... args) { + super(pattern, args); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ListPopulation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ListPopulation.java new file mode 100644 index 000000000..58ec37dee --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/ListPopulation.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Population of chromosomes represented by a {@link List}. + * + * @since 2.0 + */ +public abstract class ListPopulation implements Population { + + /** List of chromosomes */ + private List chromosomes; + + /** maximal size of the population */ + private int populationLimit; + + /** + * Creates a new ListPopulation instance and initializes its inner chromosome list. + * + * @param populationLimit maximal size of the population + * @throws NotPositiveException if the population limit is not a positive number (< 1) + */ + public ListPopulation(final int populationLimit) throws NotPositiveException { + this(Collections. emptyList(), populationLimit); + } + + /** + * Creates a new ListPopulation instance. + *

    + * Note: the chromosomes of the specified list are added to the population. + * + * @param chromosomes list of chromosomes to be added to the population + * @param populationLimit maximal size of the population + * @throws NullArgumentException if the list of chromosomes is {@code null} + * @throws NotPositiveException if the population limit is not a positive number (< 1) + * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit + */ + public ListPopulation(final List chromosomes, final int populationLimit) + throws NullArgumentException, NotPositiveException, NumberIsTooLargeException { + + if (chromosomes == null) { + throw new NullArgumentException(); + } + if (populationLimit <= 0) { + throw new NotPositiveException(LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit); + } + if (chromosomes.size() > populationLimit) { + throw new NumberIsTooLargeException(LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE, + chromosomes.size(), populationLimit, false); + } + this.populationLimit = populationLimit; + this.chromosomes = new ArrayList(populationLimit); + this.chromosomes.addAll(chromosomes); + } + + /** + * Sets the list of chromosomes. + *

    + * Note: this method removes all existing chromosomes in the population and adds all chromosomes + * of the specified list to the population. + * + * @param chromosomes the list of chromosomes + * @throws NullArgumentException if the list of chromosomes is {@code null} + * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit + * @deprecated use {@link #addChromosomes(Collection)} instead + */ + @Deprecated + public void setChromosomes(final List chromosomes) + throws NullArgumentException, NumberIsTooLargeException { + + if (chromosomes == null) { + throw new NullArgumentException(); + } + if (chromosomes.size() > populationLimit) { + throw new NumberIsTooLargeException(LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE, + chromosomes.size(), populationLimit, false); + } + this.chromosomes.clear(); + this.chromosomes.addAll(chromosomes); + } + + /** + * Add a {@link Collection} of chromosomes to this {@link Population}. + * @param chromosomeColl a {@link Collection} of chromosomes + * @throws NumberIsTooLargeException if the population would exceed the population limit when + * adding this chromosome + * @since 3.1 + */ + public void addChromosomes(final Collection chromosomeColl) throws NumberIsTooLargeException { + if (chromosomes.size() + chromosomeColl.size() > populationLimit) { + throw new NumberIsTooLargeException(LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE, + chromosomes.size(), populationLimit, false); + } + this.chromosomes.addAll(chromosomeColl); + } + + /** + * Returns an unmodifiable list of the chromosomes in this population. + * @return the unmodifiable list of chromosomes + */ + public List getChromosomes() { + return Collections.unmodifiableList(chromosomes); + } + + /** + * Access the list of chromosomes. + * @return the list of chromosomes + * @since 3.1 + */ + protected List getChromosomeList() { + return chromosomes; + } + + /** + * Add the given chromosome to the population. + * + * @param chromosome the chromosome to add. + * @throws NumberIsTooLargeException if the population would exceed the {@code populationLimit} after + * adding this chromosome + */ + public void addChromosome(final Chromosome chromosome) throws NumberIsTooLargeException { + if (chromosomes.size() >= populationLimit) { + throw new NumberIsTooLargeException(LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE, + chromosomes.size(), populationLimit, false); + } + this.chromosomes.add(chromosome); + } + + /** + * Access the fittest chromosome in this population. + * @return the fittest chromosome. + */ + public Chromosome getFittestChromosome() { + // best so far + Chromosome bestChromosome = this.chromosomes.get(0); + for (Chromosome chromosome : this.chromosomes) { + if (chromosome.compareTo(bestChromosome) > 0) { + // better chromosome found + bestChromosome = chromosome; + } + } + return bestChromosome; + } + + /** + * Access the maximum population size. + * @return the maximum population size. + */ + public int getPopulationLimit() { + return this.populationLimit; + } + + /** + * Sets the maximal population size. + * @param populationLimit maximal population size. + * @throws NotPositiveException if the population limit is not a positive number (< 1) + * @throws NumberIsTooSmallException if the new population size is smaller than the current number + * of chromosomes in the population + */ + public void setPopulationLimit(final int populationLimit) throws NotPositiveException, NumberIsTooSmallException { + if (populationLimit <= 0) { + throw new NotPositiveException(LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit); + } + if (populationLimit < chromosomes.size()) { + throw new NumberIsTooSmallException(populationLimit, chromosomes.size(), true); + } + this.populationLimit = populationLimit; + } + + /** + * Access the current population size. + * @return the current population size. + */ + public int getPopulationSize() { + return this.chromosomes.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return this.chromosomes.toString(); + } + + /** + * Returns an iterator over the unmodifiable list of chromosomes. + *

    Any call to {@link Iterator#remove()} will result in a {@link UnsupportedOperationException}.

    + * + * @return chromosome iterator + */ + public Iterator iterator() { + return getChromosomes().iterator(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/MutationPolicy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/MutationPolicy.java new file mode 100644 index 000000000..f9470a050 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/MutationPolicy.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Algorithm used to mutate a chromosome. + * + * @since 2.0 + */ +public interface MutationPolicy { + + /** + * Mutate the given chromosome. + * @param original the original chromosome. + * @return the mutated chromosome. + * @throws MathIllegalArgumentException if the given chromosome is not compatible with this {@link MutationPolicy} + */ + Chromosome mutate(Chromosome original) throws MathIllegalArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/NPointCrossover.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/NPointCrossover.java new file mode 100644 index 000000000..6d68339a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/NPointCrossover.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; + +/** + * N-point crossover policy. For each iteration a random crossover point is + * selected and the first part from each parent is copied to the corresponding + * child, and the second parts are copied crosswise. + * + * Example (2-point crossover): + *
    + * -C- denotes a crossover point
    + *           -C-       -C-                         -C-        -C-
    + * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
    + *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
    + *        ||      (*)       ||                  ||      (**)       ||
    + *        VV      (**)      VV                  VV      (*)        VV
    + *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
    + * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
    + * 
    + * + * This policy works only on {@link AbstractListChromosome}, and therefore it + * is parameterized by T. Moreover, the chromosomes must have same lengths. + * + * @param generic type of the {@link AbstractListChromosome}s for crossover + * @since 3.1 + */ +public class NPointCrossover implements CrossoverPolicy { + + /** The number of crossover points. */ + private final int crossoverPoints; + + /** + * Creates a new {@link NPointCrossover} policy using the given number of points. + *

    + * Note: the number of crossover points must be < chromosome length - 1. + * This condition can only be checked at runtime, as the chromosome length is not known in advance. + * + * @param crossoverPoints the number of crossover points + * @throws NotStrictlyPositiveException if the number of {@code crossoverPoints} is not strictly positive + */ + public NPointCrossover(final int crossoverPoints) throws NotStrictlyPositiveException { + if (crossoverPoints <= 0) { + throw new NotStrictlyPositiveException(crossoverPoints); + } + this.crossoverPoints = crossoverPoints; + } + + /** + * Returns the number of crossover points used by this {@link CrossoverPolicy}. + * + * @return the number of crossover points + */ + public int getCrossoverPoints() { + return crossoverPoints; + } + + /** + * Performs a N-point crossover. N random crossover points are selected and are used + * to divide the parent chromosomes into segments. The segments are copied in alternate + * order from the two parents to the corresponding child chromosomes. + * + * Example (2-point crossover): + *

    +     * -C- denotes a crossover point
    +     *           -C-       -C-                         -C-        -C-
    +     * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
    +     *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
    +     *        ||      (*)       ||                  ||      (**)       ||
    +     *        VV      (**)      VV                  VV      (*)        VV
    +     *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
    +     * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
    +     * 
    + * + * @param first first parent (p1) + * @param second second parent (p2) + * @return pair of two children (c1,c2) + * @throws MathIllegalArgumentException iff one of the chromosomes is + * not an instance of {@link AbstractListChromosome} + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + @SuppressWarnings("unchecked") // OK because of instanceof checks + public ChromosomePair crossover(final Chromosome first, final Chromosome second) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (!(first instanceof AbstractListChromosome && second instanceof AbstractListChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); + } + return mate((AbstractListChromosome) first, (AbstractListChromosome) second); + } + + /** + * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. + * + * @param first the first chromosome + * @param second the second chromosome + * @return the pair of new chromosomes that resulted from the crossover + * @throws DimensionMismatchException if the length of the two chromosomes is different + * @throws NumberIsTooLargeException if the number of crossoverPoints is too large for the actual chromosomes + */ + private ChromosomePair mate(final AbstractListChromosome first, + final AbstractListChromosome second) + throws DimensionMismatchException, NumberIsTooLargeException { + + final int length = first.getLength(); + if (length != second.getLength()) { + throw new DimensionMismatchException(second.getLength(), length); + } + if (crossoverPoints >= length) { + throw new NumberIsTooLargeException(crossoverPoints, length, false); + } + + // array representations of the parents + final List parent1Rep = first.getRepresentation(); + final List parent2Rep = second.getRepresentation(); + // and of the children + final List child1Rep = new ArrayList(length); + final List child2Rep = new ArrayList(length); + + final RandomGenerator random = GeneticAlgorithm.getRandomGenerator(); + + List c1 = child1Rep; + List c2 = child2Rep; + + int remainingPoints = crossoverPoints; + int lastIndex = 0; + for (int i = 0; i < crossoverPoints; i++, remainingPoints--) { + // select the next crossover point at random + final int crossoverIndex = 1 + lastIndex + random.nextInt(length - lastIndex - remainingPoints); + + // copy the current segment + for (int j = lastIndex; j < crossoverIndex; j++) { + c1.add(parent1Rep.get(j)); + c2.add(parent2Rep.get(j)); + } + + // swap the children for the next segment + List tmp = c1; + c1 = c2; + c2 = tmp; + + lastIndex = crossoverIndex; + } + + // copy the last segment + for (int j = lastIndex; j < length; j++) { + c1.add(parent1Rep.get(j)); + c2.add(parent2Rep.get(j)); + } + + return new ChromosomePair(first.newFixedLengthChromosome(child1Rep), + second.newFixedLengthChromosome(child2Rep)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OnePointCrossover.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OnePointCrossover.java new file mode 100644 index 000000000..6cd3919c5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OnePointCrossover.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + + +/** + * 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: + *
    + * -C- denotes a crossover point
    + *                   -C-                                 -C-
    + * p1 = (1 0 1 0 0 1  | 0 1 1)    X    p2 = (0 1 1 0 1 0  | 1 1 1)
    + *      \------------/ \-----/              \------------/ \-----/
    + *            ||         (*)                       ||        (**)
    + *            VV         (**)                      VV        (*)
    + *      /------------\ /-----\              /------------\ /-----\
    + * c1 = (1 0 1 0 0 1  | 1 1 1)    X    c2 = (0 1 1 0 1 0  | 0 1 1)
    + * 
    + * + * This policy works only on {@link AbstractListChromosome}, and therefore it + * is parameterized by T. Moreover, the chromosomes must have same lengths. + * + * @param generic type of the {@link AbstractListChromosome}s for crossover + * @since 2.0 + * + */ +public class OnePointCrossover 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    c2 = (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) + * @throws MathIllegalArgumentException iff one of the chromosomes is + * not an instance of {@link AbstractListChromosome} + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + @SuppressWarnings("unchecked") // OK because of instanceof checks + public ChromosomePair crossover(final Chromosome first, final Chromosome second) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (! (first instanceof AbstractListChromosome && second instanceof AbstractListChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); + } + return crossover((AbstractListChromosome) first, (AbstractListChromosome) second); + } + + + /** + * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. + * + * @param first the first chromosome. + * @param second the second chromosome. + * @return the pair of new chromosomes that resulted from the crossover. + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + private ChromosomePair crossover(final AbstractListChromosome first, + final AbstractListChromosome second) throws DimensionMismatchException { + final int length = first.getLength(); + if (length != second.getLength()) { + throw new DimensionMismatchException(second.getLength(), length); + } + + // array representations of the parents + final List parent1Rep = first.getRepresentation(); + final List parent2Rep = second.getRepresentation(); + // and of the children + final List child1Rep = new ArrayList(length); + final List child2Rep = new ArrayList(length); + + // select a crossover point at random (0 and length makes no sense) + final int crossoverIndex = 1 + (GeneticAlgorithm.getRandomGenerator().nextInt(length-2)); + + // copy the first part + for (int i = 0; i < crossoverIndex; i++) { + child1Rep.add(parent1Rep.get(i)); + child2Rep.add(parent2Rep.get(i)); + } + // and switch the second part + for (int i = crossoverIndex; i < length; i++) { + child1Rep.add(parent2Rep.get(i)); + child2Rep.add(parent1Rep.get(i)); + } + + return new ChromosomePair(first.newFixedLengthChromosome(child1Rep), + second.newFixedLengthChromosome(child2Rep)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OrderedCrossover.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OrderedCrossover.java new file mode 100644 index 000000000..aca8ea956 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/OrderedCrossover.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Order 1 Crossover [OX1] builds offspring from ordered chromosomes by copying a + * consecutive slice from one parent, and filling up the remaining genes from the other + * parent as they appear. + *

    + * This policy works by applying the following rules: + *

      + *
    1. select a random slice of consecutive genes from parent 1
    2. + *
    3. copy the slice to child 1 and mark out the genes in parent 2
    4. + *
    5. starting from the right side of the slice, copy genes from parent 2 as they + * appear to child 1 if they are not yet marked out.
    6. + *
    + *

    + * Example (random sublist from index 3 to 7, underlined): + *

    + * p1 = (8 4 7 3 6 2 5 1 9 0)   X   c1 = (0 4 7 3 6 2 5 1 8 9)
    + *             ---------                        ---------
    + * p2 = (0 1 2 3 4 5 6 7 8 9)   X   c2 = (8 1 2 3 4 5 6 7 9 0)
    + * 
    + *

    + * This policy works only on {@link AbstractListChromosome}, and therefore it + * is parameterized by T. Moreover, the chromosomes must have same lengths. + * + * @see + * Order 1 Crossover Operator + * + * @param generic type of the {@link AbstractListChromosome}s for crossover + * @since 3.1 + */ +public class OrderedCrossover implements CrossoverPolicy { + + /** + * {@inheritDoc} + * + * @throws MathIllegalArgumentException iff one of the chromosomes is + * not an instance of {@link AbstractListChromosome} + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + @SuppressWarnings("unchecked") + public ChromosomePair crossover(final Chromosome first, final Chromosome second) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (!(first instanceof AbstractListChromosome && second instanceof AbstractListChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); + } + return mate((AbstractListChromosome) first, (AbstractListChromosome) second); + } + + /** + * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. + * + * @param first the first chromosome + * @param second the second chromosome + * @return the pair of new chromosomes that resulted from the crossover + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + protected ChromosomePair mate(final AbstractListChromosome first, final AbstractListChromosome second) + throws DimensionMismatchException { + + final int length = first.getLength(); + if (length != second.getLength()) { + throw new DimensionMismatchException(second.getLength(), length); + } + + // array representations of the parents + final List parent1Rep = first.getRepresentation(); + final List parent2Rep = second.getRepresentation(); + // and of the children + final List child1 = new ArrayList(length); + final List child2 = new ArrayList(length); + // sets of already inserted items for quick access + final Set child1Set = new HashSet(length); + final Set child2Set = new HashSet(length); + + final RandomGenerator random = GeneticAlgorithm.getRandomGenerator(); + // choose random points, making sure that lb < ub. + int a = random.nextInt(length); + int b; + do { + b = random.nextInt(length); + } while (a == b); + // determine the lower and upper bounds + final int lb = FastMath.min(a, b); + final int ub = FastMath.max(a, b); + + // add the subLists that are between lb and ub + child1.addAll(parent1Rep.subList(lb, ub + 1)); + child1Set.addAll(child1); + child2.addAll(parent2Rep.subList(lb, ub + 1)); + child2Set.addAll(child2); + + // iterate over every item in the parents + for (int i = 1; i <= length; i++) { + final int idx = (ub + i) % length; + + // retrieve the current item in each parent + final T item1 = parent1Rep.get(idx); + final T item2 = parent2Rep.get(idx); + + // if the first child already contains the item in the second parent add it + if (!child1Set.contains(item2)) { + child1.add(item2); + child1Set.add(item2); + } + + // if the second child already contains the item in the first parent add it + if (!child2Set.contains(item1)) { + child2.add(item1); + child2Set.add(item1); + } + } + + // rotate so that the original slice is in the same place as in the parents. + Collections.rotate(child1, lb); + Collections.rotate(child2, lb); + + return new ChromosomePair(first.newFixedLengthChromosome(child1), + second.newFixedLengthChromosome(child2)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/PermutationChromosome.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/PermutationChromosome.java new file mode 100644 index 000000000..9da1fa66b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/PermutationChromosome.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.List; + +/** + * Interface indicating that the chromosome represents a permutation of objects. + * + * @param type of the permuted objects + * @since 2.0 + */ +public interface PermutationChromosome { + + /** + * Permutes the sequence 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 sequence represented by this permutation + */ + List decode(List sequence); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Population.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Population.java new file mode 100644 index 000000000..1e4c12b8c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/Population.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; + + +/** + * A collection of chromosomes that facilitates generational evolution. + * + * @since 2.0 + */ +public interface Population extends Iterable { + /** + * Access the current population size. + * @return the current population size. + */ + int getPopulationSize(); + + /** + * Access the maximum population size. + * @return the maximum population size. + */ + int getPopulationLimit(); + + /** + * Start the population for the next generation. + * @return the beginnings of the next generation. + */ + Population nextGeneration(); + + /** + * Add the given chromosome to the population. + * @param chromosome the chromosome to add. + * @throws NumberIsTooLargeException if the population would exceed the population limit when adding + * this chromosome + */ + void addChromosome(Chromosome chromosome) throws NumberIsTooLargeException; + + /** + * Access the fittest chromosome in this population. + * @return the fittest chromosome. + */ + Chromosome getFittestChromosome(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/RandomKey.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/RandomKey.java new file mode 100644 index 000000000..23a35546b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/RandomKey.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * 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. + *

    + * 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). + *

    + * With this representation, common operators like n-point crossover can be + * used, because any such chromosome represents a valid permutation. + *

    + * Since the chromosome (and thus its arrayRepresentation) is immutable, the + * array representation is sorted only once in the constructor. + *

    + * For details, see: + *

      + *
    • Bean, J.C.: Genetic algorithms and random keys for sequencing and + * optimization. ORSA Journal on Computing 6 (1994) 154-160
    • + *
    • Rothlauf, F.: Representations for Genetic and Evolutionary Algorithms. + * Volume 104 of Studies in Fuzziness and Soft Computing. Physica-Verlag, + * Heidelberg (2002)
    • + *
    + * + * @param type of the permuted objects + * @since 2.0 + */ +public abstract class RandomKey extends AbstractListChromosome implements PermutationChromosome { + + /** Cache of sorted representation (unmodifiable). */ + private final List sortedRepresentation; + + /** + * Base sequence [0,1,...,n-1], permuted according to the representation (unmodifiable). + */ + private final List baseSeqPermutation; + + /** + * Constructor. + * + * @param representation list of [0,1] values representing the permutation + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public RandomKey(final List representation) throws InvalidRepresentationException { + super(representation); + // store the sorted representation + List sortedRepr = new ArrayList (getRepresentation()); + Collections.sort(sortedRepr); + sortedRepresentation = Collections.unmodifiableList(sortedRepr); + // store the permutation of [0,1,...,n-1] list for toString() and isSame() methods + baseSeqPermutation = Collections.unmodifiableList( + decodeGeneric(baseSequence(getLength()), getRepresentation(), sortedRepresentation) + ); + } + + /** + * Constructor. + * + * @param representation array of [0,1] values representing the permutation + * @throws InvalidRepresentationException iff the representation can not represent a valid chromosome + */ + public RandomKey(final Double[] representation) throws InvalidRepresentationException { + this(Arrays.asList(representation)); + } + + /** + * {@inheritDoc} + */ + public List decode(final List sequence) { + return decodeGeneric(sequence, getRepresentation(), sortedRepresentation); + } + + /** + * Decodes a permutation represented by representation and + * returns a (generic) list with the permuted values. + * + * @param generic type of the sequence values + * @param sequence the unpermuted sequence + * @param representation representation of the permutation ([0,1] vector) + * @param sortedRepr sorted representation + * @return list with the sequence values permuted according to the representation + * @throws DimensionMismatchException iff the length of the sequence, + * representation or sortedRepr lists are not equal + */ + private static List decodeGeneric(final List sequence, List representation, + final List sortedRepr) + throws DimensionMismatchException { + + int l = sequence.size(); + + // the size of the three lists must be equal + if (representation.size() != l) { + throw new DimensionMismatchException(representation.size(), l); + } + if (sortedRepr.size() != l) { + throw new DimensionMismatchException(sortedRepr.size(), l); + } + + // do not modify the original representation + List reprCopy = new ArrayList (representation); + + // now find the indices in the original repr and use them for permuting + List res = new ArrayList (l); + for (int i=0; itrue iff another is a RandomKey and + * encodes the same permutation. + * + * @param another chromosome to compare + * @return true iff chromosomes encode the same permutation + */ + @Override + protected boolean isSame(final Chromosome another) { + // type check + if (! (another instanceof RandomKey)) { + return false; + } + RandomKey anotherRk = (RandomKey) another; + // size check + if (getLength() != anotherRk.getLength()) { + return false; + } + + // two different representations can still encode the same permutation + // the ordering is what counts + List thisPerm = this.baseSeqPermutation; + List anotherPerm = anotherRk.baseSeqPermutation; + + for (int i=0; i chromosomeRepresentation) + throws InvalidRepresentationException { + + for (double val : chromosomeRepresentation) { + if (val < 0 || val > 1) { + throw new InvalidRepresentationException(LocalizedFormats.OUT_OF_RANGE_SIMPLE, + val, 0, 1); + } + } + } + + + /** + * Generates a representation corresponding to a random permutation of + * length l which can be passed to the RandomKey constructor. + * + * @param l length of the permutation + * @return representation of a random permutation + */ + public static final List randomPermutation(final int l) { + List repr = new ArrayList(l); + for (int i=0; i identityPermutation(final int l) { + List repr = new ArrayList(l); + for (int i=0; idata sorted by comparator. The + * data is not modified during the process. + * + * This is useful if you want to inject some permutations to the initial + * population. + * + * @param 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 List comparatorPermutation(final List data, + final Comparator comparator) { + List sortedData = new ArrayList(data); + Collections.sort(sortedData, comparator); + + return inducedPermutation(data, sortedData); + } + + /** + * Generates a representation of a permutation corresponding to a + * permutation which yields permutedData when applied to + * originalData. + * + * This method can be viewed as an inverse to {@link #decode(List)}. + * + * @param 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 + * originalData -> permutedData + * @throws DimensionMismatchException iff the length of originalData + * and permutedData lists are not equal + * @throws MathIllegalArgumentException iff the permutedData and + * originalData lists contain different data + */ + public static List inducedPermutation(final List originalData, + final List permutedData) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (originalData.size() != permutedData.size()) { + throw new DimensionMismatchException(permutedData.size(), originalData.size()); + } + int l = originalData.size(); + + List origDataCopy = new ArrayList (originalData); + + Double[] res = new Double[l]; + for (int i=0; i baseSequence(final int l) { + List baseSequence = new ArrayList (l); + for (int i=0; ioriginal is not a {@link RandomKey} instance + */ + public Chromosome mutate(final Chromosome original) throws MathIllegalArgumentException { + if (!(original instanceof RandomKey)) { + throw new MathIllegalArgumentException(LocalizedFormats.RANDOMKEY_MUTATION_WRONG_CLASS, + original.getClass().getSimpleName()); + } + + RandomKey originalRk = (RandomKey) original; + List repr = originalRk.getRepresentation(); + int rInd = GeneticAlgorithm.getRandomGenerator().nextInt(repr.size()); + + List newRepr = new ArrayList (repr); + newRepr.set(rInd, GeneticAlgorithm.getRandomGenerator().nextDouble()); + + return originalRk.newFixedLengthChromosome(newRepr); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/SelectionPolicy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/SelectionPolicy.java new file mode 100644 index 000000000..e95929d0a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/SelectionPolicy.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 com.fr.third.org.apache.commons.math3.genetics; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Algorithm used to select a chromosome pair from a population. + * + * @since 2.0 + */ +public interface SelectionPolicy { + /** + * Select two chromosomes from the population. + * @param population the population from which the chromosomes are choosen. + * @return the selected chromosomes. + * @throws MathIllegalArgumentException if the population is not compatible with this {@link SelectionPolicy} + */ + ChromosomePair select(Population population) throws MathIllegalArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/StoppingCondition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/StoppingCondition.java new file mode 100644 index 000000000..d41d0b3ae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/StoppingCondition.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.genetics; + +/** + * Algorithm used to determine when to stop evolution. + * + * @since 2.0 + */ +public interface StoppingCondition { + /** + * Determine whether or not the given population satisfies the stopping condition. + * + * @param population the population to test. + * @return true if this stopping condition is met by the given population, + * false otherwise. + */ + boolean isSatisfied(Population population); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/TournamentSelection.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/TournamentSelection.java new file mode 100644 index 000000000..42cf08820 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/TournamentSelection.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 com.fr.third.org.apache.commons.math3.genetics; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Tournament selection scheme. Each of the two selected chromosomes is selected + * based on n-ary tournament -- this is done by drawing {@link #arity} random + * chromosomes without replacement from the population, and then selecting the + * fittest chromosome among them. + * + * @since 2.0 + */ +public class TournamentSelection implements SelectionPolicy { + + /** number of chromosomes included in the tournament selections */ + private int arity; + + /** + * Creates a new TournamentSelection instance. + * + * @param arity how many chromosomes will be drawn to the tournament + */ + public TournamentSelection(final int arity) { + this.arity = arity; + } + + /** + * Select two chromosomes from the population. Each of the two selected + * chromosomes is selected based on n-ary tournament -- this is done by + * drawing {@link #arity} random chromosomes without replacement from the + * population, and then selecting the fittest chromosome among them. + * + * @param population the population from which the chromosomes are chosen. + * @return the selected chromosomes. + * @throws MathIllegalArgumentException if the tournament arity is bigger than the population size + */ + public ChromosomePair select(final Population population) throws MathIllegalArgumentException { + return new ChromosomePair(tournament((ListPopulation) population), + tournament((ListPopulation) population)); + } + + /** + * Helper for {@link #select(Population)}. Draw {@link #arity} random chromosomes without replacement from the + * population, and then select the fittest chromosome among them. + * + * @param population the population from which the chromosomes are chosen. + * @return the selected chromosome. + * @throws MathIllegalArgumentException if the tournament arity is bigger than the population size + */ + private Chromosome tournament(final ListPopulation population) throws MathIllegalArgumentException { + if (population.getPopulationSize() < this.arity) { + throw new MathIllegalArgumentException(LocalizedFormats.TOO_LARGE_TOURNAMENT_ARITY, + arity, population.getPopulationSize()); + } + // auxiliary population + ListPopulation tournamentPopulation = new ListPopulation(this.arity) { + /** {@inheritDoc} */ + public Population nextGeneration() { + // not useful here + return null; + } + }; + + // create a copy of the chromosome list + List chromosomes = new ArrayList (population.getChromosomes()); + for (int i=0; i + * This crossover policy evaluates each gene of the parent chromosomes by chosing a + * uniform random number {@code p} in the range [0, 1]. If {@code p} < {@code ratio}, + * the parent genes are swapped. This means with a ratio of 0.7, 30% of the genes from the + * first parent and 70% from the second parent will be selected for the first offspring (and + * vice versa for the second offspring). + *

    + * This policy works only on {@link AbstractListChromosome}, and therefore it + * is parameterized by T. Moreover, the chromosomes must have same lengths. + * + * @see Crossover techniques (Wikipedia) + * @see Crossover (Obitko.com) + * @see Uniform crossover + * @param generic type of the {@link AbstractListChromosome}s for crossover + * @since 3.1 + */ +public class UniformCrossover implements CrossoverPolicy { + + /** The mixing ratio. */ + private final double ratio; + + /** + * Creates a new {@link UniformCrossover} policy using the given mixing ratio. + * + * @param ratio the mixing ratio + * @throws OutOfRangeException if the mixing ratio is outside the [0, 1] range + */ + public UniformCrossover(final double ratio) throws OutOfRangeException { + if (ratio < 0.0d || ratio > 1.0d) { + throw new OutOfRangeException(LocalizedFormats.CROSSOVER_RATE, ratio, 0.0d, 1.0d); + } + this.ratio = ratio; + } + + /** + * Returns the mixing ratio used by this {@link CrossoverPolicy}. + * + * @return the mixing ratio + */ + public double getRatio() { + return ratio; + } + + /** + * {@inheritDoc} + * + * @throws MathIllegalArgumentException iff one of the chromosomes is + * not an instance of {@link AbstractListChromosome} + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + @SuppressWarnings("unchecked") + public ChromosomePair crossover(final Chromosome first, final Chromosome second) + throws DimensionMismatchException, MathIllegalArgumentException { + + if (!(first instanceof AbstractListChromosome && second instanceof AbstractListChromosome)) { + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); + } + return mate((AbstractListChromosome) first, (AbstractListChromosome) second); + } + + /** + * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. + * + * @param first the first chromosome + * @param second the second chromosome + * @return the pair of new chromosomes that resulted from the crossover + * @throws DimensionMismatchException if the length of the two chromosomes is different + */ + private ChromosomePair mate(final AbstractListChromosome first, + final AbstractListChromosome second) throws DimensionMismatchException { + final int length = first.getLength(); + if (length != second.getLength()) { + throw new DimensionMismatchException(second.getLength(), length); + } + + // array representations of the parents + final List parent1Rep = first.getRepresentation(); + final List parent2Rep = second.getRepresentation(); + // and of the children + final List child1Rep = new ArrayList(length); + final List child2Rep = new ArrayList(length); + + final RandomGenerator random = GeneticAlgorithm.getRandomGenerator(); + + for (int index = 0; index < length; index++) { + + if (random.nextDouble() < ratio) { + // swap the bits -> take other parent + child1Rep.add(parent2Rep.get(index)); + child2Rep.add(parent1Rep.get(index)); + } else { + child1Rep.add(parent1Rep.get(index)); + child2Rep.add(parent2Rep.get(index)); + } + } + + return new ChromosomePair(first.newFixedLengthChromosome(child1Rep), + second.newFixedLengthChromosome(child2Rep)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/package-info.java new file mode 100644 index 000000000..8c29add7e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/genetics/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package provides Genetic Algorithms components and implementations. + */ +package com.fr.third.org.apache.commons.math3.genetics; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Point.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Point.java new file mode 100644 index 000000000..6b0f20624 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Point.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 com.fr.third.org.apache.commons.math3.geometry; + +import java.io.Serializable; + +/** This interface represents a generic geometrical point. + * @param Type of the space. + * @see Space + * @see Vector + * @since 3.3 + */ +public interface Point extends Serializable { + + /** Get the space to which the point belongs. + * @return containing space + */ + Space getSpace(); + + /** + * Returns true if any coordinate of this point is NaN; false otherwise + * @return true if any coordinate of this point is NaN; false otherwise + */ + boolean isNaN(); + + /** Compute the distance between the instance and another point. + * @param p second point + * @return the distance between the instance and p + */ + double distance(Point p); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Space.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Space.java new file mode 100644 index 000000000..e13ab21f3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Space.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 com.fr.third.org.apache.commons.math3.geometry; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; + +/** This interface represents a generic space, with affine and vectorial counterparts. + * @see Vector + * @since 3.0 + */ +public interface Space extends Serializable { + + /** Get the dimension of the space. + * @return dimension of the space + */ + int getDimension(); + + /** Get the n-1 dimension subspace of this space. + * @return n-1 dimension sub-space of this space + * @see #getDimension() + * @exception MathUnsupportedOperationException for dimension-1 spaces + * which do not have sub-spaces + */ + Space getSubSpace() throws MathUnsupportedOperationException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Vector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Vector.java new file mode 100644 index 000000000..c135db4f8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/Vector.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 com.fr.third.org.apache.commons.math3.geometry; + +import java.text.NumberFormat; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; + +/** This interface represents a generic vector in a vectorial space or a point in an affine space. + * @param Type of the space. + * @see Space + * @see Point + * @since 3.0 + */ +public interface Vector extends Point { + + /** Get the null vector of the vectorial space or origin point of the affine space. + * @return null vector of the vectorial space or origin point of the affine space + */ + Vector getZero(); + + /** Get the L1 norm for the vector. + * @return L1 norm for the vector + */ + double getNorm1(); + + /** Get the L2 norm for the vector. + * @return Euclidean norm for the vector + */ + double getNorm(); + + /** Get the square of the norm for the vector. + * @return square of the Euclidean norm for the vector + */ + double getNormSq(); + + /** Get the L norm for the vector. + * @return L norm for the vector + */ + double getNormInf(); + + /** Add a vector to the instance. + * @param v vector to add + * @return a new vector + */ + Vector add(Vector v); + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + Vector add(double factor, Vector v); + + /** Subtract a vector from the instance. + * @param v vector to subtract + * @return a new vector + */ + Vector subtract(Vector v); + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + Vector subtract(double factor, Vector v); + + /** Get the opposite of the instance. + * @return a new vector which is opposite to the instance + */ + Vector negate(); + + /** Get a normalized vector aligned with the instance. + * @return a new normalized vector + * @exception MathArithmeticException if the norm is zero + */ + Vector normalize() throws MathArithmeticException; + + /** Multiply the instance by a scalar. + * @param a scalar + * @return a new vector + */ + Vector scalarMultiply(double a); + + /** + * Returns true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + * @return true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + */ + boolean isInfinite(); + + /** Compute the distance between the instance and another vector according to the L1 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm1() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L1 norm + */ + double distance1(Vector v); + + /** Compute the distance between the instance and another vector according to the L2 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L2 norm + */ + double distance(Vector v); + + /** Compute the distance between the instance and another vector according to the L norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormInf() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L norm + */ + double distanceInf(Vector v); + + /** Compute the square of the distance between the instance and another vector. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormSq() except that no intermediate + * vector is built

    + * @param v second vector + * @return the square of the distance between the instance and p + */ + double distanceSq(Vector v); + + /** Compute the dot-product of the instance and another vector. + * @param v second vector + * @return the dot product this.v + */ + double dotProduct(Vector v); + + /** Get a string representation of this vector. + * @param format the custom format for components + * @return a string representation of this vector + */ + String toString(final NumberFormat format); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/VectorFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/VectorFormat.java new file mode 100644 index 000000000..4569b3e4b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/VectorFormat.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 com.fr.third.org.apache.commons.math3.geometry; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a vector in components list format "{x; y; ...}". + *

    The prefix and suffix "{" and "}" and the separator "; " can be replaced by + * any user-defined strings. The number format for components can be configured.

    + *

    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.

    + *

    Note: using "," as a separator may interfere with the grouping separator + * of the default {@link NumberFormat} for the current locale. Thus it is advised + * to use a {@link NumberFormat} instance with disabled grouping in such a case.

    + * + * @param Type of the space. + * @since 3.0 + */ +public abstract class VectorFormat { + + /** The default prefix: "{". */ + public static final String DEFAULT_PREFIX = "{"; + + /** The default suffix: "}". */ + public static final String DEFAULT_SUFFIX = "}"; + + /** The default separator: ", ". */ + public static final String DEFAULT_SEPARATOR = "; "; + + /** Prefix. */ + private final String prefix; + + /** Suffix. */ + private final String suffix; + + /** Separator. */ + private final String separator; + + /** Trimmed prefix. */ + private final String trimmedPrefix; + + /** Trimmed suffix. */ + private final String trimmedSuffix; + + /** Trimmed separator. */ + private final String trimmedSeparator; + + /** The format used for components. */ + private final NumberFormat format; + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and separator: + * "{", "}", and "; " and the default number format for components.

    + */ + protected VectorFormat() { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + protected VectorFormat(final NumberFormat format) { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + */ + protected VectorFormat(final String prefix, final String suffix, + final String separator) { + this(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + * @param format the custom format for components. + */ + protected VectorFormat(final String prefix, final String suffix, + final String separator, final NumberFormat format) { + this.prefix = prefix; + this.suffix = suffix; + this.separator = separator; + trimmedPrefix = prefix.trim(); + trimmedSuffix = suffix.trim(); + trimmedSeparator = separator.trim(); + this.format = format; + } + + /** + * Get the set of locales for which point/vector formats are available. + *

    This is the same set as the {@link NumberFormat} set.

    + * @return available point/vector format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * Get the format prefix. + * @return format prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Get the format suffix. + * @return format suffix. + */ + public String getSuffix() { + return suffix; + } + + /** + * Get the format separator between components. + * @return format separator. + */ + public String getSeparator() { + return separator; + } + + /** + * Get the components format. + * @return components format. + */ + public NumberFormat getFormat() { + return format; + } + + /** + * Formats a {@link Vector} object to produce a string. + * @param vector the object to format. + * @return a formatted string. + */ + public String format(Vector vector) { + return format(vector, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats a {@link Vector} object to produce a string. + * @param vector the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public abstract StringBuffer format(Vector vector, + StringBuffer toAppendTo, FieldPosition pos); + + /** + * Formats the coordinates of a {@link Vector} to produce a string. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @param coordinates coordinates of the object to format. + * @return the value passed in as toAppendTo. + */ + protected StringBuffer format(StringBuffer toAppendTo, FieldPosition pos, + double ... coordinates) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + // format prefix + toAppendTo.append(prefix); + + // format components + for (int i = 0; i < coordinates.length; ++i) { + if (i > 0) { + toAppendTo.append(separator); + } + CompositeFormat.formatDouble(coordinates[i], format, toAppendTo, pos); + } + + // format suffix + toAppendTo.append(suffix); + + return toAppendTo; + + } + + /** + * Parses a string to produce a {@link Vector} object. + * @param source the string to parse + * @return the parsed {@link Vector} object. + * @throws MathParseException if the beginning of the specified string + * cannot be parsed. + */ + public abstract Vector parse(String source) throws MathParseException; + + /** + * Parses a string to produce a {@link Vector} object. + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the parsed {@link Vector} object. + */ + public abstract Vector parse(String source, ParsePosition pos); + + /** + * Parses a string to produce an array of coordinates. + * @param dimension dimension of the space + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return coordinates array. + */ + protected double[] parseCoordinates(int dimension, String source, ParsePosition pos) { + + int initialIndex = pos.getIndex(); + double[] coordinates = new double[dimension]; + + // parse prefix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) { + return null; + } + + for (int i = 0; i < dimension; ++i) { + + // skip whitespace + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + + // parse separator + if (i > 0 && !CompositeFormat.parseFixedstring(source, trimmedSeparator, pos)) { + return null; + } + + // skip whitespace + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + + // parse coordinate + Number c = CompositeFormat.parseNumber(source, format, pos); + if (c == null) { + // invalid coordinate + // set index back to initial, error index should already be set + pos.setIndex(initialIndex); + return null; + } + + // store coordinate + coordinates[i] = c.doubleValue(); + + } + + // parse suffix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) { + return null; + } + + return coordinates; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/Encloser.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/Encloser.java new file mode 100644 index 000000000..2ee011902 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/Encloser.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.enclosing; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Interface for algorithms computing enclosing balls. + * @param Space type. + * @param

    Point type. + * @see EnclosingBall + * @since 3.3 + */ +public interface Encloser> { + + /** Find a ball enclosing a list of points. + * @param points points to enclose + * @return enclosing ball + */ + EnclosingBall enclose(Iterable

    points); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java new file mode 100644 index 000000000..42a16e3a4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.enclosing; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This class represents a ball enclosing some points. + * @param Space type. + * @param

    Point type. + * @see Space + * @see Point + * @see Encloser + * @since 3.3 + */ +public class EnclosingBall> implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140126L; + + /** Center of the ball. */ + private final P center; + + /** Radius of the ball. */ + private final double radius; + + /** Support points used to define the ball. */ + private final P[] support; + + /** Simple constructor. + * @param center center of the ball + * @param radius radius of the ball + * @param support support points used to define the ball + */ + public EnclosingBall(final P center, final double radius, final P ... support) { + this.center = center; + this.radius = radius; + this.support = support.clone(); + } + + /** Get the center of the ball. + * @return center of the ball + */ + public P getCenter() { + return center; + } + + /** Get the radius of the ball. + * @return radius of the ball (can be negative if the ball is empty) + */ + public double getRadius() { + return radius; + } + + /** Get the support points used to define the ball. + * @return support points used to define the ball + */ + public P[] getSupport() { + return support.clone(); + } + + /** Get the number of support points used to define the ball. + * @return number of support points used to define the ball + */ + public int getSupportSize() { + return support.length; + } + + /** Check if a point is within the ball or at boundary. + * @param point point to test + * @return true if the point is within the ball or at boundary + */ + public boolean contains(final P point) { + return point.distance(center) <= radius; + } + + /** Check if a point is within an enlarged ball or at boundary. + * @param point point to test + * @param margin margin to consider + * @return true if the point is within the ball enlarged + * by the margin or at boundary + */ + public boolean contains(final P point, final double margin) { + return point.distance(center) <= radius + margin; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java new file mode 100644 index 000000000..b843cd620 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.enclosing; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Interface for generating balls based on support points. + *

    + * This generator is used in the {@link WelzlEncloser Emo Welzl} algorithm + * and its derivatives. + *

    + * @param Space type. + * @param

    Point type. + * @see EnclosingBall + * @since 3.3 + */ +public interface SupportBallGenerator> { + + /** Create a ball whose boundary lies on prescribed support points. + * @param support support points (may be empty) + * @return ball whose boundary lies on the prescribed support points + */ + EnclosingBall ballOnSupport(List

    support); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java new file mode 100644 index 000000000..3819c2f37 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.enclosing; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time. + *

    + * The class implements the algorithm described in paper Smallest + * Enclosing Disks (Balls and Ellipsoids) by Emo Welzl, Lecture Notes in Computer Science + * 555 (1991) 359-370. The pivoting improvement published in the paper Fast and + * Robust Smallest Enclosing Balls, by Bernd Gärtner and further modified in + * paper + * Efficient Computation of Smallest Enclosing Balls in Three Dimensions by Linus Källberg + * to avoid performing local copies of data have been included. + *

    + * @param Space type. + * @param

    Point type. + * @since 3.3 + */ +public class WelzlEncloser> implements Encloser { + + /** Tolerance below which points are consider to be identical. */ + private final double tolerance; + + /** Generator for balls on support. */ + private final SupportBallGenerator generator; + + /** Simple constructor. + * @param tolerance below which points are consider to be identical + * @param generator generator for balls on support + */ + public WelzlEncloser(final double tolerance, final SupportBallGenerator generator) { + this.tolerance = tolerance; + this.generator = generator; + } + + /** {@inheritDoc} */ + public EnclosingBall enclose(final Iterable

    points) { + + if (points == null || !points.iterator().hasNext()) { + // return an empty ball + return generator.ballOnSupport(new ArrayList

    ()); + } + + // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements + return pivotingBall(points); + + } + + /** Compute enclosing ball using Gärtner's pivoting heuristic. + * @param points points to be enclosed + * @return enclosing ball + */ + private EnclosingBall pivotingBall(final Iterable

    points) { + + final P first = points.iterator().next(); + final List

    extreme = new ArrayList

    (first.getSpace().getDimension() + 1); + final List

    support = new ArrayList

    (first.getSpace().getDimension() + 1); + + // start with only first point selected as a candidate support + extreme.add(first); + EnclosingBall ball = moveToFrontBall(extreme, extreme.size(), support); + + while (true) { + + // select the point farthest to current ball + final P farthest = selectFarthest(points, ball); + + if (ball.contains(farthest, tolerance)) { + // we have found a ball containing all points + return ball; + } + + // recurse search, restricted to the small subset containing support and farthest point + support.clear(); + support.add(farthest); + EnclosingBall savedBall = ball; + ball = moveToFrontBall(extreme, extreme.size(), support); + if (ball.getRadius() < savedBall.getRadius()) { + // this should never happen + throw new MathInternalError(); + } + + // it was an interesting point, move it to the front + // according to Gärtner's heuristic + extreme.add(0, farthest); + + // prune the least interesting points + extreme.subList(ball.getSupportSize(), extreme.size()).clear(); + + + } + } + + /** Compute enclosing ball using Welzl's move to front heuristic. + * @param extreme subset of extreme points + * @param nbExtreme number of extreme points to consider + * @param support points that must belong to the ball support + * @return enclosing ball, for the extreme subset only + */ + private EnclosingBall moveToFrontBall(final List

    extreme, final int nbExtreme, + final List

    support) { + + // create a new ball on the prescribed support + EnclosingBall ball = generator.ballOnSupport(support); + + if (ball.getSupportSize() <= ball.getCenter().getSpace().getDimension()) { + + for (int i = 0; i < nbExtreme; ++i) { + final P pi = extreme.get(i); + if (!ball.contains(pi, tolerance)) { + + // we have found an outside point, + // enlarge the ball by adding it to the support + support.add(pi); + ball = moveToFrontBall(extreme, i, support); + support.remove(support.size() - 1); + + // it was an interesting point, move it to the front + // according to Welzl's heuristic + for (int j = i; j > 0; --j) { + extreme.set(j, extreme.get(j - 1)); + } + extreme.set(0, pi); + + } + } + + } + + return ball; + + } + + /** Select the point farthest to the current ball. + * @param points points to be enclosed + * @param ball current ball + * @return farthest point + */ + public P selectFarthest(final Iterable

    points, final EnclosingBall ball) { + + final P center = ball.getCenter(); + P farthest = null; + double dMax = -1.0; + + for (final P point : points) { + final double d = point.distance(center); + if (d > dMax) { + farthest = point; + dMax = d; + } + } + + return farthest; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/package-info.java new file mode 100644 index 000000000..e1d799d97 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/enclosing/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides interfaces and classes related to the smallest enclosing ball problem. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.enclosing; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java new file mode 100644 index 000000000..82a6ee6f8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** + * This class implements a one-dimensional space. + * @since 3.0 + */ +public class Euclidean1D implements Serializable, Space { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1178039568877797126L; + + /** Private constructor for the singleton. + */ + private Euclidean1D() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static Euclidean1D getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public int getDimension() { + return 1; + } + + /** {@inheritDoc} + *

    + * As the 1-dimension Euclidean space does not have proper sub-spaces, + * this method always throws a {@link NoSubSpaceException} + *

    + * @return nothing + * @throws NoSubSpaceException in all cases + */ + public Space getSubSpace() throws NoSubSpaceException { + throw new NoSubSpaceException(); + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final Euclidean1D INSTANCE = new Euclidean1D(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + + /** Specialized exception for inexistent sub-space. + *

    + * This exception is thrown when attempting to get the sub-space of a one-dimensional space + *

    + */ + public static class NoSubSpaceException extends MathUnsupportedOperationException { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140225L; + + /** Simple constructor. + */ + public NoSubSpaceException() { + super(LocalizedFormats.NOT_SUPPORTED_IN_DIMENSION_N, 1); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Interval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Interval.java new file mode 100644 index 000000000..7b876a7f2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Interval.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; + + +/** This class represents a 1D interval. + * @see IntervalsSet + * @since 3.0 + */ +public class Interval { + + /** The lower bound of the interval. */ + private final double lower; + + /** The upper bound of the interval. */ + private final double upper; + + /** Simple constructor. + * @param lower lower bound of the interval + * @param upper upper bound of the interval + */ + public Interval(final double lower, final double upper) { + if (upper < lower) { + throw new NumberIsTooSmallException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, + upper, lower, true); + } + this.lower = lower; + this.upper = upper; + } + + /** Get the lower bound of the interval. + * @return lower bound of the interval + * @since 3.1 + */ + public double getInf() { + return lower; + } + + /** Get the lower bound of the interval. + * @return lower bound of the interval + * @deprecated as of 3.1, replaced by {@link #getInf()} + */ + @Deprecated + public double getLower() { + return getInf(); + } + + /** Get the upper bound of the interval. + * @return upper bound of the interval + * @since 3.1 + */ + public double getSup() { + return upper; + } + + /** Get the upper bound of the interval. + * @return upper bound of the interval + * @deprecated as of 3.1, replaced by {@link #getSup()} + */ + @Deprecated + public double getUpper() { + return getSup(); + } + + /** Get the size of the interval. + * @return size of the interval + * @since 3.1 + */ + public double getSize() { + return upper - lower; + } + + /** Get the length of the interval. + * @return length of the interval + * @deprecated as of 3.1, replaced by {@link #getSize()} + */ + @Deprecated + public double getLength() { + return getSize(); + } + + /** Get the barycenter of the interval. + * @return barycenter of the interval + * @since 3.1 + */ + public double getBarycenter() { + return 0.5 * (lower + upper); + } + + /** Get the midpoint of the interval. + * @return midpoint of the interval + * @deprecated as of 3.1, replaced by {@link #getBarycenter()} + */ + @Deprecated + public double getMidPoint() { + return getBarycenter(); + } + + /** Check a point with respect to the interval. + * @param point point to check + * @param tolerance tolerance below which points are considered to + * belong to the boundary + * @return a code representing the point status: either {@link + * Region.Location#INSIDE}, {@link Region.Location#OUTSIDE} or {@link Region.Location#BOUNDARY} + * @since 3.1 + */ + public Region.Location checkPoint(final double point, final double tolerance) { + if (point < lower - tolerance || point > upper + tolerance) { + return Region.Location.OUTSIDE; + } else if (point > lower + tolerance && point < upper - tolerance) { + return Region.Location.INSIDE; + } else { + return Region.Location.BOUNDARY; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java new file mode 100644 index 000000000..766f2701c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryProjection; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** This class represents a 1D region: a set of intervals. + * @since 3.0 + */ +public class IntervalsSet extends AbstractRegion implements Iterable { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Build an intervals set representing the whole real line. + * @param tolerance tolerance below which points are considered identical. + * @since 3.3 + */ + public IntervalsSet(final double tolerance) { + super(tolerance); + } + + /** Build an intervals set corresponding to a single interval. + * @param lower lower bound of the interval, must be lesser or equal + * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY}) + * @param upper upper bound of the interval, must be greater or equal + * to {@code lower} (may be {@code Double.POSITIVE_INFINITY}) + * @param tolerance tolerance below which points are considered identical. + * @since 3.3 + */ + public IntervalsSet(final double lower, final double upper, final double tolerance) { + super(buildTree(lower, upper, tolerance), tolerance); + } + + /** Build an intervals set from an inside/outside BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the intervals set + * @param tolerance tolerance below which points are considered identical. + * @since 3.3 + */ + public IntervalsSet(final BSPTree tree, final double tolerance) { + super(tree, tolerance); + } + + /** Build an intervals set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoints polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements + * @param tolerance tolerance below which points are considered identical. + * @since 3.3 + */ + public IntervalsSet(final Collection> boundary, + final double tolerance) { + super(boundary, tolerance); + } + + /** Build an intervals set representing the whole real line. + * @deprecated as of 3.1 replaced with {@link #IntervalsSet(double)} + */ + @Deprecated + public IntervalsSet() { + this(DEFAULT_TOLERANCE); + } + + /** Build an intervals set corresponding to a single interval. + * @param lower lower bound of the interval, must be lesser or equal + * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY}) + * @param upper upper bound of the interval, must be greater or equal + * to {@code lower} (may be {@code Double.POSITIVE_INFINITY}) + * @deprecated as of 3.3 replaced with {@link #IntervalsSet(double, double, double)} + */ + @Deprecated + public IntervalsSet(final double lower, final double upper) { + this(lower, upper, DEFAULT_TOLERANCE); + } + + /** Build an intervals set from an inside/outside BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the intervals set + * @deprecated as of 3.3, replaced with {@link #IntervalsSet(BSPTree, double)} + */ + @Deprecated + public IntervalsSet(final BSPTree tree) { + this(tree, DEFAULT_TOLERANCE); + } + + /** Build an intervals set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoints polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements + * @deprecated as of 3.3, replaced with {@link #IntervalsSet(Collection, double)} + */ + @Deprecated + public IntervalsSet(final Collection> boundary) { + this(boundary, DEFAULT_TOLERANCE); + } + + /** Build an inside/outside tree representing a single interval. + * @param lower lower bound of the interval, must be lesser or equal + * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY}) + * @param upper upper bound of the interval, must be greater or equal + * to {@code lower} (may be {@code Double.POSITIVE_INFINITY}) + * @param tolerance tolerance below which points are considered identical. + * @return the built tree + */ + private static BSPTree buildTree(final double lower, final double upper, + final double tolerance) { + if (Double.isInfinite(lower) && (lower < 0)) { + if (Double.isInfinite(upper) && (upper > 0)) { + // the tree must cover the whole real line + return new BSPTree(Boolean.TRUE); + } + // the tree must be open on the negative infinity side + final SubHyperplane upperCut = + new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane(); + return new BSPTree(upperCut, + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null); + } + final SubHyperplane lowerCut = + new OrientedPoint(new Vector1D(lower), false, tolerance).wholeHyperplane(); + if (Double.isInfinite(upper) && (upper > 0)) { + // the tree must be open on the positive infinity side + return new BSPTree(lowerCut, + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null); + } + + // the tree must be bounded on the two sides + final SubHyperplane upperCut = + new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane(); + return new BSPTree(lowerCut, + new BSPTree(Boolean.FALSE), + new BSPTree(upperCut, + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null), + null); + + } + + /** {@inheritDoc} */ + @Override + public IntervalsSet buildNew(final BSPTree tree) { + return new IntervalsSet(tree, getTolerance()); + } + + /** {@inheritDoc} */ + @Override + protected void computeGeometricalProperties() { + if (getTree(false).getCut() == null) { + setBarycenter((Point) Vector1D.NaN); + setSize(((Boolean) getTree(false).getAttribute()) ? Double.POSITIVE_INFINITY : 0); + } else { + double size = 0.0; + double sum = 0.0; + for (final Interval interval : asList()) { + size += interval.getSize(); + sum += interval.getSize() * interval.getBarycenter(); + } + setSize(size); + if (Double.isInfinite(size)) { + setBarycenter((Point) Vector1D.NaN); + } else if (size >= Precision.SAFE_MIN) { + setBarycenter((Point) new Vector1D(sum / size)); + } else { + setBarycenter((Point) ((OrientedPoint) getTree(false).getCut().getHyperplane()).getLocation()); + } + } + } + + /** Get the lowest value belonging to the instance. + * @return lowest value belonging to the instance + * ({@code Double.NEGATIVE_INFINITY} if the instance doesn't + * have any low bound, {@code Double.POSITIVE_INFINITY} if the + * instance is empty) + */ + public double getInf() { + BSPTree node = getTree(false); + double inf = Double.POSITIVE_INFINITY; + while (node.getCut() != null) { + final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane(); + inf = op.getLocation().getX(); + node = op.isDirect() ? node.getMinus() : node.getPlus(); + } + return ((Boolean) node.getAttribute()) ? Double.NEGATIVE_INFINITY : inf; + } + + /** Get the highest value belonging to the instance. + * @return highest value belonging to the instance + * ({@code Double.POSITIVE_INFINITY} if the instance doesn't + * have any high bound, {@code Double.NEGATIVE_INFINITY} if the + * instance is empty) + */ + public double getSup() { + BSPTree node = getTree(false); + double sup = Double.NEGATIVE_INFINITY; + while (node.getCut() != null) { + final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane(); + sup = op.getLocation().getX(); + node = op.isDirect() ? node.getPlus() : node.getMinus(); + } + return ((Boolean) node.getAttribute()) ? Double.POSITIVE_INFINITY : sup; + } + + /** {@inheritDoc} + * @since 3.3 + */ + @Override + public BoundaryProjection projectToBoundary(final Point point) { + + // get position of test point + final double x = ((Vector1D) point).getX(); + + double previous = Double.NEGATIVE_INFINITY; + for (final double[] a : this) { + if (x < a[0]) { + // the test point lies between the previous and the current intervals + // offset will be positive + final double previousOffset = x - previous; + final double currentOffset = a[0] - x; + if (previousOffset < currentOffset) { + return new BoundaryProjection(point, finiteOrNullPoint(previous), previousOffset); + } else { + return new BoundaryProjection(point, finiteOrNullPoint(a[0]), currentOffset); + } + } else if (x <= a[1]) { + // the test point lies within the current interval + // offset will be negative + final double offset0 = a[0] - x; + final double offset1 = x - a[1]; + if (offset0 < offset1) { + return new BoundaryProjection(point, finiteOrNullPoint(a[1]), offset1); + } else { + return new BoundaryProjection(point, finiteOrNullPoint(a[0]), offset0); + } + } + previous = a[1]; + } + + // the test point if past the last sub-interval + return new BoundaryProjection(point, finiteOrNullPoint(previous), x - previous); + + } + + /** Build a finite point. + * @param x abscissa of the point + * @return a new point for finite abscissa, null otherwise + */ + private Vector1D finiteOrNullPoint(final double x) { + return Double.isInfinite(x) ? null : new Vector1D(x); + } + + /** Build an ordered list of intervals representing the instance. + *

    This method builds this intervals set as an ordered list of + * {@link Interval Interval} elements. If the intervals set has no + * lower limit, the first interval will have its low bound equal to + * {@code Double.NEGATIVE_INFINITY}. If the intervals set has + * no upper limit, the last interval will have its upper bound equal + * to {@code Double.POSITIVE_INFINITY}. An empty tree will + * build an empty list while a tree representing the whole real line + * will build a one element list with both bounds being + * infinite.

    + * @return a new ordered list containing {@link Interval Interval} + * elements + */ + public List asList() { + final List list = new ArrayList(); + for (final double[] a : this) { + list.add(new Interval(a[0], a[1])); + } + return list; + } + + /** Get the first leaf node of a tree. + * @param root tree root + * @return first leaf node + */ + private BSPTree getFirstLeaf(final BSPTree root) { + + if (root.getCut() == null) { + return root; + } + + // find the smallest internal node + BSPTree smallest = null; + for (BSPTree n = root; n != null; n = previousInternalNode(n)) { + smallest = n; + } + + return leafBefore(smallest); + + } + + /** Get the node corresponding to the first interval boundary. + * @return smallest internal node, + * or null if there are no internal nodes (i.e. the set is either empty or covers the real line) + */ + private BSPTree getFirstIntervalBoundary() { + + // start search at the tree root + BSPTree node = getTree(false); + if (node.getCut() == null) { + return null; + } + + // walk tree until we find the smallest internal node + node = getFirstLeaf(node).getParent(); + + // walk tree until we find an interval boundary + while (node != null && !(isIntervalStart(node) || isIntervalEnd(node))) { + node = nextInternalNode(node); + } + + return node; + + } + + /** Check if an internal node corresponds to the start abscissa of an interval. + * @param node internal node to check + * @return true if the node corresponds to the start abscissa of an interval + */ + private boolean isIntervalStart(final BSPTree node) { + + if ((Boolean) leafBefore(node).getAttribute()) { + // it has an inside cell before it, it may end an interval but not start it + return false; + } + + if (!(Boolean) leafAfter(node).getAttribute()) { + // it has an outside cell after it, it is a dummy cut away from real intervals + return false; + } + + // the cell has an outside before and an inside after it + // it is the start of an interval + return true; + + } + + /** Check if an internal node corresponds to the end abscissa of an interval. + * @param node internal node to check + * @return true if the node corresponds to the end abscissa of an interval + */ + private boolean isIntervalEnd(final BSPTree node) { + + if (!(Boolean) leafBefore(node).getAttribute()) { + // it has an outside cell before it, it may start an interval but not end it + return false; + } + + if ((Boolean) leafAfter(node).getAttribute()) { + // it has an inside cell after it, it is a dummy cut in the middle of an interval + return false; + } + + // the cell has an inside before and an outside after it + // it is the end of an interval + return true; + + } + + /** Get the next internal node. + * @param node current internal node + * @return next internal node in ascending order, or null + * if this is the last internal node + */ + private BSPTree nextInternalNode(BSPTree node) { + + if (childAfter(node).getCut() != null) { + // the next node is in the sub-tree + return leafAfter(node).getParent(); + } + + // there is nothing left deeper in the tree, we backtrack + while (isAfterParent(node)) { + node = node.getParent(); + } + return node.getParent(); + + } + + /** Get the previous internal node. + * @param node current internal node + * @return previous internal node in ascending order, or null + * if this is the first internal node + */ + private BSPTree previousInternalNode(BSPTree node) { + + if (childBefore(node).getCut() != null) { + // the next node is in the sub-tree + return leafBefore(node).getParent(); + } + + // there is nothing left deeper in the tree, we backtrack + while (isBeforeParent(node)) { + node = node.getParent(); + } + return node.getParent(); + + } + + /** Find the leaf node just before an internal node. + * @param node internal node at which the sub-tree starts + * @return leaf node just before the internal node + */ + private BSPTree leafBefore(BSPTree node) { + + node = childBefore(node); + while (node.getCut() != null) { + node = childAfter(node); + } + + return node; + + } + + /** Find the leaf node just after an internal node. + * @param node internal node at which the sub-tree starts + * @return leaf node just after the internal node + */ + private BSPTree leafAfter(BSPTree node) { + + node = childAfter(node); + while (node.getCut() != null) { + node = childBefore(node); + } + + return node; + + } + + /** Check if a node is the child before its parent in ascending order. + * @param node child node considered + * @return true is the node has a parent end is before it in ascending order + */ + private boolean isBeforeParent(final BSPTree node) { + final BSPTree parent = node.getParent(); + if (parent == null) { + return false; + } else { + return node == childBefore(parent); + } + } + + /** Check if a node is the child after its parent in ascending order. + * @param node child node considered + * @return true is the node has a parent end is after it in ascending order + */ + private boolean isAfterParent(final BSPTree node) { + final BSPTree parent = node.getParent(); + if (parent == null) { + return false; + } else { + return node == childAfter(parent); + } + } + + /** Find the child node just before an internal node. + * @param node internal node at which the sub-tree starts + * @return child node just before the internal node + */ + private BSPTree childBefore(BSPTree node) { + if (isDirect(node)) { + // smaller abscissas are on minus side, larger abscissas are on plus side + return node.getMinus(); + } else { + // smaller abscissas are on plus side, larger abscissas are on minus side + return node.getPlus(); + } + } + + /** Find the child node just after an internal node. + * @param node internal node at which the sub-tree starts + * @return child node just after the internal node + */ + private BSPTree childAfter(BSPTree node) { + if (isDirect(node)) { + // smaller abscissas are on minus side, larger abscissas are on plus side + return node.getPlus(); + } else { + // smaller abscissas are on plus side, larger abscissas are on minus side + return node.getMinus(); + } + } + + /** Check if an internal node has a direct oriented point. + * @param node internal node to check + * @return true if the oriented point is direct + */ + private boolean isDirect(final BSPTree node) { + return ((OrientedPoint) node.getCut().getHyperplane()).isDirect(); + } + + /** Get the abscissa of an internal node. + * @param node internal node to check + * @return abscissa + */ + private double getAngle(final BSPTree node) { + return ((OrientedPoint) node.getCut().getHyperplane()).getLocation().getX(); + } + + /** {@inheritDoc} + *

    + * The iterator returns the limit values of sub-intervals in ascending order. + *

    + *

    + * The iterator does not support the optional {@code remove} operation. + *

    + * @since 3.3 + */ + public Iterator iterator() { + return new SubIntervalsIterator(); + } + + /** Local iterator for sub-intervals. */ + private class SubIntervalsIterator implements Iterator { + + /** Current node. */ + private BSPTree current; + + /** Sub-interval no yet returned. */ + private double[] pending; + + /** Simple constructor. + */ + SubIntervalsIterator() { + + current = getFirstIntervalBoundary(); + + if (current == null) { + // all the leaf tree nodes share the same inside/outside status + if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) { + // it is an inside node, it represents the full real line + pending = new double[] { + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY + }; + } else { + pending = null; + } + } else if (isIntervalEnd(current)) { + // the first boundary is an interval end, + // so the first interval starts at infinity + pending = new double[] { + Double.NEGATIVE_INFINITY, getAngle(current) + }; + } else { + selectPending(); + } + } + + /** Walk the tree to select the pending sub-interval. + */ + private void selectPending() { + + // look for the start of the interval + BSPTree start = current; + while (start != null && !isIntervalStart(start)) { + start = nextInternalNode(start); + } + + if (start == null) { + // we have exhausted the iterator + current = null; + pending = null; + return; + } + + // look for the end of the interval + BSPTree end = start; + while (end != null && !isIntervalEnd(end)) { + end = nextInternalNode(end); + } + + if (end != null) { + + // we have identified the interval + pending = new double[] { + getAngle(start), getAngle(end) + }; + + // prepare search for next interval + current = end; + + } else { + + // the final interval is open toward infinity + pending = new double[] { + getAngle(start), Double.POSITIVE_INFINITY + }; + + // there won't be any other intervals + current = null; + + } + + } + + /** {@inheritDoc} */ + public boolean hasNext() { + return pending != null; + } + + /** {@inheritDoc} */ + public double[] next() { + if (pending == null) { + throw new NoSuchElementException(); + } + final double[] next = pending; + selectPending(); + return next; + } + + /** {@inheritDoc} */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.java new file mode 100644 index 000000000..74f96b3d0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.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 com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Vector; + +/** This class represents a 1D oriented hyperplane. + *

    An hyperplane in 1D is a simple point, its orientation being a + * boolean.

    + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.0 + */ +public class OrientedPoint implements Hyperplane { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Vector location. */ + private Vector1D location; + + /** Orientation. */ + private boolean direct; + + /** Tolerance below which points are considered to belong to the hyperplane. */ + private final double tolerance; + + /** Simple constructor. + * @param location location of the hyperplane + * @param direct if true, the plus side of the hyperplane is towards + * abscissas greater than {@code location} + * @param tolerance tolerance below which points are considered to belong to the hyperplane + * @since 3.3 + */ + public OrientedPoint(final Vector1D location, final boolean direct, final double tolerance) { + this.location = location; + this.direct = direct; + this.tolerance = tolerance; + } + + /** Simple constructor. + * @param location location of the hyperplane + * @param direct if true, the plus side of the hyperplane is towards + * abscissas greater than {@code location} + * @deprecated as of 3.3, replaced with {@link #OrientedPoint(Vector1D, boolean, double)} + */ + @Deprecated + public OrientedPoint(final Vector1D location, final boolean direct) { + this(location, direct, DEFAULT_TOLERANCE); + } + + /** Copy the instance. + *

    Since instances are immutable, this method directly returns + * the instance.

    + * @return the instance itself + */ + public OrientedPoint copySelf() { + return this; + } + + /** Get the offset (oriented distance) of a vector. + * @param vector vector to check + * @return offset of the vector + */ + public double getOffset(Vector vector) { + return getOffset((Point) vector); + } + + /** {@inheritDoc} */ + public double getOffset(final Point point) { + final double delta = ((Vector1D) point).getX() - location.getX(); + return direct ? delta : -delta; + } + + /** Build a region covering the whole hyperplane. + *

    Since this class represent zero dimension spaces which does + * not have lower dimension sub-spaces, this method returns a dummy + * implementation of a {@link + * SubHyperplane SubHyperplane}. + * This implementation is only used to allow the {@link + * SubHyperplane + * SubHyperplane} class implementation to work properly, it should + * not be used otherwise.

    + * @return a dummy sub hyperplane + */ + public SubOrientedPoint wholeHyperplane() { + return new SubOrientedPoint(this, null); + } + + /** Build a region covering the whole space. + * @return a region containing the instance (really an {@link + * IntervalsSet IntervalsSet} instance) + */ + public IntervalsSet wholeSpace() { + return new IntervalsSet(tolerance); + } + + /** {@inheritDoc} */ + public boolean sameOrientationAs(final Hyperplane other) { + return !(direct ^ ((OrientedPoint) other).direct); + } + + /** {@inheritDoc} + * @since 3.3 + */ + public Point project(Point point) { + return location; + } + + /** {@inheritDoc} + * @since 3.3 + */ + public double getTolerance() { + return tolerance; + } + + /** Get the hyperplane location on the real line. + * @return the hyperplane location + */ + public Vector1D getLocation() { + return location; + } + + /** Check if the hyperplane orientation is direct. + * @return true if the plus side of the hyperplane is towards + * abscissae greater than hyperplane location + */ + public boolean isDirect() { + return direct; + } + + /** Revert the instance. + */ + public void revertSelf() { + direct = !direct; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java new file mode 100644 index 000000000..9d3a7c898 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; + +/** This class represents sub-hyperplane for {@link OrientedPoint}. + *

    An hyperplane in 1D is a simple point, its orientation being a + * boolean.

    + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.0 + */ +public class SubOrientedPoint extends AbstractSubHyperplane { + + /** Simple constructor. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + public SubOrientedPoint(final Hyperplane hyperplane, + final Region remainingRegion) { + super(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public double getSize() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean isEmpty() { + return false; + } + + /** {@inheritDoc} */ + @Override + protected AbstractSubHyperplane buildNew(final Hyperplane hyperplane, + final Region remainingRegion) { + return new SubOrientedPoint(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public SplitSubHyperplane split(final Hyperplane hyperplane) { + final double global = hyperplane.getOffset(((OrientedPoint) getHyperplane()).getLocation()); + if (global < -1.0e-10) { + return new SplitSubHyperplane(null, this); + } else if (global > 1.0e-10) { + return new SplitSubHyperplane(this, null); + } else { + return new SplitSubHyperplane(null, null); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java new file mode 100644 index 000000000..e90991f54 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import java.text.NumberFormat; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents a 1D vector. + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.0 + */ +public class Vector1D implements Vector { + + /** Origin (coordinates: 0). */ + public static final Vector1D ZERO = new Vector1D(0.0); + + /** Unit (coordinates: 1). */ + public static final Vector1D ONE = new Vector1D(1.0); + + // CHECKSTYLE: stop ConstantName + /** A vector with all coordinates set to NaN. */ + public static final Vector1D NaN = new Vector1D(Double.NaN); + // CHECKSTYLE: resume ConstantName + + /** A vector with all coordinates set to positive infinity. */ + public static final Vector1D POSITIVE_INFINITY = + new Vector1D(Double.POSITIVE_INFINITY); + + /** A vector with all coordinates set to negative infinity. */ + public static final Vector1D NEGATIVE_INFINITY = + new Vector1D(Double.NEGATIVE_INFINITY); + + /** Serializable UID. */ + private static final long serialVersionUID = 7556674948671647925L; + + /** Abscissa. */ + private final double x; + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @see #getX() + */ + public Vector1D(double x) { + this.x = x; + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector1D(double a, Vector1D u) { + this.x = a * u.x; + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2) { + this.x = a1 * u1.x + a2 * u2.x; + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2, + double a3, Vector1D u3) { + this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x; + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2, + double a3, Vector1D u3, double a4, Vector1D u4) { + this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x; + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #Vector1D(double) + */ + public double getX() { + return x; + } + + /** {@inheritDoc} */ + public Space getSpace() { + return Euclidean1D.getInstance(); + } + + /** {@inheritDoc} */ + public Vector1D getZero() { + return ZERO; + } + + /** {@inheritDoc} */ + public double getNorm1() { + return FastMath.abs(x); + } + + /** {@inheritDoc} */ + public double getNorm() { + return FastMath.abs(x); + } + + /** {@inheritDoc} */ + public double getNormSq() { + return x * x; + } + + /** {@inheritDoc} */ + public double getNormInf() { + return FastMath.abs(x); + } + + /** {@inheritDoc} */ + public Vector1D add(Vector v) { + Vector1D v1 = (Vector1D) v; + return new Vector1D(x + v1.getX()); + } + + /** {@inheritDoc} */ + public Vector1D add(double factor, Vector v) { + Vector1D v1 = (Vector1D) v; + return new Vector1D(x + factor * v1.getX()); + } + + /** {@inheritDoc} */ + public Vector1D subtract(Vector p) { + Vector1D p3 = (Vector1D) p; + return new Vector1D(x - p3.x); + } + + /** {@inheritDoc} */ + public Vector1D subtract(double factor, Vector v) { + Vector1D v1 = (Vector1D) v; + return new Vector1D(x - factor * v1.getX()); + } + + /** {@inheritDoc} */ + public Vector1D normalize() throws MathArithmeticException { + double s = getNorm(); + if (s == 0) { + throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); + } + return scalarMultiply(1 / s); + } + /** {@inheritDoc} */ + public Vector1D negate() { + return new Vector1D(-x); + } + + /** {@inheritDoc} */ + public Vector1D scalarMultiply(double a) { + return new Vector1D(a * x); + } + + /** {@inheritDoc} */ + public boolean isNaN() { + return Double.isNaN(x); + } + + /** {@inheritDoc} */ + public boolean isInfinite() { + return !isNaN() && Double.isInfinite(x); + } + + /** {@inheritDoc} */ + public double distance1(Vector p) { + Vector1D p3 = (Vector1D) p; + final double dx = FastMath.abs(p3.x - x); + return dx; + } + + /** {@inheritDoc} + * @deprecated as of 3.3, replaced with {@link #distance(Point)} + */ + @Deprecated + public double distance(Vector p) { + return distance((Point) p); + } + + /** {@inheritDoc} */ + public double distance(Point p) { + Vector1D p3 = (Vector1D) p; + final double dx = p3.x - x; + return FastMath.abs(dx); + } + + /** {@inheritDoc} */ + public double distanceInf(Vector p) { + Vector1D p3 = (Vector1D) p; + final double dx = FastMath.abs(p3.x - x); + return dx; + } + + /** {@inheritDoc} */ + public double distanceSq(Vector p) { + Vector1D p3 = (Vector1D) p; + final double dx = p3.x - x; + return dx * dx; + } + + /** {@inheritDoc} */ + public double dotProduct(final Vector v) { + final Vector1D v1 = (Vector1D) v; + return x * v1.x; + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNorm() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the distance between p1 and p2 according to the L2 norm + */ + public static double distance(Vector1D p1, Vector1D p2) { + return p1.distance(p2); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNormInf() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the distance between p1 and p2 according to the L norm + */ + public static double distanceInf(Vector1D p1, Vector1D p2) { + return p1.distanceInf(p2); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNormSq() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the square of the distance between p1 and p2 + */ + public static double distanceSq(Vector1D p1, Vector1D p2) { + return p1.distanceSq(p2); + } + + /** + * Test for the equality of two 1D vectors. + *

    + * If all coordinates of two 1D vectors are exactly the same, and none are + * Double.NaN, the two 1D vectors are considered to be equal. + *

    + *

    + * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) coordinates of the + * 1D vector are equal to Double.NaN, the 1D vector is equal to + * {@link #NaN}. + *

    + * + * @param other Object to test for equality to this + * @return true if two 1D vector objects are equal, false if + * object is null, not an instance of Vector1D, or + * not equal to this Vector1D instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof Vector1D) { + final Vector1D rhs = (Vector1D)other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return x == rhs.x; + } + return false; + } + + /** + * Get a hashCode for the 1D vector. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 7785; + } + return 997 * MathUtils.hash(x); + } + + /** Get a string representation of this vector. + * @return a string representation of this vector + */ + @Override + public String toString() { + return Vector1DFormat.getInstance().format(this); + } + + /** {@inheritDoc} */ + public String toString(final NumberFormat format) { + return new Vector1DFormat(format).format(this); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java new file mode 100644 index 000000000..b5878eee7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.VectorFormat; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a 1D vector in components list format "{x}". + *

    The prefix and suffix "{" and "}" can be replaced by + * any user-defined strings. The number format for components can be configured.

    + *

    White space is ignored at parse time, even if it is in the prefix, suffix + * or separator specifications. So even if the default separator does include a space + * character that is used at format time, both input string "{1}" and + * " { 1 } " will be parsed without error and the same vector will be + * returned. In the second case, however, the parse position after parsing will be + * just after the closing curly brace, i.e. just before the trailing space.

    + *

    Note: using "," as a separator may interfere with the grouping separator + * of the default {@link NumberFormat} for the current locale. Thus it is advised + * to use a {@link NumberFormat} instance with disabled grouping in such a case.

    + * + * @since 3.0 + */ +public class Vector1DFormat extends VectorFormat { + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and separator: + * "{", "}", and "; " and the default number format for components.

    + */ + public Vector1DFormat() { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + public Vector1DFormat(final NumberFormat format) { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + */ + public Vector1DFormat(final String prefix, final String suffix) { + super(prefix, suffix, DEFAULT_SEPARATOR, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param format the custom format for components. + */ + public Vector1DFormat(final String prefix, final String suffix, + final NumberFormat format) { + super(prefix, suffix, DEFAULT_SEPARATOR, format); + } + + /** + * Returns the default 1D vector format for the current locale. + * @return the default 1D vector format. + */ + public static Vector1DFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default 1D vector format for the given locale. + * @param locale the specific locale used by the format. + * @return the 1D vector format specific to the given locale. + */ + public static Vector1DFormat getInstance(final Locale locale) { + return new Vector1DFormat(CompositeFormat.getDefaultNumberFormat(locale)); + } + + /** {@inheritDoc} */ + @Override + public StringBuffer format(final Vector vector, final StringBuffer toAppendTo, + final FieldPosition pos) { + final Vector1D p1 = (Vector1D) vector; + return format(toAppendTo, pos, p1.getX()); + } + + /** {@inheritDoc} */ + @Override + public Vector1D parse(final String source) throws MathParseException { + ParsePosition parsePosition = new ParsePosition(0); + Vector1D result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + Vector1D.class); + } + return result; + } + + /** {@inheritDoc} */ + @Override + public Vector1D parse(final String source, final ParsePosition pos) { + final double[] coordinates = parseCoordinates(1, source, pos); + if (coordinates == null) { + return null; + } + return new Vector1D(coordinates[0]); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/package-info.java new file mode 100644 index 000000000..63ceb07dc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/oned/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides basic 1D geometry components. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.oned; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java new file mode 100644 index 000000000..da25eeeb7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** This class represents exceptions thrown while extractiong Cardan + * or Euler angles from a rotation. + + * @since 1.2 + */ +public class CardanEulerSingularityException + extends MathIllegalStateException { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1360952845582206770L; + + /** + * Simple constructor. + * build an exception with a default message. + * @param isCardan if true, the rotation is related to Cardan angles, + * if false it is related to EulerAngles + */ + public CardanEulerSingularityException(boolean isCardan) { + super(isCardan ? LocalizedFormats.CARDAN_ANGLES_SINGULARITY : LocalizedFormats.EULER_ANGLES_SINGULARITY); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java new file mode 100644 index 000000000..870c5e24b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; + +/** + * This class implements a three-dimensional space. + * @since 3.0 + */ +public class Euclidean3D implements Serializable, Space { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 6249091865814886817L; + + /** Private constructor for the singleton. + */ + private Euclidean3D() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static Euclidean3D getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public int getDimension() { + return 3; + } + + /** {@inheritDoc} */ + public Euclidean2D getSubSpace() { + return Euclidean2D.getInstance(); + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final Euclidean3D INSTANCE = new Euclidean3D(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java new file mode 100644 index 000000000..ec519fbcf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java @@ -0,0 +1,1663 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * This class is a re-implementation of {@link Rotation} using {@link RealFieldElement}. + *

    Instance of this class are guaranteed to be immutable.

    + * + * @param the type of the field elements + * @see FieldVector3D + * @see RotationOrder + * @since 3.2 + */ + +public class FieldRotation> implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20130224l; + + /** Scalar coordinate of the quaternion. */ + private final T q0; + + /** First coordinate of the vectorial part of the quaternion. */ + private final T q1; + + /** Second coordinate of the vectorial part of the quaternion. */ + private final T q2; + + /** Third coordinate of the vectorial part of the quaternion. */ + private final T q3; + + /** Build a rotation from the quaternion coordinates. + *

    A rotation can be built from a normalized quaternion, + * i.e. a quaternion for which q02 + + * q12 + q22 + + * q32 = 1. If the quaternion is not normalized, + * the constructor can normalize it in a preprocessing step.

    + *

    Note that some conventions put the scalar part of the quaternion + * as the 4th component and the vector part as the first three + * components. This is not our convention. We put the scalar part + * as the first component.

    + * @param q0 scalar part of the quaternion + * @param q1 first coordinate of the vectorial part of the quaternion + * @param q2 second coordinate of the vectorial part of the quaternion + * @param q3 third coordinate of the vectorial part of the quaternion + * @param needsNormalization if true, the coordinates are considered + * not to be normalized, a normalization preprocessing step is performed + * before using them + */ + public FieldRotation(final T q0, final T q1, final T q2, final T q3, final boolean needsNormalization) { + + if (needsNormalization) { + // normalization preprocessing + final T inv = + q0.multiply(q0).add(q1.multiply(q1)).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().reciprocal(); + this.q0 = inv.multiply(q0); + this.q1 = inv.multiply(q1); + this.q2 = inv.multiply(q2); + this.q3 = inv.multiply(q3); + } else { + this.q0 = q0; + this.q1 = q1; + this.q2 = q2; + this.q3 = q3; + } + + } + + /** Build a rotation from an axis and an angle. + *

    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(FieldVector3D) apply} the instance to +i, we will get + * +j.

    + *

    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!

    + *

    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.

    + * @param axis axis around which to rotate + * @param angle rotation angle. + * @exception MathIllegalArgumentException if the axis norm is zero + * @deprecated as of 3.6, replaced with {@link + * #FieldRotation(FieldVector3D, RealFieldElement, RotationConvention)} + */ + @Deprecated + public FieldRotation(final FieldVector3D axis, final T angle) + throws MathIllegalArgumentException { + this(axis, angle, RotationConvention.VECTOR_OPERATOR); + } + + /** Build a rotation from an axis and an angle. + *

    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(FieldVector3D) apply} the instance to +i, we will get + * +j.

    + *

    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!

    + *

    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.

    + * @param axis axis around which to rotate + * @param angle rotation angle. + * @param convention convention to use for the semantics of the angle + * @exception MathIllegalArgumentException if the axis norm is zero + * @since 3.6 + */ + public FieldRotation(final FieldVector3D axis, final T angle, final RotationConvention convention) + throws MathIllegalArgumentException { + + final T norm = axis.getNorm(); + if (norm.getReal() == 0) { + throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS); + } + + final T halfAngle = angle.multiply(convention == RotationConvention.VECTOR_OPERATOR ? -0.5 : 0.5); + final T coeff = halfAngle.sin().divide(norm); + + q0 = halfAngle.cos(); + q1 = coeff.multiply(axis.getX()); + q2 = coeff.multiply(axis.getY()); + q3 = coeff.multiply(axis.getZ()); + + } + + /** Build a rotation from a 3X3 matrix. + + *

    Rotation matrices are orthogonal matrices, i.e. unit matrices + * (which are matrices for which m.mT = 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.

    + + *

    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.

    + + * @param m rotation matrix + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + + * @exception NotARotationMatrixException if the matrix is not a 3X3 + * matrix, or if it cannot be transformed into an orthogonal matrix + * with the given threshold, or if the determinant of the resulting + * orthogonal matrix is negative + + */ + public FieldRotation(final T[][] m, final double threshold) + throws NotARotationMatrixException { + + // dimension check + if ((m.length != 3) || (m[0].length != 3) || + (m[1].length != 3) || (m[2].length != 3)) { + throw new NotARotationMatrixException( + LocalizedFormats.ROTATION_MATRIX_DIMENSIONS, + m.length, m[0].length); + } + + // compute a "close" orthogonal matrix + final T[][] ort = orthogonalizeMatrix(m, threshold); + + // check the sign of the determinant + final T d0 = ort[1][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[1][2])); + final T d1 = ort[0][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[0][2])); + final T d2 = ort[0][1].multiply(ort[1][2]).subtract(ort[1][1].multiply(ort[0][2])); + final T det = + ort[0][0].multiply(d0).subtract(ort[1][0].multiply(d1)).add(ort[2][0].multiply(d2)); + if (det.getReal() < 0.0) { + throw new NotARotationMatrixException( + LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT, + det); + } + + final T[] quat = mat2quat(ort); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build the rotation that transforms a pair of vectors into another pair. + + *

    Except for possible scale factors, if the instance were applied to + * the pair (u1, u2) it will produce the pair + * (v1, v2).

    + + *

    If the angular separation between u1 and u2 is + * not the same as the angular separation between v1 and + * v2, then a corrected v'2 will be used rather than + * v2, the corrected vector will be in the (±v1, + * +v2) half-plane.

    + + * @param u1 first vector of the origin pair + * @param u2 second vector of the origin pair + * @param v1 desired image of u1 by the rotation + * @param v2 desired image of u2 by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero, + * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear) + */ + public FieldRotation(FieldVector3D u1, FieldVector3D u2, FieldVector3D v1, FieldVector3D v2) + throws MathArithmeticException { + + // build orthonormalized base from u1, u2 + // this fails when vectors are null or collinear, which is forbidden to define a rotation + final FieldVector3D u3 = FieldVector3D.crossProduct(u1, u2).normalize(); + u2 = FieldVector3D.crossProduct(u3, u1).normalize(); + u1 = u1.normalize(); + + // build an orthonormalized base from v1, v2 + // this fails when vectors are null or collinear, which is forbidden to define a rotation + final FieldVector3D v3 = FieldVector3D.crossProduct(v1, v2).normalize(); + v2 = FieldVector3D.crossProduct(v3, v1).normalize(); + v1 = v1.normalize(); + + // buid a matrix transforming the first base into the second one + final T[][] array = MathArrays.buildArray(u1.getX().getField(), 3, 3); + array[0][0] = u1.getX().multiply(v1.getX()).add(u2.getX().multiply(v2.getX())).add(u3.getX().multiply(v3.getX())); + array[0][1] = u1.getY().multiply(v1.getX()).add(u2.getY().multiply(v2.getX())).add(u3.getY().multiply(v3.getX())); + array[0][2] = u1.getZ().multiply(v1.getX()).add(u2.getZ().multiply(v2.getX())).add(u3.getZ().multiply(v3.getX())); + array[1][0] = u1.getX().multiply(v1.getY()).add(u2.getX().multiply(v2.getY())).add(u3.getX().multiply(v3.getY())); + array[1][1] = u1.getY().multiply(v1.getY()).add(u2.getY().multiply(v2.getY())).add(u3.getY().multiply(v3.getY())); + array[1][2] = u1.getZ().multiply(v1.getY()).add(u2.getZ().multiply(v2.getY())).add(u3.getZ().multiply(v3.getY())); + array[2][0] = u1.getX().multiply(v1.getZ()).add(u2.getX().multiply(v2.getZ())).add(u3.getX().multiply(v3.getZ())); + array[2][1] = u1.getY().multiply(v1.getZ()).add(u2.getY().multiply(v2.getZ())).add(u3.getY().multiply(v3.getZ())); + array[2][2] = u1.getZ().multiply(v1.getZ()).add(u2.getZ().multiply(v2.getZ())).add(u3.getZ().multiply(v3.getZ())); + + T[] quat = mat2quat(array); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build one of the rotations that transform one vector into another one. + + *

    Except for a possible scale factor, if the instance were + * applied to the vector u it will produce the vector v. There is an + * infinite number of such rotations, this constructor choose the + * one with the smallest associated angle (i.e. the one whose axis + * is orthogonal to the (u, v) plane). If u and v are collinear, an + * arbitrary rotation axis is chosen.

    + + * @param u origin vector + * @param v desired image of u by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero + */ + public FieldRotation(final FieldVector3D u, final FieldVector3D v) throws MathArithmeticException { + + final T normProduct = u.getNorm().multiply(v.getNorm()); + if (normProduct.getReal() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR); + } + + final T dot = FieldVector3D.dotProduct(u, v); + + if (dot.getReal() < ((2.0e-15 - 1.0) * normProduct.getReal())) { + // special case u = -v: we select a PI angle rotation around + // an arbitrary vector orthogonal to u + final FieldVector3D w = u.orthogonal(); + q0 = normProduct.getField().getZero(); + q1 = w.getX().negate(); + q2 = w.getY().negate(); + q3 = w.getZ().negate(); + } else { + // general case: (u, v) defines a plane, we select + // the shortest possible rotation: axis orthogonal to this plane + q0 = dot.divide(normProduct).add(1.0).multiply(0.5).sqrt(); + final T coeff = q0.multiply(normProduct).multiply(2.0).reciprocal(); + final FieldVector3D q = FieldVector3D.crossProduct(v, u); + q1 = coeff.multiply(q.getX()); + q2 = coeff.multiply(q.getY()); + q3 = coeff.multiply(q.getZ()); + } + + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

    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.

    + *

    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).

    + + * @param order order of rotations to use + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + * @deprecated as of 3.6, replaced with {@link + * #FieldRotation(RotationOrder, RotationConvention, + * RealFieldElement, RealFieldElement, RealFieldElement)} + */ + @Deprecated + public FieldRotation(final RotationOrder order, final T alpha1, final T alpha2, final T alpha3) { + this(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3); + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

    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.

    + *

    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).

    + + * @param order order of rotations to compose, from left to right + * (i.e. we will use {@code r1.compose(r2.compose(r3, convention), convention)}) + * @param convention convention to use for the semantics of the angle + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + * @since 3.6 + */ + public FieldRotation(final RotationOrder order, final RotationConvention convention, + final T alpha1, final T alpha2, final T alpha3) { + final T one = alpha1.getField().getOne(); + final FieldRotation r1 = new FieldRotation(new FieldVector3D(one, order.getA1()), alpha1, convention); + final FieldRotation r2 = new FieldRotation(new FieldVector3D(one, order.getA2()), alpha2, convention); + final FieldRotation r3 = new FieldRotation(new FieldVector3D(one, order.getA3()), alpha3, convention); + final FieldRotation composed = r1.compose(r2.compose(r3, convention), convention); + q0 = composed.q0; + q1 = composed.q1; + q2 = composed.q2; + q3 = composed.q3; + } + + /** Convert an orthogonal rotation matrix to a quaternion. + * @param ort orthogonal rotation matrix + * @return quaternion corresponding to the matrix + */ + private T[] mat2quat(final T[][] ort) { + + final T[] quat = MathArrays.buildArray(ort[0][0].getField(), 4); + + // There are different ways to compute the quaternions elements + // from the matrix. They all involve computing one element from + // the diagonal of the matrix, and computing the three other ones + // using a formula involving a division by the first element, + // which unfortunately can be zero. Since the norm of the + // quaternion is 1, we know at least one element has an absolute + // value greater or equal to 0.5, so it is always possible to + // select the right formula and avoid division by zero and even + // numerical inaccuracy. Checking the elements in turn and using + // the first one greater than 0.45 is safe (this leads to a simple + // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) + T s = ort[0][0].add(ort[1][1]).add(ort[2][2]); + if (s.getReal() > -0.19) { + // compute q0 and deduce q1, q2 and q3 + quat[0] = s.add(1.0).sqrt().multiply(0.5); + T inv = quat[0].reciprocal().multiply(0.25); + quat[1] = inv.multiply(ort[1][2].subtract(ort[2][1])); + quat[2] = inv.multiply(ort[2][0].subtract(ort[0][2])); + quat[3] = inv.multiply(ort[0][1].subtract(ort[1][0])); + } else { + s = ort[0][0].subtract(ort[1][1]).subtract(ort[2][2]); + if (s.getReal() > -0.19) { + // compute q1 and deduce q0, q2 and q3 + quat[1] = s.add(1.0).sqrt().multiply(0.5); + T inv = quat[1].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[1][2].subtract(ort[2][1])); + quat[2] = inv.multiply(ort[0][1].add(ort[1][0])); + quat[3] = inv.multiply(ort[0][2].add(ort[2][0])); + } else { + s = ort[1][1].subtract(ort[0][0]).subtract(ort[2][2]); + if (s.getReal() > -0.19) { + // compute q2 and deduce q0, q1 and q3 + quat[2] = s.add(1.0).sqrt().multiply(0.5); + T inv = quat[2].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[2][0].subtract(ort[0][2])); + quat[1] = inv.multiply(ort[0][1].add(ort[1][0])); + quat[3] = inv.multiply(ort[2][1].add(ort[1][2])); + } else { + // compute q3 and deduce q0, q1 and q2 + s = ort[2][2].subtract(ort[0][0]).subtract(ort[1][1]); + quat[3] = s.add(1.0).sqrt().multiply(0.5); + T inv = quat[3].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[0][1].subtract(ort[1][0])); + quat[1] = inv.multiply(ort[0][2].add(ort[2][0])); + quat[2] = inv.multiply(ort[2][1].add(ort[1][2])); + } + } + } + + return quat; + + } + + /** Revert a rotation. + * Build a rotation which reverse the effect of another + * rotation. This means that if r(u) = v, then r.revert(v) = u. The + * instance is not changed. + * @return a new rotation whose effect is the reverse of the effect + * of the instance + */ + public FieldRotation revert() { + return new FieldRotation(q0.negate(), q1, q2, q3, false); + } + + /** Get the scalar coordinate of the quaternion. + * @return scalar coordinate of the quaternion + */ + public T getQ0() { + return q0; + } + + /** Get the first coordinate of the vectorial part of the quaternion. + * @return first coordinate of the vectorial part of the quaternion + */ + public T getQ1() { + return q1; + } + + /** Get the second coordinate of the vectorial part of the quaternion. + * @return second coordinate of the vectorial part of the quaternion + */ + public T getQ2() { + return q2; + } + + /** Get the third coordinate of the vectorial part of the quaternion. + * @return third coordinate of the vectorial part of the quaternion + */ + public T getQ3() { + return q3; + } + + /** Get the normalized axis of the rotation. + * @return normalized axis of the rotation + * @see #FieldRotation(FieldVector3D, RealFieldElement) + * @deprecated as of 3.6, replaced with {@link #getAxis(RotationConvention)} + */ + @Deprecated + public FieldVector3D getAxis() { + return getAxis(RotationConvention.VECTOR_OPERATOR); + } + + /** Get the normalized axis of the rotation. + *

    + * Note that as {@link #getAngle()} always returns an angle + * between 0 and π, changing the convention changes the + * direction of the axis, not the sign of the angle. + *

    + * @param convention convention to use for the semantics of the angle + * @return normalized axis of the rotation + * @see #FieldRotation(FieldVector3D, RealFieldElement) + * @since 3.6 + */ + public FieldVector3D getAxis(final RotationConvention convention) { + final T squaredSine = q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)); + if (squaredSine.getReal() == 0) { + final Field field = squaredSine.getField(); + return new FieldVector3D(convention == RotationConvention.VECTOR_OPERATOR ? field.getOne(): field.getOne().negate(), + field.getZero(), + field.getZero()); + } else { + final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1; + if (q0.getReal() < 0) { + T inverse = squaredSine.sqrt().reciprocal().multiply(sgn); + return new FieldVector3D(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse)); + } + final T inverse = squaredSine.sqrt().reciprocal().negate().multiply(sgn); + return new FieldVector3D(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse)); + } + } + + /** Get the angle of the rotation. + * @return angle of the rotation (between 0 and π) + * @see #FieldRotation(FieldVector3D, RealFieldElement) + */ + public T getAngle() { + if ((q0.getReal() < -0.1) || (q0.getReal() > 0.1)) { + return q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().asin().multiply(2); + } else if (q0.getReal() < 0) { + return q0.negate().acos().multiply(2); + } + return q0.acos().multiply(2); + } + + /** Get the Cardan or Euler angles corresponding to the instance. + + *

    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 + * a1, a2 and a3 is the same as + * the rotation defined by the angles π + a1, π + * - a2 and π + a3. This method implements + * the following arbitrary choices:

    + *
      + *
    • 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),
    • + *
    • for Euler angles, the chosen set is the one for which the + * second angle is between 0 and π (i.e its sine is positive).
    • + *
    + + *

    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 + * nothing 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!

    + + * @param order rotation order to use + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)} + */ + @Deprecated + public T[] getAngles(final RotationOrder order) + throws CardanEulerSingularityException { + return getAngles(order, RotationConvention.VECTOR_OPERATOR); + } + + /** Get the Cardan or Euler angles corresponding to the instance. + + *

    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 + * a1, a2 and a3 is the same as + * the rotation defined by the angles π + a1, π + * - a2 and π + a3. This method implements + * the following arbitrary choices:

    + *
      + *
    • 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),
    • + *
    • for Euler angles, the chosen set is the one for which the + * second angle is between 0 and π (i.e its sine is positive).
    • + *
    + + *

    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 + * nothing 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!

    + + * @param order rotation order to use + * @param convention convention to use for the semantics of the angle + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + * @since 3.6 + */ + public T[] getAngles(final RotationOrder order, RotationConvention convention) + throws CardanEulerSingularityException { + + if (convention == RotationConvention.VECTOR_OPERATOR) { + if (order == RotationOrder.XYZ) { + + // r (+K) coordinates are : + // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (+I) coordinates are : + // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) + final // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(vector(0, 0, 1)); + final FieldVector3D v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getY().negate().atan2(v1.getZ()), + v2.getZ().asin(), + v2.getY().negate().atan2(v2.getX())); + + } else if (order == RotationOrder.XZY) { + + // r (+J) coordinates are : + // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) + // (-r) (+I) coordinates are : + // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + final FieldVector3D v1 = applyTo(vector(0, 1, 0)); + final FieldVector3D v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getZ().atan2(v1.getY()), + v2.getY().asin().negate(), + v2.getZ().atan2(v2.getX())); + + } else if (order == RotationOrder.YXZ) { + + // r (+K) coordinates are : + // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) + // (-r) (+J) coordinates are : + // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + final FieldVector3D v1 = applyTo(vector(0, 0, 1)); + final FieldVector3D v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getX().atan2(v1.getZ()), + v2.getZ().asin().negate(), + v2.getX().atan2(v2.getY())); + + } else if (order == RotationOrder.YZX) { + + // r (+I) coordinates are : + // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) + // (-r) (+J) coordinates are : + // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + final FieldVector3D v1 = applyTo(vector(1, 0, 0)); + final FieldVector3D v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getZ().negate().atan2(v1.getX()), + v2.getX().asin(), + v2.getZ().negate().atan2(v2.getY())); + + } else if (order == RotationOrder.ZXY) { + + // r (+J) coordinates are : + // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) + // (-r) (+K) coordinates are : + // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + final FieldVector3D v1 = applyTo(vector(0, 1, 0)); + final FieldVector3D v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getX().negate().atan2(v1.getY()), + v2.getY().asin(), + v2.getX().negate().atan2(v2.getZ())); + + } else if (order == RotationOrder.ZYX) { + + // r (+I) coordinates are : + // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) + // (-r) (+K) coordinates are : + // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + final FieldVector3D v1 = applyTo(vector(1, 0, 0)); + final FieldVector3D v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v1.getY().atan2(v1.getX()), + v2.getX().asin().negate(), + v2.getY().atan2(v2.getZ())); + + } else if (order == RotationOrder.XYX) { + + // r (+I) coordinates are : + // cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta) + // (-r) (+I) coordinates are : + // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2) + // and we can choose to have theta in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(1, 0, 0)); + final FieldVector3D v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getY().atan2(v1.getZ().negate()), + v2.getX().acos(), + v2.getY().atan2(v2.getZ())); + + } else if (order == RotationOrder.XZX) { + + // r (+I) coordinates are : + // cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi) + // (-r) (+I) coordinates are : + // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2) + // and we can choose to have psi in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(1, 0, 0)); + final FieldVector3D v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getZ().atan2(v1.getY()), + v2.getX().acos(), + v2.getZ().atan2(v2.getY().negate())); + + } else if (order == RotationOrder.YXY) { + + // r (+J) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // (-r) (+J) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // and we can choose to have phi in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(0, 1, 0)); + final FieldVector3D v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getX().atan2(v1.getZ()), + v2.getY().acos(), + v2.getX().atan2(v2.getZ().negate())); + + } else if (order == RotationOrder.YZY) { + + // r (+J) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // (-r) (+J) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // and we can choose to have psi in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(0, 1, 0)); + final FieldVector3D v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getZ().atan2(v1.getX().negate()), + v2.getY().acos(), + v2.getZ().atan2(v2.getX())); + + } else if (order == RotationOrder.ZXZ) { + + // r (+K) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // (-r) (+K) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(0, 0, 1)); + final FieldVector3D v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getX().atan2(v1.getY().negate()), + v2.getZ().acos(), + v2.getX().atan2(v2.getY())); + + } else { // last possibility is ZYZ + + // r (+K) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // (-r) (+K) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + final FieldVector3D v1 = applyTo(vector(0, 0, 1)); + final FieldVector3D v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v1.getY().atan2(v1.getX()), + v2.getZ().acos(), + v2.getY().atan2(v2.getX().negate())); + + } + } else { + if (order == RotationOrder.XYZ) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), -cos (theta) sin (psi), sin (theta) + // (-r) (Vector3D.plusK) coordinates are : + // sin (theta), -sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_I); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getY().negate().atan2(v2.getZ()), + v2.getX().asin(), + v1.getY().negate().atan2(v1.getX())); + + } else if (order == RotationOrder.XZY) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), -sin (psi), cos (psi) sin (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // -sin (psi), cos (phi) cos (psi), sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_I); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getZ().atan2(v2.getY()), + v2.getX().asin().negate(), + v1.getZ().atan2(v1.getX())); + + } else if (order == RotationOrder.YXZ) { + + // r (Vector3D.plusJ) coordinates are : + // cos (phi) sin (psi), cos (phi) cos (psi), -sin (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (theta) cos (phi), -sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_J); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getX().atan2(v2.getZ()), + v2.getY().asin().negate(), + v1.getX().atan2(v1.getY())); + + } else if (order == RotationOrder.YZX) { + + // r (Vector3D.plusJ) coordinates are : + // sin (psi), cos (psi) cos (phi), -cos (psi) sin (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), sin (psi), -sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_J); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getZ().negate().atan2(v2.getX()), + v2.getY().asin(), + v1.getZ().negate().atan2(v1.getY())); + + } else if (order == RotationOrder.ZXY) { + + // r (Vector3D.plusK) coordinates are : + // -cos (phi) sin (theta), sin (phi), cos (phi) cos (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // -sin (psi) cos (phi), cos (psi) cos (phi), sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_K); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getX().negate().atan2(v2.getY()), + v2.getZ().asin(), + v1.getX().negate().atan2(v1.getZ())); + + } else if (order == RotationOrder.ZYX) { + + // r (Vector3D.plusK) coordinates are : + // -sin (theta), cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), sin (psi) cos (theta), -sin (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + FieldVector3D v1 = applyTo(Vector3D.PLUS_K); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return buildArray(v2.getY().atan2(v2.getX()), + v2.getZ().asin().negate(), + v1.getY().atan2(v1.getZ())); + + } else if (order == RotationOrder.XYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta), sin (phi2) sin (theta), cos (phi2) sin (theta) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta), sin (theta) sin (phi1), -sin (theta) cos (phi1) + // and we can choose to have theta in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_I); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getY().atan2(v2.getZ().negate()), + v2.getX().acos(), + v1.getY().atan2(v1.getZ())); + + } else if (order == RotationOrder.XZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi), -cos (phi2) sin (psi), sin (phi2) sin (psi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi), sin (psi) cos (phi1), sin (psi) sin (phi1) + // and we can choose to have psi in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_I); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getZ().atan2(v2.getY()), + v2.getX().acos(), + v1.getZ().atan2(v1.getY().negate())); + + } else if (order == RotationOrder.YXY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // and we can choose to have phi in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_J); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getX().atan2(v2.getZ()), + v2.getY().acos(), + v1.getX().atan2(v1.getZ().negate())); + + } else if (order == RotationOrder.YZY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // (-r) (Vector3D.plusJ) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // and we can choose to have psi in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_J); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getZ().atan2(v2.getX().negate()), + v2.getY().acos(), + v1.getZ().atan2(v1.getX())); + + } else if (order == RotationOrder.ZXZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_K); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getX().atan2(v2.getY().negate()), + v2.getZ().acos(), + v1.getX().atan2(v1.getY())); + + } else { // last possibility is ZYZ + + // r (Vector3D.plusK) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // (-r) (Vector3D.plusK) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + FieldVector3D v1 = applyTo(Vector3D.PLUS_K); + FieldVector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return buildArray(v2.getY().atan2(v2.getX()), + v2.getZ().acos(), + v1.getY().atan2(v1.getX().negate())); + + } + } + + } + + /** Create a dimension 3 array. + * @param a0 first array element + * @param a1 second array element + * @param a2 third array element + * @return new array + */ + private T[] buildArray(final T a0, final T a1, final T a2) { + final T[] array = MathArrays.buildArray(a0.getField(), 3); + array[0] = a0; + array[1] = a1; + array[2] = a2; + return array; + } + + /** Create a constant vector. + * @param x abscissa + * @param y ordinate + * @param z height + * @return a constant vector + */ + private FieldVector3D vector(final double x, final double y, final double z) { + final T zero = q0.getField().getZero(); + return new FieldVector3D(zero.add(x), zero.add(y), zero.add(z)); + } + + /** Get the 3X3 matrix corresponding to the instance + * @return the matrix corresponding to the instance + */ + public T[][] getMatrix() { + + // products + final T q0q0 = q0.multiply(q0); + final T q0q1 = q0.multiply(q1); + final T q0q2 = q0.multiply(q2); + final T q0q3 = q0.multiply(q3); + final T q1q1 = q1.multiply(q1); + final T q1q2 = q1.multiply(q2); + final T q1q3 = q1.multiply(q3); + final T q2q2 = q2.multiply(q2); + final T q2q3 = q2.multiply(q3); + final T q3q3 = q3.multiply(q3); + + // create the matrix + final T[][] m = MathArrays.buildArray(q0.getField(), 3, 3); + + m [0][0] = q0q0.add(q1q1).multiply(2).subtract(1); + m [1][0] = q1q2.subtract(q0q3).multiply(2); + m [2][0] = q1q3.add(q0q2).multiply(2); + + m [0][1] = q1q2.add(q0q3).multiply(2); + m [1][1] = q0q0.add(q2q2).multiply(2).subtract(1); + m [2][1] = q2q3.subtract(q0q1).multiply(2); + + m [0][2] = q1q3.subtract(q0q2).multiply(2); + m [1][2] = q2q3.add(q0q1).multiply(2); + m [2][2] = q0q0.add(q3q3).multiply(2).subtract(1); + + return m; + + } + + /** Convert to a constant vector without derivatives. + * @return a constant vector + */ + public Rotation toRotation() { + return new Rotation(q0.getReal(), q1.getReal(), q2.getReal(), q3.getReal(), false); + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public FieldVector3D applyTo(final FieldVector3D u) { + + final T x = u.getX(); + final T y = u.getY(); + final T z = u.getZ(); + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + return new FieldVector3D(q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public FieldVector3D applyTo(final Vector3D u) { + + final double x = u.getX(); + final double y = u.getY(); + final double z = u.getZ(); + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + return new FieldVector3D(q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyTo(final T[] in, final T[] out) { + + final T x = in[0]; + final T y = in[1]; + final T z = in[2]; + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + out[0] = q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to + */ + public void applyTo(final double[] in, final T[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + out[0] = q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply a rotation to a vector. + * @param r rotation to apply + * @param u vector to apply the rotation to + * @param the type of the field elements + * @return a new vector which is the image of u by the rotation + */ + public static > FieldVector3D applyTo(final Rotation r, final FieldVector3D u) { + + final T x = u.getX(); + final T y = u.getY(); + final T z = u.getZ(); + + final T s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3())); + + return new FieldVector3D(x.multiply(r.getQ0()).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(r.getQ0()).add(s.multiply(r.getQ1())).multiply(2).subtract(x), + y.multiply(r.getQ0()).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(r.getQ0()).add(s.multiply(r.getQ2())).multiply(2).subtract(y), + z.multiply(r.getQ0()).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(r.getQ0()).add(s.multiply(r.getQ3())).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public FieldVector3D applyInverseTo(final FieldVector3D u) { + + final T x = u.getX(); + final T y = u.getY(); + final T z = u.getZ(); + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final T m0 = q0.negate(); + + return new FieldVector3D(m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public FieldVector3D applyInverseTo(final Vector3D u) { + + final double x = u.getX(); + final double y = u.getY(); + final double z = u.getZ(); + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final T m0 = q0.negate(); + + return new FieldVector3D(m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyInverseTo(final T[] in, final T[] out) { + + final T x = in[0]; + final T y = in[1]; + final T z = in[2]; + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final T m0 = q0.negate(); + + out[0] = m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the inverse of the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to + */ + public void applyInverseTo(final double[] in, final T[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final T m0 = q0.negate(); + + out[0] = m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the inverse of a rotation to a vector. + * @param r rotation to apply + * @param u vector to apply the inverse of the rotation to + * @param the type of the field elements + * @return a new vector which such that u is its image by the rotation + */ + public static > FieldVector3D applyInverseTo(final Rotation r, final FieldVector3D u) { + + final T x = u.getX(); + final T y = u.getY(); + final T z = u.getZ(); + + final T s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3())); + final double m0 = -r.getQ0(); + + return new FieldVector3D(x.multiply(m0).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(m0).add(s.multiply(r.getQ1())).multiply(2).subtract(x), + y.multiply(m0).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(m0).add(s.multiply(r.getQ2())).multiply(2).subtract(y), + z.multiply(m0).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(m0).add(s.multiply(r.getQ3())).multiply(2).subtract(z)); + + } + + /** Apply the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #compose(FieldRotation, RotationConvention) + * compose(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public FieldRotation applyTo(final FieldRotation r) { + return compose(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let {@code u} be any + * vector and {@code v} its image by {@code r1} (i.e. + * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by + * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then + * {@code w = comp.applyTo(u)}, where + * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the instance + */ + public FieldRotation compose(final FieldRotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInternal(r) : r.composeInternal(this); + } + + /** Compose the instance with another rotation using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + * using vector operator convention + */ + private FieldRotation composeInternal(final FieldRotation r) { + return new FieldRotation(r.q0.multiply(q0).subtract(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))), + r.q1.multiply(q0).add(r.q0.multiply(q1)).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))), + r.q2.multiply(q0).add(r.q0.multiply(q2)).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))), + r.q3.multiply(q0).add(r.q0.multiply(q3)).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))), + false); + } + + /** Apply the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #compose(Rotation, RotationConvention) + * compose(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public FieldRotation applyTo(final Rotation r) { + return compose(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let {@code u} be any + * vector and {@code v} its image by {@code r1} (i.e. + * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by + * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then + * {@code w = comp.applyTo(u)}, where + * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the instance + */ + public FieldRotation compose(final Rotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInternal(r) : applyTo(r, this); + } + + /** Compose the instance with another rotation using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + * using vector operator convention + */ + private FieldRotation composeInternal(final Rotation r) { + return new FieldRotation(q0.multiply(r.getQ0()).subtract(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))), + q0.multiply(r.getQ1()).add(q1.multiply(r.getQ0())).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))), + q0.multiply(r.getQ2()).add(q2.multiply(r.getQ0())).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))), + q0.multiply(r.getQ3()).add(q3.multiply(r.getQ0())).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))), + false); + } + + /** Apply a rotation to another rotation. + * Applying a rotation to another rotation is computing the composition + * in an order compliant with the following rule : let u be any + * vector and v its image by rInner (i.e. rInner.applyTo(u) = v), let w be the image + * of v by rOuter (i.e. rOuter.applyTo(v) = w), then w = comp.applyTo(u), + * where comp = applyTo(rOuter, rInner). + * @param r1 rotation to apply + * @param rInner rotation to apply the rotation to + * @param the type of the field elements + * @return a new rotation which is the composition of r by the instance + */ + public static > FieldRotation applyTo(final Rotation r1, final FieldRotation rInner) { + return new FieldRotation(rInner.q0.multiply(r1.getQ0()).subtract(rInner.q1.multiply(r1.getQ1()).add(rInner.q2.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ3()))), + rInner.q1.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ1())).add(rInner.q2.multiply(r1.getQ3()).subtract(rInner.q3.multiply(r1.getQ2()))), + rInner.q2.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ1()).subtract(rInner.q1.multiply(r1.getQ3()))), + rInner.q3.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ3())).add(rInner.q1.multiply(r1.getQ2()).subtract(rInner.q2.multiply(r1.getQ1()))), + false); + } + + /** Apply the inverse of the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #composeInverse(FieldRotation, RotationConvention) + * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public FieldRotation applyInverseTo(final FieldRotation r) { + return composeInverse(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the inverse of the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let {@code u} be any vector and {@code v} its image by {@code r1} + * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image + * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}). + * Then {@code w = comp.applyTo(u)}, where + * {@code comp = r2.composeInverse(r1)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed, which means it is the + * innermost rotation that will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public FieldRotation composeInverse(final FieldRotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInverseInternal(r) : r.composeInternal(revert()); + } + + /** Compose the inverse of the instance with another rotation + * using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance using vector operator convention + */ + private FieldRotation composeInverseInternal(FieldRotation r) { + return new FieldRotation(r.q0.multiply(q0).add(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))).negate(), + r.q0.multiply(q1).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))).subtract(r.q1.multiply(q0)), + r.q0.multiply(q2).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))).subtract(r.q2.multiply(q0)), + r.q0.multiply(q3).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))).subtract(r.q3.multiply(q0)), + false); + } + + /** Apply the inverse of the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #composeInverse(Rotation, RotationConvention) + * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public FieldRotation applyInverseTo(final Rotation r) { + return composeInverse(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the inverse of the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let {@code u} be any vector and {@code v} its image by {@code r1} + * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image + * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}). + * Then {@code w = comp.applyTo(u)}, where + * {@code comp = r2.composeInverse(r1)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed, which means it is the + * innermost rotation that will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public FieldRotation composeInverse(final Rotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInverseInternal(r) : applyTo(r, revert()); + } + + /** Compose the inverse of the instance with another rotation + * using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance using vector operator convention + */ + private FieldRotation composeInverseInternal(Rotation r) { + return new FieldRotation(q0.multiply(r.getQ0()).add(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))).negate(), + q1.multiply(r.getQ0()).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))).subtract(q0.multiply(r.getQ1())), + q2.multiply(r.getQ0()).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))).subtract(q0.multiply(r.getQ2())), + q3.multiply(r.getQ0()).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))).subtract(q0.multiply(r.getQ3())), + false); + } + + /** Apply the inverse of a rotation to another rotation. + * Applying the inverse of a rotation to another rotation is computing + * the composition in an order compliant with the following rule : + * let u be any vector and v its image by rInner (i.e. rInner.applyTo(u) = v), + * let w be the inverse image of v by rOuter + * (i.e. rOuter.applyInverseTo(v) = w), then w = comp.applyTo(u), where + * comp = applyInverseTo(rOuter, rInner). + * @param rOuter rotation to apply the rotation to + * @param rInner rotation to apply the rotation to + * @param the type of the field elements + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public static > FieldRotation applyInverseTo(final Rotation rOuter, final FieldRotation rInner) { + return new FieldRotation(rInner.q0.multiply(rOuter.getQ0()).add(rInner.q1.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ2())).add(rInner.q3.multiply(rOuter.getQ3()))).negate(), + rInner.q0.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ3()).subtract(rInner.q3.multiply(rOuter.getQ2()))).subtract(rInner.q1.multiply(rOuter.getQ0())), + rInner.q0.multiply(rOuter.getQ2()).add(rInner.q3.multiply(rOuter.getQ1()).subtract(rInner.q1.multiply(rOuter.getQ3()))).subtract(rInner.q2.multiply(rOuter.getQ0())), + rInner.q0.multiply(rOuter.getQ3()).add(rInner.q1.multiply(rOuter.getQ2()).subtract(rInner.q2.multiply(rOuter.getQ1()))).subtract(rInner.q3.multiply(rOuter.getQ0())), + false); + } + + /** Perfect orthogonality on a 3X3 matrix. + * @param m initial matrix (not exactly orthogonal) + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + * @return an orthogonal matrix close to m + * @exception NotARotationMatrixException if the matrix cannot be + * orthogonalized with the given threshold after 10 iterations + */ + private T[][] orthogonalizeMatrix(final T[][] m, final double threshold) + throws NotARotationMatrixException { + + T x00 = m[0][0]; + T x01 = m[0][1]; + T x02 = m[0][2]; + T x10 = m[1][0]; + T x11 = m[1][1]; + T x12 = m[1][2]; + T x20 = m[2][0]; + T x21 = m[2][1]; + T x22 = m[2][2]; + double fn = 0; + double fn1; + + final T[][] o = MathArrays.buildArray(m[0][0].getField(), 3, 3); + + // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M) + int i = 0; + while (++i < 11) { + + // Mt.Xn + final T mx00 = m[0][0].multiply(x00).add(m[1][0].multiply(x10)).add(m[2][0].multiply(x20)); + final T mx10 = m[0][1].multiply(x00).add(m[1][1].multiply(x10)).add(m[2][1].multiply(x20)); + final T mx20 = m[0][2].multiply(x00).add(m[1][2].multiply(x10)).add(m[2][2].multiply(x20)); + final T mx01 = m[0][0].multiply(x01).add(m[1][0].multiply(x11)).add(m[2][0].multiply(x21)); + final T mx11 = m[0][1].multiply(x01).add(m[1][1].multiply(x11)).add(m[2][1].multiply(x21)); + final T mx21 = m[0][2].multiply(x01).add(m[1][2].multiply(x11)).add(m[2][2].multiply(x21)); + final T mx02 = m[0][0].multiply(x02).add(m[1][0].multiply(x12)).add(m[2][0].multiply(x22)); + final T mx12 = m[0][1].multiply(x02).add(m[1][1].multiply(x12)).add(m[2][1].multiply(x22)); + final T mx22 = m[0][2].multiply(x02).add(m[1][2].multiply(x12)).add(m[2][2].multiply(x22)); + + // Xn+1 + o[0][0] = x00.subtract(x00.multiply(mx00).add(x01.multiply(mx10)).add(x02.multiply(mx20)).subtract(m[0][0]).multiply(0.5)); + o[0][1] = x01.subtract(x00.multiply(mx01).add(x01.multiply(mx11)).add(x02.multiply(mx21)).subtract(m[0][1]).multiply(0.5)); + o[0][2] = x02.subtract(x00.multiply(mx02).add(x01.multiply(mx12)).add(x02.multiply(mx22)).subtract(m[0][2]).multiply(0.5)); + o[1][0] = x10.subtract(x10.multiply(mx00).add(x11.multiply(mx10)).add(x12.multiply(mx20)).subtract(m[1][0]).multiply(0.5)); + o[1][1] = x11.subtract(x10.multiply(mx01).add(x11.multiply(mx11)).add(x12.multiply(mx21)).subtract(m[1][1]).multiply(0.5)); + o[1][2] = x12.subtract(x10.multiply(mx02).add(x11.multiply(mx12)).add(x12.multiply(mx22)).subtract(m[1][2]).multiply(0.5)); + o[2][0] = x20.subtract(x20.multiply(mx00).add(x21.multiply(mx10)).add(x22.multiply(mx20)).subtract(m[2][0]).multiply(0.5)); + o[2][1] = x21.subtract(x20.multiply(mx01).add(x21.multiply(mx11)).add(x22.multiply(mx21)).subtract(m[2][1]).multiply(0.5)); + o[2][2] = x22.subtract(x20.multiply(mx02).add(x21.multiply(mx12)).add(x22.multiply(mx22)).subtract(m[2][2]).multiply(0.5)); + + // correction on each elements + final double corr00 = o[0][0].getReal() - m[0][0].getReal(); + final double corr01 = o[0][1].getReal() - m[0][1].getReal(); + final double corr02 = o[0][2].getReal() - m[0][2].getReal(); + final double corr10 = o[1][0].getReal() - m[1][0].getReal(); + final double corr11 = o[1][1].getReal() - m[1][1].getReal(); + final double corr12 = o[1][2].getReal() - m[1][2].getReal(); + final double corr20 = o[2][0].getReal() - m[2][0].getReal(); + final double corr21 = o[2][1].getReal() - m[2][1].getReal(); + final double corr22 = o[2][2].getReal() - m[2][2].getReal(); + + // Frobenius norm of the correction + fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 + + corr10 * corr10 + corr11 * corr11 + corr12 * corr12 + + corr20 * corr20 + corr21 * corr21 + corr22 * corr22; + + // convergence test + if (FastMath.abs(fn1 - fn) <= threshold) { + return o; + } + + // prepare next iteration + x00 = o[0][0]; + x01 = o[0][1]; + x02 = o[0][2]; + x10 = o[1][0]; + x11 = o[1][1]; + x12 = o[1][2]; + x20 = o[2][0]; + x21 = o[2][1]; + x22 = o[2][2]; + fn = fn1; + + } + + // the algorithm did not converge after 10 iterations + throw new NotARotationMatrixException(LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX, + i - 1); + + } + + /** Compute the distance between two rotations. + *

    The distance 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:

    + *
    +     *        r1(r) = r2
    +     * 
    + *

    This distance is an angle between 0 and π. Its value is the smallest + * possible upper bound of the angle in radians between r1(v) + * and r2(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.

    + *

    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).

    + * @param r1 first rotation + * @param r2 second rotation + * @param the type of the field elements + * @return distance between r1 and r2 + */ + public static > T distance(final FieldRotation r1, final FieldRotation r2) { + return r1.composeInverseInternal(r2).getAngle(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java new file mode 100644 index 000000000..beaae0d49 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java @@ -0,0 +1,1185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; +import java.text.NumberFormat; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * This class is a re-implementation of {@link Vector3D} using {@link RealFieldElement}. + *

    Instance of this class are guaranteed to be immutable.

    + * @param the type of the field elements + * @since 3.2 + */ +public class FieldVector3D> implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20130224L; + + /** Abscissa. */ + private final T x; + + /** Ordinate. */ + private final T y; + + /** Height. */ + private final T z; + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @param z height + * @see #getX() + * @see #getY() + * @see #getZ() + */ + public FieldVector3D(final T x, final T y, final T z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Simple constructor. + * Build a vector from its coordinates + * @param v coordinates array + * @exception DimensionMismatchException if array does not have 3 elements + * @see #toArray() + */ + public FieldVector3D(final T[] v) throws DimensionMismatchException { + if (v.length != 3) { + throw new DimensionMismatchException(v.length, 3); + } + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + } + + /** Simple constructor. + * Build a vector from its azimuthal coordinates + * @param alpha azimuth (α) 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 FieldVector3D(final T alpha, final T delta) { + T cosDelta = delta.cos(); + this.x = alpha.cos().multiply(cosDelta); + this.y = alpha.sin().multiply(cosDelta); + this.z = delta.sin(); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public FieldVector3D(final T a, final FieldVector3Du) { + this.x = a.multiply(u.x); + this.y = a.multiply(u.y); + this.z = a.multiply(u.z); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public FieldVector3D(final T a, final Vector3D u) { + this.x = a.multiply(u.getX()); + this.y = a.multiply(u.getY()); + this.z = a.multiply(u.getZ()); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public FieldVector3D(final double a, final FieldVector3D u) { + this.x = u.x.multiply(a); + this.y = u.y.multiply(a); + this.z = u.z.multiply(a); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public FieldVector3D(final T a1, final FieldVector3D u1, + final T a2, final FieldVector3D u2) { + final T prototype = a1; + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ()); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public FieldVector3D(final T a1, final Vector3D u1, + final T a2, final Vector3D u2) { + final T prototype = a1; + this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2); + this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2); + this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public FieldVector3D(final double a1, final FieldVector3D u1, + final double a2, final FieldVector3D u2) { + final T prototype = u1.getX(); + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ()); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public FieldVector3D(final T a1, final FieldVector3D u1, + final T a2, final FieldVector3D u2, + final T a3, final FieldVector3D u3) { + final T prototype = a1; + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ()); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public FieldVector3D(final T a1, final Vector3D u1, + final T a2, final Vector3D u2, + final T a3, final Vector3D u3) { + final T prototype = a1; + this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2, u3.getX(), a3); + this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2, u3.getY(), a3); + this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2, u3.getZ(), a3); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public FieldVector3D(final double a1, final FieldVector3D u1, + final double a2, final FieldVector3D u2, + final double a3, final FieldVector3D u3) { + final T prototype = u1.getX(); + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ()); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public FieldVector3D(final T a1, final FieldVector3D u1, + final T a2, final FieldVector3D u2, + final T a3, final FieldVector3D u3, + final T a4, final FieldVector3D u4) { + final T prototype = a1; + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX(), a4, u4.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY(), a4, u4.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ(), a4, u4.getZ()); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public FieldVector3D(final T a1, final Vector3D u1, + final T a2, final Vector3D u2, + final T a3, final Vector3D u3, + final T a4, final Vector3D u4) { + final T prototype = a1; + this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2, u3.getX(), a3, u4.getX(), a4); + this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2, u3.getY(), a3, u4.getY(), a4); + this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2, u3.getZ(), a3, u4.getZ(), a4); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public FieldVector3D(final double a1, final FieldVector3D u1, + final double a2, final FieldVector3D u2, + final double a3, final FieldVector3D u3, + final double a4, final FieldVector3D u4) { + final T prototype = u1.getX(); + this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX(), a4, u4.getX()); + this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY(), a4, u4.getY()); + this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ(), a4, u4.getZ()); + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement) + */ + public T getX() { + return x; + } + + /** Get the ordinate of the vector. + * @return ordinate of the vector + * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement) + */ + public T getY() { + return y; + } + + /** Get the height of the vector. + * @return height of the vector + * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement) + */ + public T getZ() { + return z; + } + + /** Get the vector coordinates as a dimension 3 array. + * @return vector coordinates + * @see #FieldVector3D(RealFieldElement[]) + */ + public T[] toArray() { + final T[] array = MathArrays.buildArray(x.getField(), 3); + array[0] = x; + array[1] = y; + array[2] = z; + return array; + } + + /** Convert to a constant vector without derivatives. + * @return a constant vector + */ + public Vector3D toVector3D() { + return new Vector3D(x.getReal(), y.getReal(), z.getReal()); + } + + /** Get the L1 norm for the vector. + * @return L1 norm for the vector + */ + public T getNorm1() { + return x.abs().add(y.abs()).add(z.abs()); + } + + /** Get the L2 norm for the vector. + * @return Euclidean norm for the vector + */ + public T getNorm() { + // there are no cancellation problems here, so we use the straightforward formula + return x.multiply(x).add(y.multiply(y)).add(z.multiply(z)).sqrt(); + } + + /** Get the square of the norm for the vector. + * @return square of the Euclidean norm for the vector + */ + public T getNormSq() { + // there are no cancellation problems here, so we use the straightforward formula + return x.multiply(x).add(y.multiply(y)).add(z.multiply(z)); + } + + /** Get the L norm for the vector. + * @return L norm for the vector + */ + public T getNormInf() { + final T xAbs = x.abs(); + final T yAbs = y.abs(); + final T zAbs = z.abs(); + if (xAbs.getReal() <= yAbs.getReal()) { + if (yAbs.getReal() <= zAbs.getReal()) { + return zAbs; + } else { + return yAbs; + } + } else { + if (xAbs.getReal() <= zAbs.getReal()) { + return zAbs; + } else { + return xAbs; + } + } + } + + /** Get the azimuth of the vector. + * @return azimuth (α) of the vector, between -π and +π + * @see #FieldVector3D(RealFieldElement, RealFieldElement) + */ + public T getAlpha() { + return y.atan2(x); + } + + /** Get the elevation of the vector. + * @return elevation (δ) of the vector, between -π/2 and +π/2 + * @see #FieldVector3D(RealFieldElement, RealFieldElement) + */ + public T getDelta() { + return z.divide(getNorm()).asin(); + } + + /** Add a vector to the instance. + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final FieldVector3D v) { + return new FieldVector3D(x.add(v.x), y.add(v.y), z.add(v.z)); + } + + /** Add a vector to the instance. + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final Vector3D v) { + return new FieldVector3D(x.add(v.getX()), y.add(v.getY()), z.add(v.getZ())); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final T factor, final FieldVector3D v) { + return new FieldVector3D(x.getField().getOne(), this, factor, v); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final T factor, final Vector3D v) { + return new FieldVector3D(x.add(factor.multiply(v.getX())), + y.add(factor.multiply(v.getY())), + z.add(factor.multiply(v.getZ()))); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final double factor, final FieldVector3D v) { + return new FieldVector3D(1.0, this, factor, v); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public FieldVector3D add(final double factor, final Vector3D v) { + return new FieldVector3D(x.add(factor * v.getX()), + y.add(factor * v.getY()), + z.add(factor * v.getZ())); + } + + /** Subtract a vector from the instance. + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final FieldVector3D v) { + return new FieldVector3D(x.subtract(v.x), y.subtract(v.y), z.subtract(v.z)); + } + + /** Subtract a vector from the instance. + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final Vector3D v) { + return new FieldVector3D(x.subtract(v.getX()), y.subtract(v.getY()), z.subtract(v.getZ())); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final T factor, final FieldVector3D v) { + return new FieldVector3D(x.getField().getOne(), this, factor.negate(), v); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final T factor, final Vector3D v) { + return new FieldVector3D(x.subtract(factor.multiply(v.getX())), + y.subtract(factor.multiply(v.getY())), + z.subtract(factor.multiply(v.getZ()))); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final double factor, final FieldVector3D v) { + return new FieldVector3D(1.0, this, -factor, v); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public FieldVector3D subtract(final double factor, final Vector3D v) { + return new FieldVector3D(x.subtract(factor * v.getX()), + y.subtract(factor * v.getY()), + z.subtract(factor * v.getZ())); + } + + /** Get a normalized vector aligned with the instance. + * @return a new normalized vector + * @exception MathArithmeticException if the norm is zero + */ + public FieldVector3D normalize() throws MathArithmeticException { + final T s = getNorm(); + if (s.getReal() == 0) { + throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); + } + return scalarMultiply(s.reciprocal()); + } + + /** Get a vector orthogonal to the instance. + *

    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 : + *

    
    +     *   Vector3D k = u.normalize();
    +     *   Vector3D i = k.orthogonal();
    +     *   Vector3D j = Vector3D.crossProduct(k, i);
    +     * 

    + * @return a new normalized vector orthogonal to the instance + * @exception MathArithmeticException if the norm of the instance is null + */ + public FieldVector3D orthogonal() throws MathArithmeticException { + + final double threshold = 0.6 * getNorm().getReal(); + if (threshold == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + if (FastMath.abs(x.getReal()) <= threshold) { + final T inverse = y.multiply(y).add(z.multiply(z)).sqrt().reciprocal(); + return new FieldVector3D(inverse.getField().getZero(), inverse.multiply(z), inverse.multiply(y).negate()); + } else if (FastMath.abs(y.getReal()) <= threshold) { + final T inverse = x.multiply(x).add(z.multiply(z)).sqrt().reciprocal(); + return new FieldVector3D(inverse.multiply(z).negate(), inverse.getField().getZero(), inverse.multiply(x)); + } else { + final T inverse = x.multiply(x).add(y.multiply(y)).sqrt().reciprocal(); + return new FieldVector3D(inverse.multiply(y), inverse.multiply(x).negate(), inverse.getField().getZero()); + } + + } + + /** Compute the angular separation between two vectors. + *

    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.

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static > T angle(final FieldVector3D v1, final FieldVector3D v2) + throws MathArithmeticException { + + final T normProduct = v1.getNorm().multiply(v2.getNorm()); + if (normProduct.getReal() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + final T dot = dotProduct(v1, v2); + final double threshold = normProduct.getReal() * 0.9999; + if ((dot.getReal() < -threshold) || (dot.getReal() > threshold)) { + // the vectors are almost aligned, compute using the sine + FieldVector3D v3 = crossProduct(v1, v2); + if (dot.getReal() >= 0) { + return v3.getNorm().divide(normProduct).asin(); + } + return v3.getNorm().divide(normProduct).asin().subtract(FastMath.PI).negate(); + } + + // the vectors are sufficiently separated to use the cosine + return dot.divide(normProduct).acos(); + + } + + /** Compute the angular separation between two vectors. + *

    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.

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static > T angle(final FieldVector3D v1, final Vector3D v2) + throws MathArithmeticException { + + final T normProduct = v1.getNorm().multiply(v2.getNorm()); + if (normProduct.getReal() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + final T dot = dotProduct(v1, v2); + final double threshold = normProduct.getReal() * 0.9999; + if ((dot.getReal() < -threshold) || (dot.getReal() > threshold)) { + // the vectors are almost aligned, compute using the sine + FieldVector3D v3 = crossProduct(v1, v2); + if (dot.getReal() >= 0) { + return v3.getNorm().divide(normProduct).asin(); + } + return v3.getNorm().divide(normProduct).asin().subtract(FastMath.PI).negate(); + } + + // the vectors are sufficiently separated to use the cosine + return dot.divide(normProduct).acos(); + + } + + /** Compute the angular separation between two vectors. + *

    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.

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static > T angle(final Vector3D v1, final FieldVector3D v2) + throws MathArithmeticException { + return angle(v2, v1); + } + + /** Get the opposite of the instance. + * @return a new vector which is opposite to the instance + */ + public FieldVector3D negate() { + return new FieldVector3D(x.negate(), y.negate(), z.negate()); + } + + /** Multiply the instance by a scalar. + * @param a scalar + * @return a new vector + */ + public FieldVector3D scalarMultiply(final T a) { + return new FieldVector3D(x.multiply(a), y.multiply(a), z.multiply(a)); + } + + /** Multiply the instance by a scalar. + * @param a scalar + * @return a new vector + */ + public FieldVector3D scalarMultiply(final double a) { + return new FieldVector3D(x.multiply(a), y.multiply(a), z.multiply(a)); + } + + /** + * Returns true if any coordinate of this vector is NaN; false otherwise + * @return true if any coordinate of this vector is NaN; false otherwise + */ + public boolean isNaN() { + return Double.isNaN(x.getReal()) || Double.isNaN(y.getReal()) || Double.isNaN(z.getReal()); + } + + /** + * Returns true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + * @return true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + */ + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(x.getReal()) || Double.isInfinite(y.getReal()) || Double.isInfinite(z.getReal())); + } + + /** + * Test for the equality of two 3D vectors. + *

    + * If all coordinates of two 3D vectors are exactly the same, and none of their + * {@link RealFieldElement#getReal() real part} are NaN, the + * two 3D vectors are considered to be equal. + *

    + *

    + * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) real part of the + * coordinates of the 3D vector are NaN, the 3D vector is NaN. + *

    + * + * @param other Object to test for equality to this + * @return true if two 3D vector objects are equal, false if + * object is null, not an instance of Vector3D, or + * not equal to this Vector3D instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof FieldVector3D) { + @SuppressWarnings("unchecked") + final FieldVector3D rhs = (FieldVector3D) other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return x.equals(rhs.x) && y.equals(rhs.y) && z.equals(rhs.z); + + } + return false; + } + + /** + * Get a hashCode for the 3D vector. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 409; + } + return 311 * (107 * x.hashCode() + 83 * y.hashCode() + z.hashCode()); + } + + /** Compute the dot-product of the instance and another vector. + *

    + * The implementation uses specific multiplication and addition + * algorithms to preserve accuracy and reduce cancellation effects. + * It should be very accurate even for nearly orthogonal vectors. + *

    + * @see MathArrays#linearCombination(double, double, double, double, double, double) + * @param v second vector + * @return the dot product this.v + */ + public T dotProduct(final FieldVector3D v) { + return x.linearCombination(x, v.x, y, v.y, z, v.z); + } + + /** Compute the dot-product of the instance and another vector. + *

    + * The implementation uses specific multiplication and addition + * algorithms to preserve accuracy and reduce cancellation effects. + * It should be very accurate even for nearly orthogonal vectors. + *

    + * @see MathArrays#linearCombination(double, double, double, double, double, double) + * @param v second vector + * @return the dot product this.v + */ + public T dotProduct(final Vector3D v) { + return x.linearCombination(v.getX(), x, v.getY(), y, v.getZ(), z); + } + + /** Compute the cross-product of the instance with another vector. + * @param v other vector + * @return the cross product this ^ v as a new Vector3D + */ + public FieldVector3D crossProduct(final FieldVector3D v) { + return new FieldVector3D(x.linearCombination(y, v.z, z.negate(), v.y), + y.linearCombination(z, v.x, x.negate(), v.z), + z.linearCombination(x, v.y, y.negate(), v.x)); + } + + /** Compute the cross-product of the instance with another vector. + * @param v other vector + * @return the cross product this ^ v as a new Vector3D + */ + public FieldVector3D crossProduct(final Vector3D v) { + return new FieldVector3D(x.linearCombination(v.getZ(), y, -v.getY(), z), + y.linearCombination(v.getX(), z, -v.getZ(), x), + z.linearCombination(v.getY(), x, -v.getX(), y)); + } + + /** Compute the distance between the instance and another vector according to the L1 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm1() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L1 norm + */ + public T distance1(final FieldVector3D v) { + final T dx = v.x.subtract(x).abs(); + final T dy = v.y.subtract(y).abs(); + final T dz = v.z.subtract(z).abs(); + return dx.add(dy).add(dz); + } + + /** Compute the distance between the instance and another vector according to the L1 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm1() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L1 norm + */ + public T distance1(final Vector3D v) { + final T dx = x.subtract(v.getX()).abs(); + final T dy = y.subtract(v.getY()).abs(); + final T dz = z.subtract(v.getZ()).abs(); + return dx.add(dy).add(dz); + } + + /** Compute the distance between the instance and another vector according to the L2 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L2 norm + */ + public T distance(final FieldVector3D v) { + final T dx = v.x.subtract(x); + final T dy = v.y.subtract(y); + final T dz = v.z.subtract(z); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt(); + } + + /** Compute the distance between the instance and another vector according to the L2 norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNorm() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L2 norm + */ + public T distance(final Vector3D v) { + final T dx = x.subtract(v.getX()); + final T dy = y.subtract(v.getY()); + final T dz = z.subtract(v.getZ()); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt(); + } + + /** Compute the distance between the instance and another vector according to the L norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormInf() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L norm + */ + public T distanceInf(final FieldVector3D v) { + final T dx = v.x.subtract(x).abs(); + final T dy = v.y.subtract(y).abs(); + final T dz = v.z.subtract(z).abs(); + if (dx.getReal() <= dy.getReal()) { + if (dy.getReal() <= dz.getReal()) { + return dz; + } else { + return dy; + } + } else { + if (dx.getReal() <= dz.getReal()) { + return dz; + } else { + return dx; + } + } + } + + /** Compute the distance between the instance and another vector according to the L norm. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormInf() except that no intermediate + * vector is built

    + * @param v second vector + * @return the distance between the instance and p according to the L norm + */ + public T distanceInf(final Vector3D v) { + final T dx = x.subtract(v.getX()).abs(); + final T dy = y.subtract(v.getY()).abs(); + final T dz = z.subtract(v.getZ()).abs(); + if (dx.getReal() <= dy.getReal()) { + if (dy.getReal() <= dz.getReal()) { + return dz; + } else { + return dy; + } + } else { + if (dx.getReal() <= dz.getReal()) { + return dz; + } else { + return dx; + } + } + } + + /** Compute the square of the distance between the instance and another vector. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormSq() except that no intermediate + * vector is built

    + * @param v second vector + * @return the square of the distance between the instance and p + */ + public T distanceSq(final FieldVector3D v) { + final T dx = v.x.subtract(x); + final T dy = v.y.subtract(y); + final T dz = v.z.subtract(z); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)); + } + + /** Compute the square of the distance between the instance and another vector. + *

    Calling this method is equivalent to calling: + * q.subtract(p).getNormSq() except that no intermediate + * vector is built

    + * @param v second vector + * @return the square of the distance between the instance and p + */ + public T distanceSq(final Vector3D v) { + final T dx = x.subtract(v.getX()); + final T dy = y.subtract(v.getY()); + final T dz = z.subtract(v.getZ()); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the dot product v1.v2 + */ + public static > T dotProduct(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.dotProduct(v2); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the dot product v1.v2 + */ + public static > T dotProduct(final FieldVector3D v1, + final Vector3D v2) { + return v1.dotProduct(v2); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the dot product v1.v2 + */ + public static > T dotProduct(final Vector3D v1, + final FieldVector3D v2) { + return v2.dotProduct(v1); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the cross product v1 ^ v2 as a new Vector + */ + public static > FieldVector3D crossProduct(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.crossProduct(v2); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the cross product v1 ^ v2 as a new Vector + */ + public static > FieldVector3D crossProduct(final FieldVector3D v1, + final Vector3D v2) { + return v1.crossProduct(v2); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the cross product v1 ^ v2 as a new Vector + */ + public static > FieldVector3D crossProduct(final Vector3D v1, + final FieldVector3D v2) { + return new FieldVector3D(v2.x.linearCombination(v1.getY(), v2.z, -v1.getZ(), v2.y), + v2.y.linearCombination(v1.getZ(), v2.x, -v1.getX(), v2.z), + v2.z.linearCombination(v1.getX(), v2.y, -v1.getY(), v2.x)); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L1 norm + */ + public static > T distance1(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.distance1(v2); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L1 norm + */ + public static > T distance1(final FieldVector3D v1, + final Vector3D v2) { + return v1.distance1(v2); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L1 norm + */ + public static > T distance1(final Vector3D v1, + final FieldVector3D v2) { + return v2.distance1(v1); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L2 norm + */ + public static > T distance(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.distance(v2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L2 norm + */ + public static > T distance(final FieldVector3D v1, + final Vector3D v2) { + return v1.distance(v2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L2 norm + */ + public static > T distance(final Vector3D v1, + final FieldVector3D v2) { + return v2.distance(v1); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L norm + */ + public static > T distanceInf(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.distanceInf(v2); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L norm + */ + public static > T distanceInf(final FieldVector3D v1, + final Vector3D v2) { + return v1.distanceInf(v2); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the distance between v1 and v2 according to the L norm + */ + public static > T distanceInf(final Vector3D v1, + final FieldVector3D v2) { + return v2.distanceInf(v1); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the square of the distance between v1 and v2 + */ + public static > T distanceSq(final FieldVector3D v1, + final FieldVector3D v2) { + return v1.distanceSq(v2); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the square of the distance between v1 and v2 + */ + public static > T distanceSq(final FieldVector3D v1, + final Vector3D v2) { + return v1.distanceSq(v2); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @param the type of the field elements + * @return the square of the distance between v1 and v2 + */ + public static > T distanceSq(final Vector3D v1, + final FieldVector3D v2) { + return v2.distanceSq(v1); + } + + /** Get a string representation of this vector. + * @return a string representation of this vector + */ + @Override + public String toString() { + return Vector3DFormat.getInstance().format(toVector3D()); + } + + /** Get a string representation of this vector. + * @param format the custom format for components + * @return a string representation of this vector + */ + public String toString(final NumberFormat format) { + return new Vector3DFormat(format).format(toVector3D()); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Line.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Line.java new file mode 100644 index 000000000..d24f040e1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Line.java @@ -0,0 +1,275 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Embedding; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** The class represent lines in a three dimensional space. + + *

    Each oriented line is intrinsically associated with an abscissa + * which is a coordinate on the line. The point at abscissa 0 is the + * orthogonal projection of the origin on the line, another equivalent + * way to express this is to say that it is the point of the line + * which is closest to the origin. Abscissa increases in the line + * direction.

    + + * @since 3.0 + */ +public class Line implements Embedding { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Line direction. */ + private Vector3D direction; + + /** Line point closest to the origin. */ + private Vector3D zero; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** Build a line from two points. + * @param p1 first point belonging to the line (this can be any point) + * @param p2 second point belonging to the line (this can be any point, different from p1) + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if the points are equal + * @since 3.3 + */ + public Line(final Vector3D p1, final Vector3D p2, final double tolerance) + throws MathIllegalArgumentException { + reset(p1, p2); + this.tolerance = tolerance; + } + + /** Copy constructor. + *

    The created instance is completely independent from the + * original instance, it is a deep copy.

    + * @param line line to copy + */ + public Line(final Line line) { + this.direction = line.direction; + this.zero = line.zero; + this.tolerance = line.tolerance; + } + + /** Build a line from two points. + * @param p1 first point belonging to the line (this can be any point) + * @param p2 second point belonging to the line (this can be any point, different from p1) + * @exception MathIllegalArgumentException if the points are equal + * @deprecated as of 3.3, replaced with {@link #Line(Vector3D, Vector3D, double)} + */ + @Deprecated + public Line(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException { + this(p1, p2, DEFAULT_TOLERANCE); + } + + /** Reset the instance as if built from two points. + * @param p1 first point belonging to the line (this can be any point) + * @param p2 second point belonging to the line (this can be any point, different from p1) + * @exception MathIllegalArgumentException if the points are equal + */ + public void reset(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException { + final Vector3D delta = p2.subtract(p1); + final double norm2 = delta.getNormSq(); + if (norm2 == 0.0) { + throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM); + } + this.direction = new Vector3D(1.0 / FastMath.sqrt(norm2), delta); + zero = new Vector3D(1.0, p1, -p1.dotProduct(delta) / norm2, delta); + } + + /** Get the tolerance below which points are considered identical. + * @return tolerance below which points are considered identical + * @since 3.3 + */ + public double getTolerance() { + return tolerance; + } + + /** Get a line with reversed direction. + * @return a new instance, with reversed direction + */ + public Line revert() { + final Line reverted = new Line(this); + reverted.direction = reverted.direction.negate(); + return reverted; + } + + /** Get the normalized direction vector. + * @return normalized direction vector + */ + public Vector3D getDirection() { + return direction; + } + + /** Get the line point closest to the origin. + * @return line point closest to the origin + */ + public Vector3D getOrigin() { + return zero; + } + + /** Get the abscissa of a point with respect to the line. + *

    The abscissa is 0 if the projection of the point and the + * projection of the frame origin on the line are the same + * point.

    + * @param point point to check + * @return abscissa of the point + */ + public double getAbscissa(final Vector3D point) { + return point.subtract(zero).dotProduct(direction); + } + + /** Get one point from the line. + * @param abscissa desired abscissa for the point + * @return one point belonging to the line, at specified abscissa + */ + public Vector3D pointAt(final double abscissa) { + return new Vector3D(1.0, zero, abscissa, direction); + } + + /** Transform a space point into a sub-space point. + * @param vector n-dimension point of the space + * @return (n-1)-dimension point of the sub-space corresponding to + * the specified space point + */ + public Vector1D toSubSpace(Vector vector) { + return toSubSpace((Point) vector); + } + + /** Transform a sub-space point into a space point. + * @param vector (n-1)-dimension point of the sub-space + * @return n-dimension point of the space corresponding to the + * specified sub-space point + */ + public Vector3D toSpace(Vector vector) { + return toSpace((Point) vector); + } + + /** {@inheritDoc} + * @see #getAbscissa(Vector3D) + */ + public Vector1D toSubSpace(final Point point) { + return new Vector1D(getAbscissa((Vector3D) point)); + } + + /** {@inheritDoc} + * @see #pointAt(double) + */ + public Vector3D toSpace(final Point point) { + return pointAt(((Vector1D) point).getX()); + } + + /** Check if the instance is similar to another line. + *

    Lines are considered similar if they contain the same + * points. This does not mean they are equal since they can have + * opposite directions.

    + * @param line line to which instance should be compared + * @return true if the lines are similar + */ + public boolean isSimilarTo(final Line line) { + final double angle = Vector3D.angle(direction, line.direction); + return ((angle < tolerance) || (angle > (FastMath.PI - tolerance))) && contains(line.zero); + } + + /** Check if the instance contains a point. + * @param p point to check + * @return true if p belongs to the line + */ + public boolean contains(final Vector3D p) { + return distance(p) < tolerance; + } + + /** Compute the distance between the instance and a point. + * @param p to check + * @return distance between the instance and the point + */ + public double distance(final Vector3D p) { + final Vector3D d = p.subtract(zero); + final Vector3D n = new Vector3D(1.0, d, -d.dotProduct(direction), direction); + return n.getNorm(); + } + + /** Compute the shortest distance between the instance and another line. + * @param line line to check against the instance + * @return shortest distance between the instance and the line + */ + public double distance(final Line line) { + + final Vector3D normal = Vector3D.crossProduct(direction, line.direction); + final double n = normal.getNorm(); + if (n < Precision.SAFE_MIN) { + // lines are parallel + return distance(line.zero); + } + + // signed separation of the two parallel planes that contains the lines + final double offset = line.zero.subtract(zero).dotProduct(normal) / n; + + return FastMath.abs(offset); + + } + + /** Compute the point of the instance closest to another line. + * @param line line to check against the instance + * @return point of the instance closest to another line + */ + public Vector3D closestPoint(final Line line) { + + final double cos = direction.dotProduct(line.direction); + final double n = 1 - cos * cos; + if (n < Precision.EPSILON) { + // the lines are parallel + return zero; + } + + final Vector3D delta0 = line.zero.subtract(zero); + final double a = delta0.dotProduct(direction); + final double b = delta0.dotProduct(line.direction); + + return new Vector3D(1, zero, (a - b * cos) / n, direction); + + } + + /** Get the intersection point of the instance and another line. + * @param line other line + * @return intersection point of the instance and the other line + * or null if there are no intersection points + */ + public Vector3D intersection(final Line line) { + final Vector3D closest = closestPoint(line); + return line.contains(closest) ? closest : null; + } + + /** Build a sub-line covering the whole line. + * @return a sub-line covering the whole line + */ + public SubLine wholeLine() { + return new SubLine(this, new IntervalsSet(tolerance)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java new file mode 100644 index 000000000..7b0825559 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * This class represents exceptions thrown while building rotations + * from matrices. + * + * @since 1.2 + */ + +public class NotARotationMatrixException + extends MathIllegalArgumentException { + + /** Serializable version identifier */ + private static final long serialVersionUID = 5647178478658937642L; + + /** + * Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + * @since 2.2 + */ + public NotARotationMatrixException(Localizable specifier, Object ... parts) { + super(specifier, parts); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java new file mode 100644 index 000000000..8d30ece47 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.util.ArrayList; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.RegionFactory; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Extractor for {@link PolygonsSet polyhedrons sets} outlines. + *

    This class extracts the 2D outlines from {{@link PolygonsSet + * polyhedrons sets} in a specified projection plane.

    + * @since 3.0 + */ +public class OutlineExtractor { + + /** Abscissa axis of the projection plane. */ + private Vector3D u; + + /** Ordinate axis of the projection plane. */ + private Vector3D v; + + /** Normal of the projection plane (viewing direction). */ + private Vector3D w; + + /** Build an extractor for a specific projection plane. + * @param u abscissa axis of the projection point + * @param v ordinate axis of the projection point + */ + public OutlineExtractor(final Vector3D u, final Vector3D v) { + this.u = u; + this.v = v; + w = Vector3D.crossProduct(u, v); + } + + /** Extract the outline of a polyhedrons set. + * @param polyhedronsSet polyhedrons set whose outline must be extracted + * @return an outline, as an array of loops. + */ + public Vector2D[][] getOutline(final PolyhedronsSet polyhedronsSet) { + + // project all boundary facets into one polygons set + final BoundaryProjector projector = new BoundaryProjector(polyhedronsSet.getTolerance()); + polyhedronsSet.getTree(true).visit(projector); + final PolygonsSet projected = projector.getProjected(); + + // Remove the spurious intermediate vertices from the outline + final Vector2D[][] outline = projected.getVertices(); + for (int i = 0; i < outline.length; ++i) { + final Vector2D[] rawLoop = outline[i]; + int end = rawLoop.length; + int j = 0; + while (j < end) { + if (pointIsBetween(rawLoop, end, j)) { + // the point should be removed + for (int k = j; k < (end - 1); ++k) { + rawLoop[k] = rawLoop[k + 1]; + } + --end; + } else { + // the point remains in the loop + ++j; + } + } + if (end != rawLoop.length) { + // resize the array + outline[i] = new Vector2D[end]; + System.arraycopy(rawLoop, 0, outline[i], 0, end); + } + } + + return outline; + + } + + /** Check if a point is geometrically between its neighbor in an array. + *

    The neighbors are computed considering the array is a loop + * (i.e. point at index (n-1) is before point at index 0)

    + * @param loop points array + * @param n number of points to consider in the array + * @param i index of the point to check (must be between 0 and n-1) + * @return true if the point is exactly between its neighbors + */ + private boolean pointIsBetween(final Vector2D[] loop, final int n, final int i) { + final Vector2D previous = loop[(i + n - 1) % n]; + final Vector2D current = loop[i]; + final Vector2D next = loop[(i + 1) % n]; + final double dx1 = current.getX() - previous.getX(); + final double dy1 = current.getY() - previous.getY(); + final double dx2 = next.getX() - current.getX(); + final double dy2 = next.getY() - current.getY(); + final double cross = dx1 * dy2 - dx2 * dy1; + final double dot = dx1 * dx2 + dy1 * dy2; + final double d1d2 = FastMath.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)); + return (FastMath.abs(cross) <= (1.0e-6 * d1d2)) && (dot >= 0.0); + } + + /** Visitor projecting the boundary facets on a plane. */ + private class BoundaryProjector implements BSPTreeVisitor { + + /** Projection of the polyhedrons set on the plane. */ + private PolygonsSet projected; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** Simple constructor. + * @param tolerance tolerance below which points are considered identical + */ + BoundaryProjector(final double tolerance) { + this.projected = new PolygonsSet(new BSPTree(Boolean.FALSE), tolerance); + this.tolerance = tolerance; + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = + (BoundaryAttribute) node.getAttribute(); + if (attribute.getPlusOutside() != null) { + addContribution(attribute.getPlusOutside(), false); + } + if (attribute.getPlusInside() != null) { + addContribution(attribute.getPlusInside(), true); + } + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + } + + /** Add he contribution of a boundary facet. + * @param facet boundary facet + * @param reversed if true, the facet has the inside on its plus side + */ + private void addContribution(final SubHyperplane facet, final boolean reversed) { + + // extract the vertices of the facet + @SuppressWarnings("unchecked") + final AbstractSubHyperplane absFacet = + (AbstractSubHyperplane) facet; + final Plane plane = (Plane) facet.getHyperplane(); + + final double scal = plane.getNormal().dotProduct(w); + if (FastMath.abs(scal) > 1.0e-3) { + Vector2D[][] vertices = + ((PolygonsSet) absFacet.getRemainingRegion()).getVertices(); + + if ((scal < 0) ^ reversed) { + // the facet is seen from the inside, + // we need to invert its boundary orientation + final Vector2D[][] newVertices = new Vector2D[vertices.length][]; + for (int i = 0; i < vertices.length; ++i) { + final Vector2D[] loop = vertices[i]; + final Vector2D[] newLoop = new Vector2D[loop.length]; + if (loop[0] == null) { + newLoop[0] = null; + for (int j = 1; j < loop.length; ++j) { + newLoop[j] = loop[loop.length - j]; + } + } else { + for (int j = 0; j < loop.length; ++j) { + newLoop[j] = loop[loop.length - (j + 1)]; + } + } + newVertices[i] = newLoop; + } + + // use the reverted vertices + vertices = newVertices; + + } + + // compute the projection of the facet in the outline plane + final ArrayList> edges = new ArrayList>(); + for (Vector2D[] loop : vertices) { + final boolean closed = loop[0] != null; + int previous = closed ? (loop.length - 1) : 1; + Vector3D previous3D = plane.toSpace((Point) loop[previous]); + int current = (previous + 1) % loop.length; + Vector2D pPoint = new Vector2D(previous3D.dotProduct(u), + previous3D.dotProduct(v)); + while (current < loop.length) { + + final Vector3D current3D = plane.toSpace((Point) loop[current]); + final Vector2D cPoint = new Vector2D(current3D.dotProduct(u), + current3D.dotProduct(v)); + final Line line = + new Line(pPoint, cPoint, tolerance); + SubHyperplane edge = line.wholeHyperplane(); + + if (closed || (previous != 1)) { + // the previous point is a real vertex + // it defines one bounding point of the edge + final double angle = line.getAngle() + 0.5 * FastMath.PI; + final Line l = + new Line(pPoint, angle, tolerance); + edge = edge.split(l).getPlus(); + } + + if (closed || (current != (loop.length - 1))) { + // the current point is a real vertex + // it defines one bounding point of the edge + final double angle = line.getAngle() + 0.5 * FastMath.PI; + final Line l = + new Line(cPoint, angle, tolerance); + edge = edge.split(l).getMinus(); + } + + edges.add(edge); + + previous = current++; + previous3D = current3D; + pPoint = cPoint; + + } + } + final PolygonsSet projectedFacet = new PolygonsSet(edges, tolerance); + + // add the contribution of the facet to the global outline + projected = (PolygonsSet) new RegionFactory().union(projected, projectedFacet); + + } + } + + /** Get the projection of the polyhedrons set on the plane. + * @return projection of the polyhedrons set on the plane + */ + public PolygonsSet getProjected() { + return projected; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Plane.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Plane.java new file mode 100644 index 000000000..4d5c255c1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Plane.java @@ -0,0 +1,527 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Embedding; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** The class represent planes in a three dimensional space. + * @since 3.0 + */ +public class Plane implements Hyperplane, Embedding { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Offset of the origin with respect to the plane. */ + private double originOffset; + + /** Origin of the plane frame. */ + private Vector3D origin; + + /** First vector of the plane frame (in plane). */ + private Vector3D u; + + /** Second vector of the plane frame (in plane). */ + private Vector3D v; + + /** Third vector of the plane frame (plane normal). */ + private Vector3D w; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** Build a plane normal to a given direction and containing the origin. + * @param normal normal direction to the plane + * @param tolerance tolerance below which points are considered identical + * @exception MathArithmeticException if the normal norm is too small + * @since 3.3 + */ + public Plane(final Vector3D normal, final double tolerance) + throws MathArithmeticException { + setNormal(normal); + this.tolerance = tolerance; + originOffset = 0; + setFrame(); + } + + /** Build a plane from a point and a normal. + * @param p point belonging to the plane + * @param normal normal direction to the plane + * @param tolerance tolerance below which points are considered identical + * @exception MathArithmeticException if the normal norm is too small + * @since 3.3 + */ + public Plane(final Vector3D p, final Vector3D normal, final double tolerance) + throws MathArithmeticException { + setNormal(normal); + this.tolerance = tolerance; + originOffset = -p.dotProduct(w); + setFrame(); + } + + /** Build a plane from three points. + *

    The plane is oriented in the direction of + * {@code (p2-p1) ^ (p3-p1)}

    + * @param p1 first point belonging to the plane + * @param p2 second point belonging to the plane + * @param p3 third point belonging to the plane + * @param tolerance tolerance below which points are considered identical + * @exception MathArithmeticException if the points do not constitute a plane + * @since 3.3 + */ + public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3, final double tolerance) + throws MathArithmeticException { + this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1)), tolerance); + } + + /** Build a plane normal to a given direction and containing the origin. + * @param normal normal direction to the plane + * @exception MathArithmeticException if the normal norm is too small + * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, double)} + */ + @Deprecated + public Plane(final Vector3D normal) throws MathArithmeticException { + this(normal, DEFAULT_TOLERANCE); + } + + /** Build a plane from a point and a normal. + * @param p point belonging to the plane + * @param normal normal direction to the plane + * @exception MathArithmeticException if the normal norm is too small + * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, double)} + */ + @Deprecated + public Plane(final Vector3D p, final Vector3D normal) throws MathArithmeticException { + this(p, normal, DEFAULT_TOLERANCE); + } + + /** Build a plane from three points. + *

    The plane is oriented in the direction of + * {@code (p2-p1) ^ (p3-p1)}

    + * @param p1 first point belonging to the plane + * @param p2 second point belonging to the plane + * @param p3 third point belonging to the plane + * @exception MathArithmeticException if the points do not constitute a plane + * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, Vector3D, double)} + */ + @Deprecated + public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3) + throws MathArithmeticException { + this(p1, p2, p3, DEFAULT_TOLERANCE); + } + + /** Copy constructor. + *

    The instance created is completely independant of the original + * one. A deep copy is used, none of the underlying object are + * shared.

    + * @param plane plane to copy + */ + public Plane(final Plane plane) { + originOffset = plane.originOffset; + origin = plane.origin; + u = plane.u; + v = plane.v; + w = plane.w; + tolerance = plane.tolerance; + } + + /** Copy the instance. + *

    The instance created is completely independant of the original + * one. A deep copy is used, none of the underlying objects are + * shared (except for immutable objects).

    + * @return a new hyperplane, copy of the instance + */ + public Plane copySelf() { + return new Plane(this); + } + + /** Reset the instance as if built from a point and a normal. + * @param p point belonging to the plane + * @param normal normal direction to the plane + * @exception MathArithmeticException if the normal norm is too small + */ + public void reset(final Vector3D p, final Vector3D normal) throws MathArithmeticException { + setNormal(normal); + originOffset = -p.dotProduct(w); + setFrame(); + } + + /** Reset the instance from another one. + *

    The updated instance is completely independant of the original + * one. A deep reset is used none of the underlying object is + * shared.

    + * @param original plane to reset from + */ + public void reset(final Plane original) { + originOffset = original.originOffset; + origin = original.origin; + u = original.u; + v = original.v; + w = original.w; + } + + /** Set the normal vactor. + * @param normal normal direction to the plane (will be copied) + * @exception MathArithmeticException if the normal norm is too small + */ + private void setNormal(final Vector3D normal) throws MathArithmeticException { + final double norm = normal.getNorm(); + if (norm < 1.0e-10) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + w = new Vector3D(1.0 / norm, normal); + } + + /** Reset the plane frame. + */ + private void setFrame() { + origin = new Vector3D(-originOffset, w); + u = w.orthogonal(); + v = Vector3D.crossProduct(w, u); + } + + /** Get the origin point of the plane frame. + *

    The point returned is the orthogonal projection of the + * 3D-space origin in the plane.

    + * @return the origin point of the plane frame (point closest to the + * 3D-space origin) + */ + public Vector3D getOrigin() { + return origin; + } + + /** Get the normalized normal vector. + *

    The frame defined by ({@link #getU getU}, {@link #getV getV}, + * {@link #getNormal getNormal}) is a rigth-handed orthonormalized + * frame).

    + * @return normalized normal vector + * @see #getU + * @see #getV + */ + public Vector3D getNormal() { + return w; + } + + /** Get the plane first canonical vector. + *

    The frame defined by ({@link #getU getU}, {@link #getV getV}, + * {@link #getNormal getNormal}) is a rigth-handed orthonormalized + * frame).

    + * @return normalized first canonical vector + * @see #getV + * @see #getNormal + */ + public Vector3D getU() { + return u; + } + + /** Get the plane second canonical vector. + *

    The frame defined by ({@link #getU getU}, {@link #getV getV}, + * {@link #getNormal getNormal}) is a rigth-handed orthonormalized + * frame).

    + * @return normalized second canonical vector + * @see #getU + * @see #getNormal + */ + public Vector3D getV() { + return v; + } + + /** {@inheritDoc} + * @since 3.3 + */ + public Point project(Point point) { + return toSpace(toSubSpace(point)); + } + + /** {@inheritDoc} + * @since 3.3 + */ + public double getTolerance() { + return tolerance; + } + + /** Revert the plane. + *

    Replace the instance by a similar plane with opposite orientation.

    + *

    The new plane frame is chosen in such a way that a 3D point that had + * {@code (x, y)} in-plane coordinates and {@code z} offset with + * respect to the plane and is unaffected by the change will have + * {@code (y, x)} in-plane coordinates and {@code -z} offset with + * respect to the new plane. This means that the {@code u} and {@code v} + * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged, + * and the {@code w} vector returned by the {@link #getNormal} method is + * reversed.

    + */ + public void revertSelf() { + final Vector3D tmp = u; + u = v; + v = tmp; + w = w.negate(); + originOffset = -originOffset; + } + + /** Transform a space point into a sub-space point. + * @param vector n-dimension point of the space + * @return (n-1)-dimension point of the sub-space corresponding to + * the specified space point + */ + public Vector2D toSubSpace(Vector vector) { + return toSubSpace((Point) vector); + } + + /** Transform a sub-space point into a space point. + * @param vector (n-1)-dimension point of the sub-space + * @return n-dimension point of the space corresponding to the + * specified sub-space point + */ + public Vector3D toSpace(Vector vector) { + return toSpace((Point) vector); + } + + /** Transform a 3D space point into an in-plane point. + * @param point point of the space (must be a {@link Vector3D + * Vector3D} instance) + * @return in-plane point (really a {@link + * Vector2D Vector2D} instance) + * @see #toSpace + */ + public Vector2D toSubSpace(final Point point) { + final Vector3D p3D = (Vector3D) point; + return new Vector2D(p3D.dotProduct(u), p3D.dotProduct(v)); + } + + /** Transform an in-plane point into a 3D space point. + * @param point in-plane point (must be a {@link + * Vector2D Vector2D} instance) + * @return 3D space point (really a {@link Vector3D Vector3D} instance) + * @see #toSubSpace + */ + public Vector3D toSpace(final Point point) { + final Vector2D p2D = (Vector2D) point; + return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w); + } + + /** Get one point from the 3D-space. + * @param inPlane desired in-plane coordinates for the point in the + * plane + * @param offset desired offset for the point + * @return one point in the 3D-space, with given coordinates and offset + * relative to the plane + */ + public Vector3D getPointAt(final Vector2D inPlane, final double offset) { + return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w); + } + + /** Check if the instance is similar to another plane. + *

    Planes are considered similar if they contain the same + * points. This does not mean they are equal since they can have + * opposite normals.

    + * @param plane plane to which the instance is compared + * @return true if the planes are similar + */ + public boolean isSimilarTo(final Plane plane) { + final double angle = Vector3D.angle(w, plane.w); + return ((angle < 1.0e-10) && (FastMath.abs(originOffset - plane.originOffset) < tolerance)) || + ((angle > (FastMath.PI - 1.0e-10)) && (FastMath.abs(originOffset + plane.originOffset) < tolerance)); + } + + /** Rotate the plane around the specified point. + *

    The instance is not modified, a new instance is created.

    + * @param center rotation center + * @param rotation vectorial rotation operator + * @return a new plane + */ + public Plane rotate(final Vector3D center, final Rotation rotation) { + + final Vector3D delta = origin.subtract(center); + final Plane plane = new Plane(center.add(rotation.applyTo(delta)), + rotation.applyTo(w), tolerance); + + // make sure the frame is transformed as desired + plane.u = rotation.applyTo(u); + plane.v = rotation.applyTo(v); + + return plane; + + } + + /** Translate the plane by the specified amount. + *

    The instance is not modified, a new instance is created.

    + * @param translation translation to apply + * @return a new plane + */ + public Plane translate(final Vector3D translation) { + + final Plane plane = new Plane(origin.add(translation), w, tolerance); + + // make sure the frame is transformed as desired + plane.u = u; + plane.v = v; + + return plane; + + } + + /** Get the intersection of a line with the instance. + * @param line line intersecting the instance + * @return intersection point between between the line and the + * instance (null if the line is parallel to the instance) + */ + public Vector3D intersection(final Line line) { + final Vector3D direction = line.getDirection(); + final double dot = w.dotProduct(direction); + if (FastMath.abs(dot) < 1.0e-10) { + return null; + } + final Vector3D point = line.toSpace((Point) Vector1D.ZERO); + final double k = -(originOffset + w.dotProduct(point)) / dot; + return new Vector3D(1.0, point, k, direction); + } + + /** Build the line shared by the instance and another plane. + * @param other other plane + * @return line at the intersection of the instance and the + * other plane (really a {@link Line Line} instance) + */ + public Line intersection(final Plane other) { + final Vector3D direction = Vector3D.crossProduct(w, other.w); + if (direction.getNorm() < tolerance) { + return null; + } + final Vector3D point = intersection(this, other, new Plane(direction, tolerance)); + return new Line(point, point.add(direction), tolerance); + } + + /** Get the intersection point of three planes. + * @param plane1 first plane1 + * @param plane2 second plane2 + * @param plane3 third plane2 + * @return intersection point of three planes, null if some planes are parallel + */ + public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) { + + // coefficients of the three planes linear equations + final double a1 = plane1.w.getX(); + final double b1 = plane1.w.getY(); + final double c1 = plane1.w.getZ(); + final double d1 = plane1.originOffset; + + final double a2 = plane2.w.getX(); + final double b2 = plane2.w.getY(); + final double c2 = plane2.w.getZ(); + final double d2 = plane2.originOffset; + + final double a3 = plane3.w.getX(); + final double b3 = plane3.w.getY(); + final double c3 = plane3.w.getZ(); + final double d3 = plane3.originOffset; + + // direct Cramer resolution of the linear system + // (this is still feasible for a 3x3 system) + final double a23 = b2 * c3 - b3 * c2; + final double b23 = c2 * a3 - c3 * a2; + final double c23 = a2 * b3 - a3 * b2; + final double determinant = a1 * a23 + b1 * b23 + c1 * c23; + if (FastMath.abs(determinant) < 1.0e-10) { + return null; + } + + final double r = 1.0 / determinant; + return new Vector3D( + (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r, + (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r, + (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r); + + } + + /** Build a region covering the whole hyperplane. + * @return a region covering the whole hyperplane + */ + public SubPlane wholeHyperplane() { + return new SubPlane(this, new PolygonsSet(tolerance)); + } + + /** Build a region covering the whole space. + * @return a region containing the instance (really a {@link + * PolyhedronsSet PolyhedronsSet} instance) + */ + public PolyhedronsSet wholeSpace() { + return new PolyhedronsSet(tolerance); + } + + /** Check if the instance contains a point. + * @param p point to check + * @return true if p belongs to the plane + */ + public boolean contains(final Vector3D p) { + return FastMath.abs(getOffset(p)) < tolerance; + } + + /** Get the offset (oriented distance) of a parallel plane. + *

    This method should be called only for parallel planes otherwise + * the result is not meaningful.

    + *

    The offset is 0 if both planes are the same, it is + * positive if the plane is on the plus side of the instance and + * negative if it is on the minus side, according to its natural + * orientation.

    + * @param plane plane to check + * @return offset of the plane + */ + public double getOffset(final Plane plane) { + return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset); + } + + /** Get the offset (oriented distance) of a vector. + * @param vector vector to check + * @return offset of the vector + */ + public double getOffset(Vector vector) { + return getOffset((Point) vector); + } + + /** Get the offset (oriented distance) of a point. + *

    The offset is 0 if the point is on the underlying hyperplane, + * it is positive if the point is on one particular side of the + * hyperplane, and it is negative if the point is on the other side, + * according to the hyperplane natural orientation.

    + * @param point point to check + * @return offset of the point + */ + public double getOffset(final Point point) { + return ((Vector3D) point).dotProduct(w) + originOffset; + } + + /** Check if the instance has the same orientation as another hyperplane. + * @param other other hyperplane to check against the instance + * @return true if the instance and the other hyperplane have + * the same orientation + */ + public boolean sameOrientationAs(final Hyperplane other) { + return (((Plane) other).w).dotProduct(w) > 0.0; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java new file mode 100644 index 000000000..3e4823c88 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java @@ -0,0 +1,739 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.RegionFactory; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Transform; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.SubLine; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class represents a 3D region: a set of polyhedrons. + * @since 3.0 + */ +public class PolyhedronsSet extends AbstractRegion { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Build a polyhedrons set representing the whole real line. + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final double tolerance) { + super(tolerance); + } + + /** Build a polyhedrons set from a BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + *

    + * This constructor is aimed at expert use, as building the tree may + * be a difficult task. It is not intended for general use and for + * performances reasons does not check thoroughly its input, as this would + * require walking the full tree each time. Failing to provide a tree with + * the proper attributes, will therefore generate problems like + * {@link NullPointerException} or {@link ClassCastException} only later on. + * This limitation is known and explains why this constructor is for expert + * use only. The caller does have the responsibility to provided correct arguments. + *

    + * @param tree inside/outside BSP tree representing the region + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final BSPTree tree, final double tolerance) { + super(tree, tolerance); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by sub-hyperplanes. + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polyhedrons with holes + * or a set of disjoint polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link Region#checkPoint(Point) checkPoint} method will + * not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final Collection> boundary, + final double tolerance) { + super(boundary, tolerance); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices. + *

    + * The boundary is provided as a list of vertices and a list of facets. + * Each facet is specified as an integer array containing the arrays vertices + * indices in the vertices list. Each facet normal is oriented by right hand + * rule to the facet vertices list. + *

    + *

    + * Some basic sanity checks are performed but not everything is thoroughly + * assessed, so it remains under caller responsibility to ensure the vertices + * and facets are consistent and properly define a polyhedrons set. + *

    + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if some basic sanity checks fail + * @since 3.5 + */ + public PolyhedronsSet(final List vertices, final List facets, + final double tolerance) { + super(buildBoundary(vertices, facets, tolerance), tolerance); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax, + final double tolerance) { + super(buildBoundary(xMin, xMax, yMin, yMax, zMin, zMax, tolerance), tolerance); + } + + /** Build a polyhedrons set representing the whole real line. + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double)} + */ + @Deprecated + public PolyhedronsSet() { + this(DEFAULT_TOLERANCE); + } + + /** Build a polyhedrons set from a BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the region + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(BSPTree, double)} + */ + @Deprecated + public PolyhedronsSet(final BSPTree tree) { + this(tree, DEFAULT_TOLERANCE); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polyhedrons with holes + * or a set of disjoint polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link Region#checkPoint(Point) checkPoint} method will + * not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(Collection, double)} + */ + @Deprecated + public PolyhedronsSet(final Collection> boundary) { + this(boundary, DEFAULT_TOLERANCE); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double, double, + * double, double, double, double, double)} + */ + @Deprecated + public PolyhedronsSet(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax) { + this(xMin, xMax, yMin, yMax, zMin, zMax, DEFAULT_TOLERANCE); + } + + /** Build a parallellepipedic box boundary. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @param tolerance tolerance below which points are considered identical + * @return boundary tree + * @since 3.3 + */ + private static BSPTree buildBoundary(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax, + final double tolerance) { + if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance) || (zMin >= zMax - tolerance)) { + // too thin box, build an empty polygons set + return new BSPTree(Boolean.FALSE); + } + final Plane pxMin = new Plane(new Vector3D(xMin, 0, 0), Vector3D.MINUS_I, tolerance); + final Plane pxMax = new Plane(new Vector3D(xMax, 0, 0), Vector3D.PLUS_I, tolerance); + final Plane pyMin = new Plane(new Vector3D(0, yMin, 0), Vector3D.MINUS_J, tolerance); + final Plane pyMax = new Plane(new Vector3D(0, yMax, 0), Vector3D.PLUS_J, tolerance); + final Plane pzMin = new Plane(new Vector3D(0, 0, zMin), Vector3D.MINUS_K, tolerance); + final Plane pzMax = new Plane(new Vector3D(0, 0, zMax), Vector3D.PLUS_K, tolerance); + @SuppressWarnings("unchecked") + final Region boundary = + new RegionFactory().buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax); + return boundary.getTree(false); + } + + /** Build boundary from vertices and facets. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param tolerance tolerance below which points are considered identical + * @return boundary as a list of sub-hyperplanes + * @exception MathIllegalArgumentException if some basic sanity checks fail + * @since 3.5 + */ + private static List> buildBoundary(final List vertices, + final List facets, + final double tolerance) { + + // check vertices distances + for (int i = 0; i < vertices.size() - 1; ++i) { + final Vector3D vi = vertices.get(i); + for (int j = i + 1; j < vertices.size(); ++j) { + if (Vector3D.distance(vi, vertices.get(j)) <= tolerance) { + throw new MathIllegalArgumentException(LocalizedFormats.CLOSE_VERTICES, + vi.getX(), vi.getY(), vi.getZ()); + } + } + } + + // find how vertices are referenced by facets + final int[][] references = findReferences(vertices, facets); + + // find how vertices are linked together by edges along the facets they belong to + final int[][] successors = successors(vertices, facets, references); + + // check edges orientations + for (int vA = 0; vA < vertices.size(); ++vA) { + for (final int vB : successors[vA]) { + + if (vB >= 0) { + // when facets are properly oriented, if vB is the successor of vA on facet f1, + // then there must be an adjacent facet f2 where vA is the successor of vB + boolean found = false; + for (final int v : successors[vB]) { + found = found || (v == vA); + } + if (!found) { + final Vector3D start = vertices.get(vA); + final Vector3D end = vertices.get(vB); + throw new MathIllegalArgumentException(LocalizedFormats.EDGE_CONNECTED_TO_ONE_FACET, + start.getX(), start.getY(), start.getZ(), + end.getX(), end.getY(), end.getZ()); + } + } + } + } + + final List> boundary = new ArrayList>(); + + for (final int[] facet : facets) { + + // define facet plane from the first 3 points + Plane plane = new Plane(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]), + tolerance); + + // check all points are in the plane + final Vector2D[] two2Points = new Vector2D[facet.length]; + for (int i = 0 ; i < facet.length; ++i) { + final Vector3D v = vertices.get(facet[i]); + if (!plane.contains(v)) { + throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_PLANE, + v.getX(), v.getY(), v.getZ()); + } + two2Points[i] = plane.toSubSpace(v); + } + + // create the polygonal facet + boundary.add(new SubPlane(plane, new PolygonsSet(tolerance, two2Points))); + + } + + return boundary; + + } + + /** Find the facets that reference each edges. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @return references array such that r[v][k] = f for some k if facet f contains vertex v + * @exception MathIllegalArgumentException if some facets have fewer than 3 vertices + * @since 3.5 + */ + private static int[][] findReferences(final List vertices, final List facets) { + + // find the maximum number of facets a vertex belongs to + final int[] nbFacets = new int[vertices.size()]; + int maxFacets = 0; + for (final int[] facet : facets) { + if (facet.length < 3) { + throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS, + 3, facet.length, true); + } + for (final int index : facet) { + maxFacets = FastMath.max(maxFacets, ++nbFacets[index]); + } + } + + // set up the references array + final int[][] references = new int[vertices.size()][maxFacets]; + for (int[] r : references) { + Arrays.fill(r, -1); + } + for (int f = 0; f < facets.size(); ++f) { + for (final int v : facets.get(f)) { + // vertex v is referenced by facet f + int k = 0; + while (k < maxFacets && references[v][k] >= 0) { + ++k; + } + references[v][k] = f; + } + } + + return references; + + } + + /** Find the successors of all vertices among all facets they belong to. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param references facets references array + * @return indices of vertices that follow vertex v in some facet (the array + * may contain extra entries at the end, set to negative indices) + * @exception MathIllegalArgumentException if the same vertex appears more than + * once in the successors list (which means one facet orientation is wrong) + * @since 3.5 + */ + private static int[][] successors(final List vertices, final List facets, + final int[][] references) { + + // create an array large enough + final int[][] successors = new int[vertices.size()][references[0].length]; + for (final int[] s : successors) { + Arrays.fill(s, -1); + } + + for (int v = 0; v < vertices.size(); ++v) { + for (int k = 0; k < successors[v].length && references[v][k] >= 0; ++k) { + + // look for vertex v + final int[] facet = facets.get(references[v][k]); + int i = 0; + while (i < facet.length && facet[i] != v) { + ++i; + } + + // we have found vertex v, we deduce its successor on current facet + successors[v][k] = facet[(i + 1) % facet.length]; + for (int l = 0; l < k; ++l) { + if (successors[v][l] == successors[v][k]) { + final Vector3D start = vertices.get(v); + final Vector3D end = vertices.get(successors[v][k]); + throw new MathIllegalArgumentException(LocalizedFormats.FACET_ORIENTATION_MISMATCH, + start.getX(), start.getY(), start.getZ(), + end.getX(), end.getY(), end.getZ()); + } + } + + } + } + + return successors; + + } + + /** {@inheritDoc} */ + @Override + public PolyhedronsSet buildNew(final BSPTree tree) { + return new PolyhedronsSet(tree, getTolerance()); + } + + /** {@inheritDoc} */ + @Override + protected void computeGeometricalProperties() { + + // compute the contribution of all boundary facets + getTree(true).visit(new FacetsContributionVisitor()); + + if (getSize() < 0) { + // the polyhedrons set as a finite outside + // surrounded by an infinite inside + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point) Vector3D.NaN); + } else { + // the polyhedrons set is finite, apply the remaining scaling factors + setSize(getSize() / 3.0); + setBarycenter((Point) new Vector3D(1.0 / (4 * getSize()), (Vector3D) getBarycenter())); + } + + } + + /** Visitor computing geometrical properties. */ + private class FacetsContributionVisitor implements BSPTreeVisitor { + + /** Simple constructor. */ + FacetsContributionVisitor() { + setSize(0); + setBarycenter((Point) new Vector3D(0, 0, 0)); + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = + (BoundaryAttribute) node.getAttribute(); + if (attribute.getPlusOutside() != null) { + addContribution(attribute.getPlusOutside(), false); + } + if (attribute.getPlusInside() != null) { + addContribution(attribute.getPlusInside(), true); + } + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + } + + /** Add he contribution of a boundary facet. + * @param facet boundary facet + * @param reversed if true, the facet has the inside on its plus side + */ + private void addContribution(final SubHyperplane facet, final boolean reversed) { + + final Region polygon = ((SubPlane) facet).getRemainingRegion(); + final double area = polygon.getSize(); + + if (Double.isInfinite(area)) { + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point) Vector3D.NaN); + } else { + + final Plane plane = (Plane) facet.getHyperplane(); + final Vector3D facetB = plane.toSpace(polygon.getBarycenter()); + double scaled = area * facetB.dotProduct(plane.getNormal()); + if (reversed) { + scaled = -scaled; + } + + setSize(getSize() + scaled); + setBarycenter((Point) new Vector3D(1.0, (Vector3D) getBarycenter(), scaled, facetB)); + + } + + } + + } + + /** Get the first sub-hyperplane crossed by a semi-infinite line. + * @param point start point of the part of the line considered + * @param line line to consider (contains point) + * @return the first sub-hyperplane crossed by the line after the + * given point, or null if the line does not intersect any + * sub-hyperplane + */ + public SubHyperplane firstIntersection(final Vector3D point, final Line line) { + return recurseFirstIntersection(getTree(true), point, line); + } + + /** Get the first sub-hyperplane crossed by a semi-infinite line. + * @param node current node + * @param point start point of the part of the line considered + * @param line line to consider (contains point) + * @return the first sub-hyperplane crossed by the line after the + * given point, or null if the line does not intersect any + * sub-hyperplane + */ + private SubHyperplane recurseFirstIntersection(final BSPTree node, + final Vector3D point, + final Line line) { + + final SubHyperplane cut = node.getCut(); + if (cut == null) { + return null; + } + final BSPTree minus = node.getMinus(); + final BSPTree plus = node.getPlus(); + final Plane plane = (Plane) cut.getHyperplane(); + + // establish search order + final double offset = plane.getOffset((Point) point); + final boolean in = FastMath.abs(offset) < getTolerance(); + final BSPTree near; + final BSPTree far; + if (offset < 0) { + near = minus; + far = plus; + } else { + near = plus; + far = minus; + } + + if (in) { + // search in the cut hyperplane + final SubHyperplane facet = boundaryFacet(point, node); + if (facet != null) { + return facet; + } + } + + // search in the near branch + final SubHyperplane crossed = recurseFirstIntersection(near, point, line); + if (crossed != null) { + return crossed; + } + + if (!in) { + // search in the cut hyperplane + final Vector3D hit3D = plane.intersection(line); + if (hit3D != null && line.getAbscissa(hit3D) > line.getAbscissa(point)) { + final SubHyperplane facet = boundaryFacet(hit3D, node); + if (facet != null) { + return facet; + } + } + } + + // search in the far branch + return recurseFirstIntersection(far, point, line); + + } + + /** Check if a point belongs to the boundary part of a node. + * @param point point to check + * @param node node containing the boundary facet to check + * @return the boundary facet this points belongs to (or null if it + * does not belong to any boundary facet) + */ + private SubHyperplane boundaryFacet(final Vector3D point, + final BSPTree node) { + final Vector2D point2D = ((Plane) node.getCut().getHyperplane()).toSubSpace((Point) point); + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = + (BoundaryAttribute) node.getAttribute(); + if ((attribute.getPlusOutside() != null) && + (((SubPlane) attribute.getPlusOutside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { + return attribute.getPlusOutside(); + } + if ((attribute.getPlusInside() != null) && + (((SubPlane) attribute.getPlusInside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { + return attribute.getPlusInside(); + } + return null; + } + + /** Rotate the region around the specified point. + *

    The instance is not modified, a new instance is created.

    + * @param center rotation center + * @param rotation vectorial rotation operator + * @return a new instance representing the rotated region + */ + public PolyhedronsSet rotate(final Vector3D center, final Rotation rotation) { + return (PolyhedronsSet) applyTransform(new RotationTransform(center, rotation)); + } + + /** 3D rotation as a Transform. */ + private static class RotationTransform implements Transform { + + /** Center point of the rotation. */ + private Vector3D center; + + /** Vectorial rotation. */ + private Rotation rotation; + + /** Cached original hyperplane. */ + private Plane cachedOriginal; + + /** Cached 2D transform valid inside the cached original hyperplane. */ + private Transform cachedTransform; + + /** Build a rotation transform. + * @param center center point of the rotation + * @param rotation vectorial rotation + */ + RotationTransform(final Vector3D center, final Rotation rotation) { + this.center = center; + this.rotation = rotation; + } + + /** {@inheritDoc} */ + public Vector3D apply(final Point point) { + final Vector3D delta = ((Vector3D) point).subtract(center); + return new Vector3D(1.0, center, 1.0, rotation.applyTo(delta)); + } + + /** {@inheritDoc} */ + public Plane apply(final Hyperplane hyperplane) { + return ((Plane) hyperplane).rotate(center, rotation); + } + + /** {@inheritDoc} */ + public SubHyperplane apply(final SubHyperplane sub, + final Hyperplane original, + final Hyperplane transformed) { + if (original != cachedOriginal) { + // we have changed hyperplane, reset the in-hyperplane transform + + final Plane oPlane = (Plane) original; + final Plane tPlane = (Plane) transformed; + final Vector3D p00 = oPlane.getOrigin(); + final Vector3D p10 = oPlane.toSpace((Point) new Vector2D(1.0, 0.0)); + final Vector3D p01 = oPlane.toSpace((Point) new Vector2D(0.0, 1.0)); + final Vector2D tP00 = tPlane.toSubSpace((Point) apply(p00)); + final Vector2D tP10 = tPlane.toSubSpace((Point) apply(p10)); + final Vector2D tP01 = tPlane.toSubSpace((Point) apply(p01)); + + cachedOriginal = (Plane) original; + cachedTransform = + com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(tP10.getX() - tP00.getX(), + tP10.getY() - tP00.getY(), + tP01.getX() - tP00.getX(), + tP01.getY() - tP00.getY(), + tP00.getX(), + tP00.getY()); + + } + return ((SubLine) sub).applyTransform(cachedTransform); + } + + } + + /** Translate the region by the specified amount. + *

    The instance is not modified, a new instance is created.

    + * @param translation translation to apply + * @return a new instance representing the translated region + */ + public PolyhedronsSet translate(final Vector3D translation) { + return (PolyhedronsSet) applyTransform(new TranslationTransform(translation)); + } + + /** 3D translation as a transform. */ + private static class TranslationTransform implements Transform { + + /** Translation vector. */ + private Vector3D translation; + + /** Cached original hyperplane. */ + private Plane cachedOriginal; + + /** Cached 2D transform valid inside the cached original hyperplane. */ + private Transform cachedTransform; + + /** Build a translation transform. + * @param translation translation vector + */ + TranslationTransform(final Vector3D translation) { + this.translation = translation; + } + + /** {@inheritDoc} */ + public Vector3D apply(final Point point) { + return new Vector3D(1.0, (Vector3D) point, 1.0, translation); + } + + /** {@inheritDoc} */ + public Plane apply(final Hyperplane hyperplane) { + return ((Plane) hyperplane).translate(translation); + } + + /** {@inheritDoc} */ + public SubHyperplane apply(final SubHyperplane sub, + final Hyperplane original, + final Hyperplane transformed) { + if (original != cachedOriginal) { + // we have changed hyperplane, reset the in-hyperplane transform + + final Plane oPlane = (Plane) original; + final Plane tPlane = (Plane) transformed; + final Vector2D shift = tPlane.toSubSpace((Point) apply(oPlane.getOrigin())); + + cachedOriginal = (Plane) original; + cachedTransform = + com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(1, 0, 0, 1, + shift.getX(), + shift.getY()); + + } + + return ((SubLine) sub).applyTransform(cachedTransform); + + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java new file mode 100644 index 000000000..dd4ba61d7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java @@ -0,0 +1,1424 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * This class implements rotations in a three-dimensional space. + * + *

    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 + * Rotation instance (see the various constructors and + * getters). In addition, a rotation can also be built implicitly + * from a set of vectors and their image.

    + *

    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:

    + *
    + * double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ);
    + * 
    + *

    Focus is oriented on what a rotation do rather than on its + * underlying representation. Once it has been built, and regardless of its + * internal representation, a rotation is an operator 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.

    + *

    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.

    + *

    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.

    + *

    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.

    + * + *

    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 projectVectorIntoDestinationFrame or + * computeTransformedDirection. It provides simpler and more generic + * methods: {@link #applyTo(Vector3D) applyTo(Vector3D)} and {@link + * #applyInverseTo(Vector3D) applyInverseTo(Vector3D)}.

    + * + *

    Since a rotation is basically a vectorial operator, several rotations can be + * composed together and the composite operation r = r1 o + * r2 (which means that for each vector u, + * r(u) = r1(r2(u))) 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 r1 to r2 and the result + * we get is r = r1 o r2. For this purpose, the + * class provides the methods: {@link #applyTo(Rotation) applyTo(Rotation)} and + * {@link #applyInverseTo(Rotation) applyInverseTo(Rotation)}.

    + * + *

    Rotations are guaranteed to be immutable objects.

    + * + * @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. + *

    A rotation can be built from a normalized quaternion, + * i.e. a quaternion for which q02 + + * q12 + q22 + + * q32 = 1. If the quaternion is not normalized, + * the constructor can normalize it in a preprocessing step.

    + *

    Note that some conventions put the scalar part of the quaternion + * as the 4th component and the vector part as the first three + * components. This is not our convention. We put the scalar part + * as the first component.

    + * @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. + *

    + * Calling this constructor is equivalent to call + * {@link #Rotation(Vector3D, double, RotationConvention) + * new Rotation(axis, angle, RotationConvention.VECTOR_OPERATOR)} + *

    + * @param axis axis around which to rotate + * @param angle rotation angle. + * @exception MathIllegalArgumentException if the axis norm is zero + * @deprecated as of 3.6, replaced with {@link #Rotation(Vector3D, double, RotationConvention)} + */ + @Deprecated + public Rotation(Vector3D axis, double angle) throws MathIllegalArgumentException { + this(axis, angle, RotationConvention.VECTOR_OPERATOR); + } + + /** Build a rotation from an axis and an angle. + * @param axis axis around which to rotate + * @param angle rotation angle + * @param convention convention to use for the semantics of the angle + * @exception MathIllegalArgumentException if the axis norm is zero + * @since 3.6 + */ + public Rotation(final Vector3D axis, final double angle, final RotationConvention convention) + throws MathIllegalArgumentException { + + double norm = axis.getNorm(); + if (norm == 0) { + throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS); + } + + double halfAngle = convention == RotationConvention.VECTOR_OPERATOR ? -0.5 * angle : +0.5 * angle; + double coeff = FastMath.sin(halfAngle) / norm; + + q0 = FastMath.cos (halfAngle); + q1 = coeff * axis.getX(); + q2 = coeff * axis.getY(); + q3 = coeff * axis.getZ(); + + } + + /** Build a rotation from a 3X3 matrix. + + *

    Rotation matrices are orthogonal matrices, i.e. unit matrices + * (which are matrices for which m.mT = 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.

    + + *

    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.

    + + * @param m rotation matrix + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + + * @exception NotARotationMatrixException if the matrix is not a 3X3 + * matrix, or if it cannot be transformed into an orthogonal matrix + * with the given threshold, or if the determinant of the resulting + * orthogonal matrix is negative + + */ + public Rotation(double[][] m, double threshold) + throws NotARotationMatrixException { + + // dimension check + if ((m.length != 3) || (m[0].length != 3) || + (m[1].length != 3) || (m[2].length != 3)) { + throw new NotARotationMatrixException( + LocalizedFormats.ROTATION_MATRIX_DIMENSIONS, + m.length, m[0].length); + } + + // compute a "close" orthogonal matrix + double[][] ort = orthogonalizeMatrix(m, threshold); + + // check the sign of the determinant + double det = ort[0][0] * (ort[1][1] * ort[2][2] - ort[2][1] * ort[1][2]) - + ort[1][0] * (ort[0][1] * ort[2][2] - ort[2][1] * ort[0][2]) + + ort[2][0] * (ort[0][1] * ort[1][2] - ort[1][1] * ort[0][2]); + if (det < 0.0) { + throw new NotARotationMatrixException( + LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT, + det); + } + + double[] quat = mat2quat(ort); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build the rotation that transforms a pair of vectors into another pair. + + *

    Except for possible scale factors, if the instance were applied to + * the pair (u1, u2) it will produce the pair + * (v1, v2).

    + + *

    If the angular separation between u1 and u2 is + * not the same as the angular separation between v1 and + * v2, then a corrected v'2 will be used rather than + * v2, the corrected vector will be in the (±v1, + * +v2) half-plane.

    + + * @param u1 first vector of the origin pair + * @param u2 second vector of the origin pair + * @param v1 desired image of u1 by the rotation + * @param v2 desired image of u2 by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero, + * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear) + */ + public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2) + throws MathArithmeticException { + + // build orthonormalized base from u1, u2 + // this fails when vectors are null or collinear, which is forbidden to define a rotation + final Vector3D u3 = u1.crossProduct(u2).normalize(); + u2 = u3.crossProduct(u1).normalize(); + u1 = u1.normalize(); + + // build an orthonormalized base from v1, v2 + // this fails when vectors are null or collinear, which is forbidden to define a rotation + final Vector3D v3 = v1.crossProduct(v2).normalize(); + v2 = v3.crossProduct(v1).normalize(); + v1 = v1.normalize(); + + // buid a matrix transforming the first base into the second one + final double[][] m = new double[][] { + { + MathArrays.linearCombination(u1.getX(), v1.getX(), u2.getX(), v2.getX(), u3.getX(), v3.getX()), + MathArrays.linearCombination(u1.getY(), v1.getX(), u2.getY(), v2.getX(), u3.getY(), v3.getX()), + MathArrays.linearCombination(u1.getZ(), v1.getX(), u2.getZ(), v2.getX(), u3.getZ(), v3.getX()) + }, + { + MathArrays.linearCombination(u1.getX(), v1.getY(), u2.getX(), v2.getY(), u3.getX(), v3.getY()), + MathArrays.linearCombination(u1.getY(), v1.getY(), u2.getY(), v2.getY(), u3.getY(), v3.getY()), + MathArrays.linearCombination(u1.getZ(), v1.getY(), u2.getZ(), v2.getY(), u3.getZ(), v3.getY()) + }, + { + MathArrays.linearCombination(u1.getX(), v1.getZ(), u2.getX(), v2.getZ(), u3.getX(), v3.getZ()), + MathArrays.linearCombination(u1.getY(), v1.getZ(), u2.getY(), v2.getZ(), u3.getY(), v3.getZ()), + MathArrays.linearCombination(u1.getZ(), v1.getZ(), u2.getZ(), v2.getZ(), u3.getZ(), v3.getZ()) + } + }; + + double[] quat = mat2quat(m); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build one of the rotations that transform one vector into another one. + + *

    Except for a possible scale factor, if the instance were + * applied to the vector u it will produce the vector v. There is an + * infinite number of such rotations, this constructor choose the + * one with the smallest associated angle (i.e. the one whose axis + * is orthogonal to the (u, v) plane). If u and v are collinear, an + * arbitrary rotation axis is chosen.

    + + * @param u origin vector + * @param v desired image of u by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero + */ + public Rotation(Vector3D u, Vector3D v) throws MathArithmeticException { + + double normProduct = u.getNorm() * v.getNorm(); + if (normProduct == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR); + } + + double dot = u.dotProduct(v); + + if (dot < ((2.0e-15 - 1.0) * normProduct)) { + // special case u = -v: we select a PI angle rotation around + // an arbitrary vector orthogonal to u + Vector3D w = u.orthogonal(); + q0 = 0.0; + q1 = -w.getX(); + q2 = -w.getY(); + q3 = -w.getZ(); + } else { + // general case: (u, v) defines a plane, we select + // the shortest possible rotation: axis orthogonal to this plane + q0 = FastMath.sqrt(0.5 * (1.0 + dot / normProduct)); + double coeff = 1.0 / (2.0 * q0 * normProduct); + Vector3D q = v.crossProduct(u); + q1 = coeff * q.getX(); + q2 = coeff * q.getY(); + q3 = coeff * q.getZ(); + } + + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

    + * Calling this constructor is equivalent to call + * {@link #Rotation(RotationOrder, RotationConvention, double, double, double) + * new Rotation(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3)} + *

    + + * @param order order of rotations to use + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + * @deprecated as of 3.6, replaced with {@link + * #Rotation(RotationOrder, RotationConvention, double, double, double)} + */ + @Deprecated + public Rotation(RotationOrder order, + double alpha1, double alpha2, double alpha3) { + this(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3); + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

    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.

    + *

    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).

    + + * @param order order of rotations to compose, from left to right + * (i.e. we will use {@code r1.compose(r2.compose(r3, convention), convention)}) + * @param convention convention to use for the semantics of the angle + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + * @since 3.6 + */ + public Rotation(RotationOrder order, RotationConvention convention, + double alpha1, double alpha2, double alpha3) { + Rotation r1 = new Rotation(order.getA1(), alpha1, convention); + Rotation r2 = new Rotation(order.getA2(), alpha2, convention); + Rotation r3 = new Rotation(order.getA3(), alpha3, convention); + Rotation composed = r1.compose(r2.compose(r3, convention), convention); + q0 = composed.q0; + q1 = composed.q1; + q2 = composed.q2; + q3 = composed.q3; + } + + /** Convert an orthogonal rotation matrix to a quaternion. + * @param ort orthogonal rotation matrix + * @return quaternion corresponding to the matrix + */ + private static double[] mat2quat(final double[][] ort) { + + final double[] quat = new double[4]; + + // There are different ways to compute the quaternions elements + // from the matrix. They all involve computing one element from + // the diagonal of the matrix, and computing the three other ones + // using a formula involving a division by the first element, + // which unfortunately can be zero. Since the norm of the + // quaternion is 1, we know at least one element has an absolute + // value greater or equal to 0.5, so it is always possible to + // select the right formula and avoid division by zero and even + // numerical inaccuracy. Checking the elements in turn and using + // the first one greater than 0.45 is safe (this leads to a simple + // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) + double s = ort[0][0] + ort[1][1] + ort[2][2]; + if (s > -0.19) { + // compute q0 and deduce q1, q2 and q3 + quat[0] = 0.5 * FastMath.sqrt(s + 1.0); + double inv = 0.25 / quat[0]; + quat[1] = inv * (ort[1][2] - ort[2][1]); + quat[2] = inv * (ort[2][0] - ort[0][2]); + quat[3] = inv * (ort[0][1] - ort[1][0]); + } else { + s = ort[0][0] - ort[1][1] - ort[2][2]; + if (s > -0.19) { + // compute q1 and deduce q0, q2 and q3 + quat[1] = 0.5 * FastMath.sqrt(s + 1.0); + double inv = 0.25 / quat[1]; + quat[0] = inv * (ort[1][2] - ort[2][1]); + quat[2] = inv * (ort[0][1] + ort[1][0]); + quat[3] = inv * (ort[0][2] + ort[2][0]); + } else { + s = ort[1][1] - ort[0][0] - ort[2][2]; + if (s > -0.19) { + // compute q2 and deduce q0, q1 and q3 + quat[2] = 0.5 * FastMath.sqrt(s + 1.0); + double inv = 0.25 / quat[2]; + quat[0] = inv * (ort[2][0] - ort[0][2]); + quat[1] = inv * (ort[0][1] + ort[1][0]); + quat[3] = inv * (ort[2][1] + ort[1][2]); + } else { + // compute q3 and deduce q0, q1 and q2 + s = ort[2][2] - ort[0][0] - ort[1][1]; + quat[3] = 0.5 * FastMath.sqrt(s + 1.0); + double inv = 0.25 / quat[3]; + quat[0] = inv * (ort[0][1] - ort[1][0]); + quat[1] = inv * (ort[0][2] + ort[2][0]); + quat[2] = inv * (ort[2][1] + ort[1][2]); + } + } + } + + return quat; + + } + + /** Revert a rotation. + * Build a rotation which reverse the effect of another + * rotation. This means that if r(u) = v, then r.revert(v) = u. The + * instance is not changed. + * @return a new rotation whose effect is the reverse of the effect + * of the instance + */ + public Rotation revert() { + return new Rotation(-q0, q1, q2, q3, false); + } + + /** Get the scalar coordinate of the quaternion. + * @return scalar coordinate of the quaternion + */ + public double getQ0() { + return q0; + } + + /** Get the first coordinate of the vectorial part of the quaternion. + * @return first coordinate of the vectorial part of the quaternion + */ + public double getQ1() { + return q1; + } + + /** Get the second coordinate of the vectorial part of the quaternion. + * @return second coordinate of the vectorial part of the quaternion + */ + public double getQ2() { + return q2; + } + + /** Get the third coordinate of the vectorial part of the quaternion. + * @return third coordinate of the vectorial part of the quaternion + */ + public double getQ3() { + return q3; + } + + /** Get the normalized axis of the rotation. + *

    + * Calling this method is equivalent to call + * {@link #getAxis(RotationConvention) getAxis(RotationConvention.VECTOR_OPERATOR)} + *

    + * @return normalized axis of the rotation + * @see #Rotation(Vector3D, double, RotationConvention) + * @deprecated as of 3.6, replaced with {@link #getAxis(RotationConvention)} + */ + @Deprecated + public Vector3D getAxis() { + return getAxis(RotationConvention.VECTOR_OPERATOR); + } + + /** Get the normalized axis of the rotation. + *

    + * Note that as {@link #getAngle()} always returns an angle + * between 0 and π, changing the convention changes the + * direction of the axis, not the sign of the angle. + *

    + * @param convention convention to use for the semantics of the angle + * @return normalized axis of the rotation + * @see #Rotation(Vector3D, double, RotationConvention) + * @since 3.6 + */ + public Vector3D getAxis(final RotationConvention convention) { + final double squaredSine = q1 * q1 + q2 * q2 + q3 * q3; + if (squaredSine == 0) { + return convention == RotationConvention.VECTOR_OPERATOR ? Vector3D.PLUS_I : Vector3D.MINUS_I; + } else { + final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1; + if (q0 < 0) { + final double inverse = sgn / FastMath.sqrt(squaredSine); + return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); + } + final double inverse = -sgn / FastMath.sqrt(squaredSine); + return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); + } + } + + /** Get the angle of the rotation. + * @return angle of the rotation (between 0 and π) + * @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. + + *

    + * Calling this method is equivalent to call + * {@link #getAngles(RotationOrder, RotationConvention) + * getAngles(order, RotationConvention.VECTOR_OPERATOR)} + *

    + + * @param order rotation order to use + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)} + */ + @Deprecated + public double[] getAngles(RotationOrder order) + throws CardanEulerSingularityException { + return getAngles(order, RotationConvention.VECTOR_OPERATOR); + } + + /** Get the Cardan or Euler angles corresponding to the instance. + + *

    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 + * a1, a2 and a3 is the same as + * the rotation defined by the angles π + a1, π + * - a2 and π + a3. This method implements + * the following arbitrary choices:

    + *
      + *
    • 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),
    • + *
    • for Euler angles, the chosen set is the one for which the + * second angle is between 0 and π (i.e its sine is positive).
    • + *
    + + *

    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 + * nothing 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!

    + + * @param order rotation order to use + * @param convention convention to use for the semantics of the angle + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + * @since 3.6 + */ + public double[] getAngles(RotationOrder order, RotationConvention convention) + throws CardanEulerSingularityException { + + if (convention == RotationConvention.VECTOR_OPERATOR) { + if (order == RotationOrder.XYZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-(v1.getY()), v1.getZ()), + FastMath.asin(v2.getZ()), + FastMath.atan2(-(v2.getY()), v2.getX()) + }; + + } else if (order == RotationOrder.XZY) { + + // r (Vector3D.plusJ) coordinates are : + // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v1.getZ(), v1.getY()), + -FastMath.asin(v2.getY()), + FastMath.atan2(v2.getZ(), v2.getX()) + }; + + } else if (order == RotationOrder.YXZ) { + + // r (Vector3D.plusK) coordinates are : + // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v1.getX(), v1.getZ()), + -FastMath.asin(v2.getZ()), + FastMath.atan2(v2.getX(), v2.getY()) + }; + + } else if (order == RotationOrder.YZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-(v1.getZ()), v1.getX()), + FastMath.asin(v2.getX()), + FastMath.atan2(-(v2.getZ()), v2.getY()) + }; + + } else if (order == RotationOrder.ZXY) { + + // r (Vector3D.plusJ) coordinates are : + // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-(v1.getX()), v1.getY()), + FastMath.asin(v2.getY()), + FastMath.atan2(-(v2.getX()), v2.getZ()) + }; + + } else if (order == RotationOrder.ZYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v1.getY(), v1.getX()), + -FastMath.asin(v2.getX()), + FastMath.atan2(v2.getY(), v2.getZ()) + }; + + } else if (order == RotationOrder.XYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2) + // and we can choose to have theta in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getY(), -v1.getZ()), + FastMath.acos(v2.getX()), + FastMath.atan2(v2.getY(), v2.getZ()) + }; + + } else if (order == RotationOrder.XZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2) + // and we can choose to have psi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getZ(), v1.getY()), + FastMath.acos(v2.getX()), + FastMath.atan2(v2.getZ(), -v2.getY()) + }; + + } else if (order == RotationOrder.YXY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // and we can choose to have phi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getX(), v1.getZ()), + FastMath.acos(v2.getY()), + FastMath.atan2(v2.getX(), -v2.getZ()) + }; + + } else if (order == RotationOrder.YZY) { + + // r (Vector3D.plusJ) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // and we can choose to have psi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getZ(), -v1.getX()), + FastMath.acos(v2.getY()), + FastMath.atan2(v2.getZ(), v2.getX()) + }; + + } else if (order == RotationOrder.ZXZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getX(), -v1.getY()), + FastMath.acos(v2.getZ()), + FastMath.atan2(v2.getX(), v2.getY()) + }; + + } else { // last possibility is ZYZ + + // r (Vector3D.plusK) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v1.getY(), v1.getX()), + FastMath.acos(v2.getZ()), + FastMath.atan2(v2.getY(), -v2.getX()) + }; + + } + } else { + if (order == RotationOrder.XYZ) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), -cos (theta) sin (psi), sin (theta) + // (-r) (Vector3D.plusK) coordinates are : + // sin (theta), -sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-v2.getY(), v2.getZ()), + FastMath.asin(v2.getX()), + FastMath.atan2(-v1.getY(), v1.getX()) + }; + + } else if (order == RotationOrder.XZY) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), -sin (psi), cos (psi) sin (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // -sin (psi), cos (phi) cos (psi), sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v2.getZ(), v2.getY()), + -FastMath.asin(v2.getX()), + FastMath.atan2(v1.getZ(), v1.getX()) + }; + + } else if (order == RotationOrder.YXZ) { + + // r (Vector3D.plusJ) coordinates are : + // cos (phi) sin (psi), cos (phi) cos (psi), -sin (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (theta) cos (phi), -sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v2.getX(), v2.getZ()), + -FastMath.asin(v2.getY()), + FastMath.atan2(v1.getX(), v1.getY()) + }; + + } else if (order == RotationOrder.YZX) { + + // r (Vector3D.plusJ) coordinates are : + // sin (psi), cos (psi) cos (phi), -cos (psi) sin (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), sin (psi), -sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-v2.getZ(), v2.getX()), + FastMath.asin(v2.getY()), + FastMath.atan2(-v1.getZ(), v1.getY()) + }; + + } else if (order == RotationOrder.ZXY) { + + // r (Vector3D.plusK) coordinates are : + // -cos (phi) sin (theta), sin (phi), cos (phi) cos (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // -sin (psi) cos (phi), cos (psi) cos (phi), sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(-v2.getX(), v2.getY()), + FastMath.asin(v2.getZ()), + FastMath.atan2(-v1.getX(), v1.getZ()) + }; + + } else if (order == RotationOrder.ZYX) { + + // r (Vector3D.plusK) coordinates are : + // -sin (theta), cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), sin (psi) cos (theta), -sin (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new double[] { + FastMath.atan2(v2.getY(), v2.getX()), + -FastMath.asin(v2.getZ()), + FastMath.atan2(v1.getY(), v1.getZ()) + }; + + } else if (order == RotationOrder.XYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta), sin (phi2) sin (theta), cos (phi2) sin (theta) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta), sin (theta) sin (phi1), -sin (theta) cos (phi1) + // and we can choose to have theta in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getY(), -v2.getZ()), + FastMath.acos(v2.getX()), + FastMath.atan2(v1.getY(), v1.getZ()) + }; + + } else if (order == RotationOrder.XZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi), -cos (phi2) sin (psi), sin (phi2) sin (psi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi), sin (psi) cos (phi1), sin (psi) sin (phi1) + // and we can choose to have psi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_I); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); + if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getZ(), v2.getY()), + FastMath.acos(v2.getX()), + FastMath.atan2(v1.getZ(), -v1.getY()) + }; + + } else if (order == RotationOrder.YXY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // and we can choose to have phi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getX(), v2.getZ()), + FastMath.acos(v2.getY()), + FastMath.atan2(v1.getX(), -v1.getZ()) + }; + + } else if (order == RotationOrder.YZY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // (-r) (Vector3D.plusJ) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // and we can choose to have psi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_J); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); + if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getZ(), -v2.getX()), + FastMath.acos(v2.getY()), + FastMath.atan2(v1.getZ(), v1.getX()) + }; + + } else if (order == RotationOrder.ZXZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getX(), -v2.getY()), + FastMath.acos(v2.getZ()), + FastMath.atan2(v1.getX(), v1.getY()) + }; + + } else { // last possibility is ZYZ + + // r (Vector3D.plusK) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // (-r) (Vector3D.plusK) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + Vector3D v1 = applyTo(Vector3D.PLUS_K); + Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); + if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new double[] { + FastMath.atan2(v2.getY(), v2.getX()), + FastMath.acos(v2.getZ()), + FastMath.atan2(v1.getY(), -v1.getX()) + }; + + } + } + + } + + /** Get the 3X3 matrix corresponding to the instance + * @return the matrix corresponding to the instance + */ + public double[][] getMatrix() { + + // products + double q0q0 = q0 * q0; + double q0q1 = q0 * q1; + double q0q2 = q0 * q2; + double q0q3 = q0 * q3; + double q1q1 = q1 * q1; + double q1q2 = q1 * q2; + double q1q3 = q1 * q3; + double q2q2 = q2 * q2; + double q2q3 = q2 * q3; + double q3q3 = q3 * q3; + + // create the matrix + double[][] m = new double[3][]; + m[0] = new double[3]; + m[1] = new double[3]; + m[2] = new double[3]; + + m [0][0] = 2.0 * (q0q0 + q1q1) - 1.0; + m [1][0] = 2.0 * (q1q2 - q0q3); + m [2][0] = 2.0 * (q1q3 + q0q2); + + m [0][1] = 2.0 * (q1q2 + q0q3); + m [1][1] = 2.0 * (q0q0 + q2q2) - 1.0; + m [2][1] = 2.0 * (q2q3 - q0q1); + + m [0][2] = 2.0 * (q1q3 - q0q2); + m [1][2] = 2.0 * (q2q3 + q0q1); + m [2][2] = 2.0 * (q0q0 + q3q3) - 1.0; + + return m; + + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public Vector3D applyTo(Vector3D u) { + + double x = u.getX(); + double y = u.getY(); + double z = u.getZ(); + + double s = q1 * x + q2 * y + q3 * z; + + return new Vector3D(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x, + 2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y, + 2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z); + + } + + /** Apply the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyTo(final double[] in, final double[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final double s = q1 * x + q2 * y + q3 * z; + + out[0] = 2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x; + out[1] = 2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y; + out[2] = 2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z; + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public Vector3D applyInverseTo(Vector3D u) { + + double x = u.getX(); + double y = u.getY(); + double z = u.getZ(); + + double s = q1 * x + q2 * y + q3 * z; + double m0 = -q0; + + return new Vector3D(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x, + 2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y, + 2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z); + + } + + /** Apply the inverse of the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyInverseTo(final double[] in, final double[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final double s = q1 * x + q2 * y + q3 * z; + final double m0 = -q0; + + out[0] = 2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x; + out[1] = 2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y; + out[2] = 2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z; + + } + + /** Apply the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #compose(Rotation, RotationConvention) + * compose(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public Rotation applyTo(Rotation r) { + return compose(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let {@code u} be any + * vector and {@code v} its image by {@code r1} (i.e. + * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by + * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then + * {@code w = comp.applyTo(u)}, where + * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the instance + */ + public Rotation compose(final Rotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInternal(r) : r.composeInternal(this); + } + + /** Compose the instance with another rotation using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + * using vector operator convention + */ + private Rotation composeInternal(final Rotation r) { + return new Rotation(r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), + r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), + r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), + r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1), + false); + } + + /** Apply the inverse of the instance to another rotation. + *

    + * Calling this method is equivalent to call + * {@link #composeInverse(Rotation, RotationConvention) + * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}. + *

    + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public Rotation applyInverseTo(Rotation r) { + return composeInverse(r, RotationConvention.VECTOR_OPERATOR); + } + + /** Compose the inverse of the instance with another rotation. + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention, + * applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let {@code u} be any vector and {@code v} its image by {@code r1} + * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image + * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}). + * Then {@code w = comp.applyTo(u)}, where + * {@code comp = r2.composeInverse(r1)}. + *

    + *

    + * If the semantics of the rotations composition corresponds to a + * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention, + * the application order will be reversed, which means it is the + * innermost rotation that will be reversed. So keeping the exact same + * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w} + * and {@code comp} as above, {@code comp} could also be computed as + * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}. + *

    + * @param r rotation to apply the rotation to + * @param convention convention to use for the semantics of the angle + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public Rotation composeInverse(final Rotation r, final RotationConvention convention) { + return convention == RotationConvention.VECTOR_OPERATOR ? + composeInverseInternal(r) : r.composeInternal(revert()); + } + + /** Compose the inverse of the instance with another rotation + * using vector operator convention. + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance using vector operator convention + */ + private Rotation composeInverseInternal(Rotation r) { + return new Rotation(-r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), + -r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), + -r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), + -r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1), + false); + } + + /** Perfect orthogonality on a 3X3 matrix. + * @param m initial matrix (not exactly orthogonal) + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + * @return an orthogonal matrix close to m + * @exception NotARotationMatrixException if the matrix cannot be + * orthogonalized with the given threshold after 10 iterations + */ + private double[][] orthogonalizeMatrix(double[][] m, double threshold) + throws NotARotationMatrixException { + double[] m0 = m[0]; + double[] m1 = m[1]; + double[] m2 = m[2]; + double x00 = m0[0]; + double x01 = m0[1]; + double x02 = m0[2]; + double x10 = m1[0]; + double x11 = m1[1]; + double x12 = m1[2]; + double x20 = m2[0]; + double x21 = m2[1]; + double x22 = m2[2]; + double fn = 0; + double fn1; + + double[][] o = new double[3][3]; + double[] o0 = o[0]; + double[] o1 = o[1]; + double[] o2 = o[2]; + + // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M) + int i = 0; + while (++i < 11) { + + // Mt.Xn + double mx00 = m0[0] * x00 + m1[0] * x10 + m2[0] * x20; + double mx10 = m0[1] * x00 + m1[1] * x10 + m2[1] * x20; + double mx20 = m0[2] * x00 + m1[2] * x10 + m2[2] * x20; + double mx01 = m0[0] * x01 + m1[0] * x11 + m2[0] * x21; + double mx11 = m0[1] * x01 + m1[1] * x11 + m2[1] * x21; + double mx21 = m0[2] * x01 + m1[2] * x11 + m2[2] * x21; + double mx02 = m0[0] * x02 + m1[0] * x12 + m2[0] * x22; + double mx12 = m0[1] * x02 + m1[1] * x12 + m2[1] * x22; + double mx22 = m0[2] * x02 + m1[2] * x12 + m2[2] * x22; + + // Xn+1 + o0[0] = x00 - 0.5 * (x00 * mx00 + x01 * mx10 + x02 * mx20 - m0[0]); + o0[1] = x01 - 0.5 * (x00 * mx01 + x01 * mx11 + x02 * mx21 - m0[1]); + o0[2] = x02 - 0.5 * (x00 * mx02 + x01 * mx12 + x02 * mx22 - m0[2]); + o1[0] = x10 - 0.5 * (x10 * mx00 + x11 * mx10 + x12 * mx20 - m1[0]); + o1[1] = x11 - 0.5 * (x10 * mx01 + x11 * mx11 + x12 * mx21 - m1[1]); + o1[2] = x12 - 0.5 * (x10 * mx02 + x11 * mx12 + x12 * mx22 - m1[2]); + o2[0] = x20 - 0.5 * (x20 * mx00 + x21 * mx10 + x22 * mx20 - m2[0]); + o2[1] = x21 - 0.5 * (x20 * mx01 + x21 * mx11 + x22 * mx21 - m2[1]); + o2[2] = x22 - 0.5 * (x20 * mx02 + x21 * mx12 + x22 * mx22 - m2[2]); + + // correction on each elements + double corr00 = o0[0] - m0[0]; + double corr01 = o0[1] - m0[1]; + double corr02 = o0[2] - m0[2]; + double corr10 = o1[0] - m1[0]; + double corr11 = o1[1] - m1[1]; + double corr12 = o1[2] - m1[2]; + double corr20 = o2[0] - m2[0]; + double corr21 = o2[1] - m2[1]; + double corr22 = o2[2] - m2[2]; + + // Frobenius norm of the correction + fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 + + corr10 * corr10 + corr11 * corr11 + corr12 * corr12 + + corr20 * corr20 + corr21 * corr21 + corr22 * corr22; + + // convergence test + if (FastMath.abs(fn1 - fn) <= threshold) { + return o; + } + + // prepare next iteration + x00 = o0[0]; + x01 = o0[1]; + x02 = o0[2]; + x10 = o1[0]; + x11 = o1[1]; + x12 = o1[2]; + x20 = o2[0]; + x21 = o2[1]; + x22 = o2[2]; + fn = fn1; + + } + + // the algorithm did not converge after 10 iterations + throw new NotARotationMatrixException( + LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX, + i - 1); + } + + /** Compute the distance between two rotations. + *

    The distance 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:

    + *
    +   *        r1(r) = r2
    +   * 
    + *

    This distance is an angle between 0 and π. Its value is the smallest + * possible upper bound of the angle in radians between r1(v) + * and r2(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.

    + *

    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).

    + * @param r1 first rotation + * @param r2 second rotation + * @return distance between r1 and r2 + */ + public static double distance(Rotation r1, Rotation r2) { + return r1.composeInverseInternal(r2).getAngle(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java new file mode 100644 index 000000000..b5bea3f57 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +/** + * This enumerates is used to differentiate the semantics of a rotation. + * @see Rotation + * @since 3.6 + */ +public enum RotationConvention { + + /** Constant for rotation that have the semantics of a vector operator. + *

    + * According to this convention, the rotation moves vectors with respect + * to a fixed reference frame. + *

    + *

    + * This means that if we define rotation r is a 90 degrees rotation around + * the Z axis, the image of vector {@link Vector3D#PLUS_I} would be + * {@link Vector3D#PLUS_J}, the image of vector {@link Vector3D#PLUS_J} + * would be {@link Vector3D#MINUS_I}, the image of vector {@link Vector3D#PLUS_K} + * would be {@link Vector3D#PLUS_K}, and the image of vector with coordinates (1, 2, 3) + * would be vector (-2, 1, 3). This means that the vector rotates counterclockwise. + *

    + *

    + * This convention was the only one supported by Apache Commons Math up to version 3.5. + *

    + *

    + * The difference with {@link #FRAME_TRANSFORM} is only the semantics of the sign + * of the angle. It is always possible to create or use a rotation using either + * convention to really represent a rotation that would have been best created or + * used with the other convention, by changing accordingly the sign of the + * rotation angle. This is how things were done up to version 3.5. + *

    + */ + VECTOR_OPERATOR, + + /** Constant for rotation that have the semantics of a frame conversion. + *

    + * According to this convention, the rotation considered vectors to be fixed, + * but their coordinates change as they are converted from an initial frame to + * a destination frame rotated with respect to the initial frame. + *

    + *

    + * This means that if we define rotation r is a 90 degrees rotation around + * the Z axis, the image of vector {@link Vector3D#PLUS_I} would be + * {@link Vector3D#MINUS_J}, the image of vector {@link Vector3D#PLUS_J} + * would be {@link Vector3D#PLUS_I}, the image of vector {@link Vector3D#PLUS_K} + * would be {@link Vector3D#PLUS_K}, and the image of vector with coordinates (1, 2, 3) + * would be vector (2, -1, 3). This means that the coordinates of the vector rotates + * clockwise, because they are expressed with respect to a destination frame that is rotated + * counterclockwise. + *

    + *

    + * The difference with {@link #VECTOR_OPERATOR} is only the semantics of the sign + * of the angle. It is always possible to create or use a rotation using either + * convention to really represent a rotation that would have been best created or + * used with the other convention, by changing accordingly the sign of the + * rotation angle. This is how things were done up to version 3.5. + *

    + */ + FRAME_TRANSFORM; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java new file mode 100644 index 000000000..474b43b50 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +/** + * This class is a utility representing a rotation order specification + * for Cardan or Euler angles specification. + * + * This class cannot be instanciated by the user. He can only use one + * of the twelve predefined supported orders as an argument to either + * the {@link Rotation#Rotation(RotationOrder,double,double,double)} + * constructor or the {@link Rotation#getAngles} method. + * + * @since 1.2 + */ +public final class RotationOrder { + + /** Set of Cardan angles. + * this ordered set of rotations is around X, then around Y, then + * around Z + */ + public static final RotationOrder XYZ = + new RotationOrder("XYZ", Vector3D.PLUS_I, Vector3D.PLUS_J, Vector3D.PLUS_K); + + /** Set of Cardan angles. + * this ordered set of rotations is around X, then around Z, then + * around Y + */ + public static final RotationOrder XZY = + new RotationOrder("XZY", Vector3D.PLUS_I, Vector3D.PLUS_K, Vector3D.PLUS_J); + + /** Set of Cardan angles. + * this ordered set of rotations is around Y, then around X, then + * around Z + */ + public static final RotationOrder YXZ = + new RotationOrder("YXZ", Vector3D.PLUS_J, Vector3D.PLUS_I, Vector3D.PLUS_K); + + /** Set of Cardan angles. + * this ordered set of rotations is around Y, then around Z, then + * around X + */ + public static final RotationOrder YZX = + new RotationOrder("YZX", Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_I); + + /** Set of Cardan angles. + * this ordered set of rotations is around Z, then around X, then + * around Y + */ + public static final RotationOrder ZXY = + new RotationOrder("ZXY", Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_J); + + /** Set of Cardan angles. + * this ordered set of rotations is around Z, then around Y, then + * around X + */ + public static final RotationOrder ZYX = + new RotationOrder("ZYX", Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_I); + + /** Set of Euler angles. + * this ordered set of rotations is around X, then around Y, then + * around X + */ + public static final RotationOrder XYX = + new RotationOrder("XYX", Vector3D.PLUS_I, Vector3D.PLUS_J, Vector3D.PLUS_I); + + /** Set of Euler angles. + * this ordered set of rotations is around X, then around Z, then + * around X + */ + public static final RotationOrder XZX = + new RotationOrder("XZX", Vector3D.PLUS_I, Vector3D.PLUS_K, Vector3D.PLUS_I); + + /** Set of Euler angles. + * this ordered set of rotations is around Y, then around X, then + * around Y + */ + public static final RotationOrder YXY = + new RotationOrder("YXY", Vector3D.PLUS_J, Vector3D.PLUS_I, Vector3D.PLUS_J); + + /** Set of Euler angles. + * this ordered set of rotations is around Y, then around Z, then + * around Y + */ + public static final RotationOrder YZY = + new RotationOrder("YZY", Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_J); + + /** Set of Euler angles. + * this ordered set of rotations is around Z, then around X, then + * around Z + */ + public static final RotationOrder ZXZ = + new RotationOrder("ZXZ", Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_K); + + /** Set of Euler angles. + * this ordered set of rotations is around Z, then around Y, then + * around Z + */ + public static final RotationOrder ZYZ = + new RotationOrder("ZYZ", Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_K); + + /** Name of the rotations order. */ + private final String name; + + /** Axis of the first rotation. */ + private final Vector3D a1; + + /** Axis of the second rotation. */ + private final Vector3D a2; + + /** Axis of the third rotation. */ + private final Vector3D a3; + + /** Private constructor. + * This is a utility class that cannot be instantiated by the user, + * so its only constructor is private. + * @param name name of the rotation order + * @param a1 axis of the first rotation + * @param a2 axis of the second rotation + * @param a3 axis of the third rotation + */ + private RotationOrder(final String name, + final Vector3D a1, final Vector3D a2, final Vector3D a3) { + this.name = name; + this.a1 = a1; + this.a2 = a2; + this.a3 = a3; + } + + /** Get a string representation of the instance. + * @return a string representation of the instance (in fact, its name) + */ + @Override + public String toString() { + return name; + } + + /** Get the axis of the first rotation. + * @return axis of the first rotation + */ + public Vector3D getA1() { + return a1; + } + + /** Get the axis of the second rotation. + * @return axis of the second rotation + */ + public Vector3D getA2() { + return a2; + } + + /** Get the axis of the second rotation. + * @return axis of the second rotation + */ + public Vector3D getA3() { + return a3; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Segment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Segment.java new file mode 100644 index 000000000..575f88e5f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Segment.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + + +/** Simple container for a two-points segment. + * @since 3.0 + */ +public class Segment { + + /** Start point of the segment. */ + private final Vector3D start; + + /** End point of the segments. */ + private final Vector3D end; + + /** Line containing the segment. */ + private final Line line; + + /** Build a segment. + * @param start start point of the segment + * @param end end point of the segment + * @param line line containing the segment + */ + public Segment(final Vector3D start, final Vector3D end, final Line line) { + this.start = start; + this.end = end; + this.line = line; + } + + /** Get the start point of the segment. + * @return start point of the segment + */ + public Vector3D getStart() { + return start; + } + + /** Get the end point of the segment. + * @return end point of the segment + */ + public Vector3D getEnd() { + return end; + } + + /** Get the line containing the segment. + * @return line containing the segment + */ + public Line getLine() { + return line; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java new file mode 100644 index 000000000..01fa1efc2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.geometry.enclosing.EnclosingBall; +import com.fr.third.org.apache.commons.math3.geometry.enclosing.SupportBallGenerator; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.DiskGenerator; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Class generating an enclosing ball from its support points. + * @since 3.3 + */ +public class SphereGenerator implements SupportBallGenerator { + + /** {@inheritDoc} */ + public EnclosingBall ballOnSupport(final List support) { + + if (support.size() < 1) { + return new EnclosingBall(Vector3D.ZERO, Double.NEGATIVE_INFINITY); + } else { + final Vector3D vA = support.get(0); + if (support.size() < 2) { + return new EnclosingBall(vA, 0, vA); + } else { + final Vector3D vB = support.get(1); + if (support.size() < 3) { + return new EnclosingBall(new Vector3D(0.5, vA, 0.5, vB), + 0.5 * vA.distance(vB), + vA, vB); + } else { + final Vector3D vC = support.get(2); + if (support.size() < 4) { + + // delegate to 2D disk generator + final Plane p = new Plane(vA, vB, vC, + 1.0e-10 * (vA.getNorm1() + vB.getNorm1() + vC.getNorm1())); + final EnclosingBall disk = + new DiskGenerator().ballOnSupport(Arrays.asList(p.toSubSpace(vA), + p.toSubSpace(vB), + p.toSubSpace(vC))); + + // convert back to 3D + return new EnclosingBall(p.toSpace(disk.getCenter()), + disk.getRadius(), vA, vB, vC); + + } else { + final Vector3D vD = support.get(3); + // a sphere is 3D can be defined as: + // (1) (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2 + // which can be written: + // (2) (x^2 + y^2 + z^2) - 2 x_0 x - 2 y_0 y - 2 z_0 z + (x_0^2 + y_0^2 + z_0^2 - r^2) = 0 + // or simply: + // (3) (x^2 + y^2 + z^2) + a x + b y + c z + d = 0 + // with sphere center coordinates -a/2, -b/2, -c/2 + // If the sphere exists, a b, c and d are a non zero solution to + // [ (x^2 + y^2 + z^2) x y z 1 ] [ 1 ] [ 0 ] + // [ (xA^2 + yA^2 + zA^2) xA yA zA 1 ] [ a ] [ 0 ] + // [ (xB^2 + yB^2 + zB^2) xB yB zB 1 ] * [ b ] = [ 0 ] + // [ (xC^2 + yC^2 + zC^2) xC yC zC 1 ] [ c ] [ 0 ] + // [ (xD^2 + yD^2 + zD^2) xD yD zD 1 ] [ d ] [ 0 ] + // So the determinant of the matrix is zero. Computing this determinant + // by expanding it using the minors m_ij of first row leads to + // (4) m_11 (x^2 + y^2 + z^2) - m_12 x + m_13 y - m_14 z + m_15 = 0 + // So by identifying equations (2) and (4) we get the coordinates + // of center as: + // x_0 = +m_12 / (2 m_11) + // y_0 = -m_13 / (2 m_11) + // z_0 = +m_14 / (2 m_11) + // Note that the minors m_11, m_12, m_13 and m_14 all have the last column + // filled with 1.0, hence simplifying the computation + final BigFraction[] c2 = new BigFraction[] { + new BigFraction(vA.getX()), new BigFraction(vB.getX()), + new BigFraction(vC.getX()), new BigFraction(vD.getX()) + }; + final BigFraction[] c3 = new BigFraction[] { + new BigFraction(vA.getY()), new BigFraction(vB.getY()), + new BigFraction(vC.getY()), new BigFraction(vD.getY()) + }; + final BigFraction[] c4 = new BigFraction[] { + new BigFraction(vA.getZ()), new BigFraction(vB.getZ()), + new BigFraction(vC.getZ()), new BigFraction(vD.getZ()) + }; + final BigFraction[] c1 = new BigFraction[] { + c2[0].multiply(c2[0]).add(c3[0].multiply(c3[0])).add(c4[0].multiply(c4[0])), + c2[1].multiply(c2[1]).add(c3[1].multiply(c3[1])).add(c4[1].multiply(c4[1])), + c2[2].multiply(c2[2]).add(c3[2].multiply(c3[2])).add(c4[2].multiply(c4[2])), + c2[3].multiply(c2[3]).add(c3[3].multiply(c3[3])).add(c4[3].multiply(c4[3])) + }; + final BigFraction twoM11 = minor(c2, c3, c4).multiply(2); + final BigFraction m12 = minor(c1, c3, c4); + final BigFraction m13 = minor(c1, c2, c4); + final BigFraction m14 = minor(c1, c2, c3); + final BigFraction centerX = m12.divide(twoM11); + final BigFraction centerY = m13.divide(twoM11).negate(); + final BigFraction centerZ = m14.divide(twoM11); + final BigFraction dx = c2[0].subtract(centerX); + final BigFraction dy = c3[0].subtract(centerY); + final BigFraction dz = c4[0].subtract(centerZ); + final BigFraction r2 = dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)); + return new EnclosingBall(new Vector3D(centerX.doubleValue(), + centerY.doubleValue(), + centerZ.doubleValue()), + FastMath.sqrt(r2.doubleValue()), + vA, vB, vC, vD); + } + } + } + } + } + + /** Compute a dimension 4 minor, when 4th column is known to be filled with 1.0. + * @param c1 first column + * @param c2 second column + * @param c3 third column + * @return value of the minor computed has an exact fraction + */ + private BigFraction minor(final BigFraction[] c1, final BigFraction[] c2, final BigFraction[] c3) { + return c2[0].multiply(c3[1]).multiply(c1[2].subtract(c1[3])). + add(c2[0].multiply(c3[2]).multiply(c1[3].subtract(c1[1]))). + add(c2[0].multiply(c3[3]).multiply(c1[1].subtract(c1[2]))). + add(c2[1].multiply(c3[0]).multiply(c1[3].subtract(c1[2]))). + add(c2[1].multiply(c3[2]).multiply(c1[0].subtract(c1[3]))). + add(c2[1].multiply(c3[3]).multiply(c1[2].subtract(c1[0]))). + add(c2[2].multiply(c3[0]).multiply(c1[1].subtract(c1[3]))). + add(c2[2].multiply(c3[1]).multiply(c1[3].subtract(c1[0]))). + add(c2[2].multiply(c3[3]).multiply(c1[0].subtract(c1[1]))). + add(c2[3].multiply(c3[0]).multiply(c1[2].subtract(c1[1]))). + add(c2[3].multiply(c3[1]).multiply(c1[0].subtract(c1[2]))). + add(c2[3].multiply(c3[2]).multiply(c1[1].subtract(c1[0]))); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java new file mode 100644 index 000000000..b2ee16f94 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java @@ -0,0 +1,395 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class provides conversions related to spherical coordinates. + *

    + * The conventions used here are the mathematical ones, i.e. spherical coordinates are + * related to Cartesian coordinates as follows: + *

    + *
      + *
    • x = r cos(θ) sin(Φ)
    • + *
    • y = r sin(θ) sin(Φ)
    • + *
    • z = r cos(Φ)
    • + *
    + *
      + *
    • r = √(x2+y2+z2)
    • + *
    • θ = atan2(y, x)
    • + *
    • Φ = acos(z/r)
    • + *
    + *

    + * r is the radius, θ is the azimuthal angle in the x-y plane and Φ is the polar + * (co-latitude) angle. These conventions are different from the conventions used + * in physics (and in particular in spherical harmonics) where the meanings of θ and + * Φ are reversed. + *

    + *

    + * This class provides conversion of coordinates and also of gradient and Hessian + * between spherical and Cartesian coordinates. + *

    + * @since 3.2 + */ +public class SphericalCoordinates implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20130206L; + + /** Cartesian coordinates. */ + private final Vector3D v; + + /** Radius. */ + private final double r; + + /** Azimuthal angle in the x-y plane θ. */ + private final double theta; + + /** Polar angle (co-latitude) Φ. */ + private final double phi; + + /** Jacobian of (r, θ &Phi). */ + private double[][] jacobian; + + /** Hessian of radius. */ + private double[][] rHessian; + + /** Hessian of azimuthal angle in the x-y plane θ. */ + private double[][] thetaHessian; + + /** Hessian of polar (co-latitude) angle Φ. */ + private double[][] phiHessian; + + /** Build a spherical coordinates transformer from Cartesian coordinates. + * @param v Cartesian coordinates + */ + public SphericalCoordinates(final Vector3D v) { + + // Cartesian coordinates + this.v = v; + + // remaining spherical coordinates + this.r = v.getNorm(); + this.theta = v.getAlpha(); + this.phi = FastMath.acos(v.getZ() / r); + + } + + /** Build a spherical coordinates transformer from spherical coordinates. + * @param r radius + * @param theta azimuthal angle in x-y plane + * @param phi polar (co-latitude) angle + */ + public SphericalCoordinates(final double r, final double theta, final double phi) { + + final double cosTheta = FastMath.cos(theta); + final double sinTheta = FastMath.sin(theta); + final double cosPhi = FastMath.cos(phi); + final double sinPhi = FastMath.sin(phi); + + // spherical coordinates + this.r = r; + this.theta = theta; + this.phi = phi; + + // Cartesian coordinates + this.v = new Vector3D(r * cosTheta * sinPhi, + r * sinTheta * sinPhi, + r * cosPhi); + + } + + /** Get the Cartesian coordinates. + * @return Cartesian coordinates + */ + public Vector3D getCartesian() { + return v; + } + + /** Get the radius. + * @return radius r + * @see #getTheta() + * @see #getPhi() + */ + public double getR() { + return r; + } + + /** Get the azimuthal angle in x-y plane. + * @return azimuthal angle in x-y plane θ + * @see #getR() + * @see #getPhi() + */ + public double getTheta() { + return theta; + } + + /** Get the polar (co-latitude) angle. + * @return polar (co-latitude) angle Φ + * @see #getR() + * @see #getTheta() + */ + public double getPhi() { + return phi; + } + + /** Convert a gradient with respect to spherical coordinates into a gradient + * with respect to Cartesian coordinates. + * @param sGradient gradient with respect to spherical coordinates + * {df/dr, df/dθ, df/dΦ} + * @return gradient with respect to Cartesian coordinates + * {df/dx, df/dy, df/dz} + */ + public double[] toCartesianGradient(final double[] sGradient) { + + // lazy evaluation of Jacobian + computeJacobian(); + + // compose derivatives as gradient^T . J + // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0 + return new double[] { + sGradient[0] * jacobian[0][0] + sGradient[1] * jacobian[1][0] + sGradient[2] * jacobian[2][0], + sGradient[0] * jacobian[0][1] + sGradient[1] * jacobian[1][1] + sGradient[2] * jacobian[2][1], + sGradient[0] * jacobian[0][2] + sGradient[2] * jacobian[2][2] + }; + + } + + /** Convert a Hessian with respect to spherical coordinates into a Hessian + * with respect to Cartesian coordinates. + *

    + * As Hessian are always symmetric, we use only the lower left part of the provided + * spherical Hessian, so the upper part may not be initialized. However, we still + * do fill up the complete array we create, with guaranteed symmetry. + *

    + * @param sHessian Hessian with respect to spherical coordinates + * {{d2f/dr2, d2f/drdθ, d2f/drdΦ}, + * {d2f/drdθ, d2f/dθ2, d2f/dθdΦ}, + * {d2f/drdΦ, d2f/dθdΦ, d2f/dΦ2} + * @param sGradient gradient with respect to spherical coordinates + * {df/dr, df/dθ, df/dΦ} + * @return Hessian with respect to Cartesian coordinates + * {{d2f/dx2, d2f/dxdy, d2f/dxdz}, + * {d2f/dxdy, d2f/dy2, d2f/dydz}, + * {d2f/dxdz, d2f/dydz, d2f/dz2}} + */ + public double[][] toCartesianHessian(final double[][] sHessian, final double[] sGradient) { + + computeJacobian(); + computeHessians(); + + // compose derivative as J^T . H_f . J + df/dr H_r + df/dtheta H_theta + df/dphi H_phi + // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0 + // and H_theta is only a 2x2 matrix as it does not depend on z + final double[][] hj = new double[3][3]; + final double[][] cHessian = new double[3][3]; + + // compute H_f . J + // beware we use ONLY the lower-left part of sHessian + hj[0][0] = sHessian[0][0] * jacobian[0][0] + sHessian[1][0] * jacobian[1][0] + sHessian[2][0] * jacobian[2][0]; + hj[0][1] = sHessian[0][0] * jacobian[0][1] + sHessian[1][0] * jacobian[1][1] + sHessian[2][0] * jacobian[2][1]; + hj[0][2] = sHessian[0][0] * jacobian[0][2] + sHessian[2][0] * jacobian[2][2]; + hj[1][0] = sHessian[1][0] * jacobian[0][0] + sHessian[1][1] * jacobian[1][0] + sHessian[2][1] * jacobian[2][0]; + hj[1][1] = sHessian[1][0] * jacobian[0][1] + sHessian[1][1] * jacobian[1][1] + sHessian[2][1] * jacobian[2][1]; + // don't compute hj[1][2] as it is not used below + hj[2][0] = sHessian[2][0] * jacobian[0][0] + sHessian[2][1] * jacobian[1][0] + sHessian[2][2] * jacobian[2][0]; + hj[2][1] = sHessian[2][0] * jacobian[0][1] + sHessian[2][1] * jacobian[1][1] + sHessian[2][2] * jacobian[2][1]; + hj[2][2] = sHessian[2][0] * jacobian[0][2] + sHessian[2][2] * jacobian[2][2]; + + // compute lower-left part of J^T . H_f . J + cHessian[0][0] = jacobian[0][0] * hj[0][0] + jacobian[1][0] * hj[1][0] + jacobian[2][0] * hj[2][0]; + cHessian[1][0] = jacobian[0][1] * hj[0][0] + jacobian[1][1] * hj[1][0] + jacobian[2][1] * hj[2][0]; + cHessian[2][0] = jacobian[0][2] * hj[0][0] + jacobian[2][2] * hj[2][0]; + cHessian[1][1] = jacobian[0][1] * hj[0][1] + jacobian[1][1] * hj[1][1] + jacobian[2][1] * hj[2][1]; + cHessian[2][1] = jacobian[0][2] * hj[0][1] + jacobian[2][2] * hj[2][1]; + cHessian[2][2] = jacobian[0][2] * hj[0][2] + jacobian[2][2] * hj[2][2]; + + // add gradient contribution + cHessian[0][0] += sGradient[0] * rHessian[0][0] + sGradient[1] * thetaHessian[0][0] + sGradient[2] * phiHessian[0][0]; + cHessian[1][0] += sGradient[0] * rHessian[1][0] + sGradient[1] * thetaHessian[1][0] + sGradient[2] * phiHessian[1][0]; + cHessian[2][0] += sGradient[0] * rHessian[2][0] + sGradient[2] * phiHessian[2][0]; + cHessian[1][1] += sGradient[0] * rHessian[1][1] + sGradient[1] * thetaHessian[1][1] + sGradient[2] * phiHessian[1][1]; + cHessian[2][1] += sGradient[0] * rHessian[2][1] + sGradient[2] * phiHessian[2][1]; + cHessian[2][2] += sGradient[0] * rHessian[2][2] + sGradient[2] * phiHessian[2][2]; + + // ensure symmetry + cHessian[0][1] = cHessian[1][0]; + cHessian[0][2] = cHessian[2][0]; + cHessian[1][2] = cHessian[2][1]; + + return cHessian; + + } + + /** Lazy evaluation of (r, θ, φ) Jacobian. + */ + private void computeJacobian() { + if (jacobian == null) { + + // intermediate variables + final double x = v.getX(); + final double y = v.getY(); + final double z = v.getZ(); + final double rho2 = x * x + y * y; + final double rho = FastMath.sqrt(rho2); + final double r2 = rho2 + z * z; + + jacobian = new double[3][3]; + + // row representing the gradient of r + jacobian[0][0] = x / r; + jacobian[0][1] = y / r; + jacobian[0][2] = z / r; + + // row representing the gradient of theta + jacobian[1][0] = -y / rho2; + jacobian[1][1] = x / rho2; + // jacobian[1][2] is already set to 0 at allocation time + + // row representing the gradient of phi + jacobian[2][0] = x * z / (rho * r2); + jacobian[2][1] = y * z / (rho * r2); + jacobian[2][2] = -rho / r2; + + } + } + + /** Lazy evaluation of Hessians. + */ + private void computeHessians() { + + if (rHessian == null) { + + // intermediate variables + final double x = v.getX(); + final double y = v.getY(); + final double z = v.getZ(); + final double x2 = x * x; + final double y2 = y * y; + final double z2 = z * z; + final double rho2 = x2 + y2; + final double rho = FastMath.sqrt(rho2); + final double r2 = rho2 + z2; + final double xOr = x / r; + final double yOr = y / r; + final double zOr = z / r; + final double xOrho2 = x / rho2; + final double yOrho2 = y / rho2; + final double xOr3 = xOr / r2; + final double yOr3 = yOr / r2; + final double zOr3 = zOr / r2; + + // lower-left part of Hessian of r + rHessian = new double[3][3]; + rHessian[0][0] = y * yOr3 + z * zOr3; + rHessian[1][0] = -x * yOr3; + rHessian[2][0] = -z * xOr3; + rHessian[1][1] = x * xOr3 + z * zOr3; + rHessian[2][1] = -y * zOr3; + rHessian[2][2] = x * xOr3 + y * yOr3; + + // upper-right part is symmetric + rHessian[0][1] = rHessian[1][0]; + rHessian[0][2] = rHessian[2][0]; + rHessian[1][2] = rHessian[2][1]; + + // lower-left part of Hessian of azimuthal angle theta + thetaHessian = new double[2][2]; + thetaHessian[0][0] = 2 * xOrho2 * yOrho2; + thetaHessian[1][0] = yOrho2 * yOrho2 - xOrho2 * xOrho2; + thetaHessian[1][1] = -2 * xOrho2 * yOrho2; + + // upper-right part is symmetric + thetaHessian[0][1] = thetaHessian[1][0]; + + // lower-left part of Hessian of polar (co-latitude) angle phi + final double rhor2 = rho * r2; + final double rho2r2 = rho * rhor2; + final double rhor4 = rhor2 * r2; + final double rho3r4 = rhor4 * rho2; + final double r2P2rho2 = 3 * rho2 + z2; + phiHessian = new double[3][3]; + phiHessian[0][0] = z * (rho2r2 - x2 * r2P2rho2) / rho3r4; + phiHessian[1][0] = -x * y * z * r2P2rho2 / rho3r4; + phiHessian[2][0] = x * (rho2 - z2) / rhor4; + phiHessian[1][1] = z * (rho2r2 - y2 * r2P2rho2) / rho3r4; + phiHessian[2][1] = y * (rho2 - z2) / rhor4; + phiHessian[2][2] = 2 * rho * zOr3 / r; + + // upper-right part is symmetric + phiHessian[0][1] = phiHessian[1][0]; + phiHessian[0][2] = phiHessian[2][0]; + phiHessian[1][2] = phiHessian[2][1]; + + } + + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(v.getX(), v.getY(), v.getZ()); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20130206L; + + /** Abscissa. + * @serial + */ + private final double x; + + /** Ordinate. + * @serial + */ + private final double y; + + /** Height. + * @serial + */ + private final double z; + + /** Simple constructor. + * @param x abscissa + * @param y ordinate + * @param z height + */ + DataTransferObject(final double x, final double y, final double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Replace the deserialized data transfer object with a {@link SphericalCoordinates}. + * @return replacement {@link SphericalCoordinates} + */ + private Object readResolve() { + return new SphericalCoordinates(new Vector3D(x, y, z)); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java new file mode 100644 index 000000000..b2cc3c15d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Interval; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; + +/** This class represents a subset of a {@link Line}. + * @since 3.0 + */ +public class SubLine { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Underlying line. */ + private final Line line; + + /** Remaining region of the hyperplane. */ + private final IntervalsSet remainingRegion; + + /** Simple constructor. + * @param line underlying line + * @param remainingRegion remaining region of the line + */ + public SubLine(final Line line, final IntervalsSet remainingRegion) { + this.line = line; + this.remainingRegion = remainingRegion; + } + + /** Create a sub-line from two endpoints. + * @param start start point + * @param end end point + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if the points are equal + * @since 3.3 + */ + public SubLine(final Vector3D start, final Vector3D end, final double tolerance) + throws MathIllegalArgumentException { + this(new Line(start, end, tolerance), buildIntervalSet(start, end, tolerance)); + } + + /** Create a sub-line from two endpoints. + * @param start start point + * @param end end point + * @exception MathIllegalArgumentException if the points are equal + * @deprecated as of 3.3, replaced with {@link #SubLine(Vector3D, Vector3D, double)} + */ + public SubLine(final Vector3D start, final Vector3D end) + throws MathIllegalArgumentException { + this(start, end, DEFAULT_TOLERANCE); + } + + /** Create a sub-line from a segment. + * @param segment single segment forming the sub-line + * @exception MathIllegalArgumentException if the segment endpoints are equal + */ + public SubLine(final Segment segment) throws MathIllegalArgumentException { + this(segment.getLine(), + buildIntervalSet(segment.getStart(), segment.getEnd(), segment.getLine().getTolerance())); + } + + /** Get the endpoints of the sub-line. + *

    + * A subline may be any arbitrary number of disjoints segments, so the endpoints + * are provided as a list of endpoint pairs. Each element of the list represents + * one segment, and each segment contains a start point at index 0 and an end point + * at index 1. If the sub-line is unbounded in the negative infinity direction, + * the start point of the first segment will have infinite coordinates. If the + * sub-line is unbounded in the positive infinity direction, the end point of the + * last segment will have infinite coordinates. So a sub-line covering the whole + * line will contain just one row and both elements of this row will have infinite + * coordinates. If the sub-line is empty, the returned list will contain 0 segments. + *

    + * @return list of segments endpoints + */ + public List getSegments() { + + final List list = remainingRegion.asList(); + final List segments = new ArrayList(list.size()); + + for (final Interval interval : list) { + final Vector3D start = line.toSpace((Point) new Vector1D(interval.getInf())); + final Vector3D end = line.toSpace((Point) new Vector1D(interval.getSup())); + segments.add(new Segment(start, end, line)); + } + + return segments; + + } + + /** Get the intersection of the instance and another sub-line. + *

    + * This method is related to the {@link Line#intersection(Line) + * intersection} method in the {@link Line Line} class, but in addition + * to compute the point along infinite lines, it also checks the point + * lies on both sub-line ranges. + *

    + * @param subLine other sub-line which may intersect instance + * @param includeEndPoints if true, endpoints are considered to belong to + * instance (i.e. they are closed sets) and may be returned, otherwise endpoints + * are considered to not belong to instance (i.e. they are open sets) and intersection + * occurring on endpoints lead to null being returned + * @return the intersection point if there is one, null if the sub-lines don't intersect + */ + public Vector3D intersection(final SubLine subLine, final boolean includeEndPoints) { + + // compute the intersection on infinite line + Vector3D v1D = line.intersection(subLine.line); + if (v1D == null) { + return null; + } + + // check location of point with respect to first sub-line + Region.Location loc1 = remainingRegion.checkPoint((Point) line.toSubSpace((Point) v1D)); + + // check location of point with respect to second sub-line + Region.Location loc2 = subLine.remainingRegion.checkPoint((Point) subLine.line.toSubSpace((Point) v1D)); + + if (includeEndPoints) { + return ((loc1 != Region.Location.OUTSIDE) && (loc2 != Region.Location.OUTSIDE)) ? v1D : null; + } else { + return ((loc1 == Region.Location.INSIDE) && (loc2 == Region.Location.INSIDE)) ? v1D : null; + } + + } + + /** Build an interval set from two points. + * @param start start point + * @param end end point + * @return an interval set + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if the points are equal + */ + private static IntervalsSet buildIntervalSet(final Vector3D start, final Vector3D end, final double tolerance) + throws MathIllegalArgumentException { + final Line line = new Line(start, end, tolerance); + return new IntervalsSet(line.toSubSpace((Point) start).getX(), + line.toSubSpace((Point) end).getX(), + tolerance); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java new file mode 100644 index 000000000..5e1ae172a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; + +/** This class represents a sub-hyperplane for {@link Plane}. + * @since 3.0 + */ +public class SubPlane extends AbstractSubHyperplane { + + /** Simple constructor. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + public SubPlane(final Hyperplane hyperplane, + final Region remainingRegion) { + super(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + protected AbstractSubHyperplane buildNew(final Hyperplane hyperplane, + final Region remainingRegion) { + return new SubPlane(hyperplane, remainingRegion); + } + + /** Split the instance in two parts by an hyperplane. + * @param hyperplane splitting hyperplane + * @return an object containing both the part of the instance + * on the plus side of the instance and the part of the + * instance on the minus side of the instance + */ + @Override + public SplitSubHyperplane split(Hyperplane hyperplane) { + + final Plane otherPlane = (Plane) hyperplane; + final Plane thisPlane = (Plane) getHyperplane(); + final Line inter = otherPlane.intersection(thisPlane); + final double tolerance = thisPlane.getTolerance(); + + if (inter == null) { + // the hyperplanes are parallel + final double global = otherPlane.getOffset(thisPlane); + if (global < -tolerance) { + return new SplitSubHyperplane(null, this); + } else if (global > tolerance) { + return new SplitSubHyperplane(this, null); + } else { + return new SplitSubHyperplane(null, null); + } + } + + // the hyperplanes do intersect + Vector2D p = thisPlane.toSubSpace((Point) inter.toSpace((Point) Vector1D.ZERO)); + Vector2D q = thisPlane.toSubSpace((Point) inter.toSpace((Point) Vector1D.ONE)); + Vector3D crossP = Vector3D.crossProduct(inter.getDirection(), thisPlane.getNormal()); + if (crossP.dotProduct(otherPlane.getNormal()) < 0) { + final Vector2D tmp = p; + p = q; + q = tmp; + } + final SubHyperplane l2DMinus = + new com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line(p, q, tolerance).wholeHyperplane(); + final SubHyperplane l2DPlus = + new com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line(q, p, tolerance).wholeHyperplane(); + + final BSPTree splitTree = getRemainingRegion().getTree(false).split(l2DMinus); + final BSPTree plusTree = getRemainingRegion().isEmpty(splitTree.getPlus()) ? + new BSPTree(Boolean.FALSE) : + new BSPTree(l2DPlus, new BSPTree(Boolean.FALSE), + splitTree.getPlus(), null); + + final BSPTree minusTree = getRemainingRegion().isEmpty(splitTree.getMinus()) ? + new BSPTree(Boolean.FALSE) : + new BSPTree(l2DMinus, new BSPTree(Boolean.FALSE), + splitTree.getMinus(), null); + + return new SplitSubHyperplane(new SubPlane(thisPlane.copySelf(), new PolygonsSet(plusTree, tolerance)), + new SubPlane(thisPlane.copySelf(), new PolygonsSet(minusTree, tolerance))); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java new file mode 100644 index 000000000..e78525bf7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java @@ -0,0 +1,588 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; +import java.text.NumberFormat; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This class implements vectors in a three-dimensional space. + *

    Instance of this class are guaranteed to be immutable.

    + * @since 1.2 + */ +public class Vector3D implements Serializable, Vector { + + /** Null vector (coordinates: 0, 0, 0). */ + public static final Vector3D ZERO = new Vector3D(0, 0, 0); + + /** First canonical vector (coordinates: 1, 0, 0). */ + public static final Vector3D PLUS_I = new Vector3D(1, 0, 0); + + /** Opposite of the first canonical vector (coordinates: -1, 0, 0). */ + public static final Vector3D MINUS_I = new Vector3D(-1, 0, 0); + + /** Second canonical vector (coordinates: 0, 1, 0). */ + public static final Vector3D PLUS_J = new Vector3D(0, 1, 0); + + /** Opposite of the second canonical vector (coordinates: 0, -1, 0). */ + public static final Vector3D MINUS_J = new Vector3D(0, -1, 0); + + /** Third canonical vector (coordinates: 0, 0, 1). */ + public static final Vector3D PLUS_K = new Vector3D(0, 0, 1); + + /** Opposite of the third canonical vector (coordinates: 0, 0, -1). */ + public static final Vector3D MINUS_K = new Vector3D(0, 0, -1); + + // CHECKSTYLE: stop ConstantName + /** A vector with all coordinates set to NaN. */ + public static final Vector3D NaN = new Vector3D(Double.NaN, Double.NaN, Double.NaN); + // CHECKSTYLE: resume ConstantName + + /** A vector with all coordinates set to positive infinity. */ + public static final Vector3D POSITIVE_INFINITY = + new Vector3D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + + /** A vector with all coordinates set to negative infinity. */ + public static final Vector3D NEGATIVE_INFINITY = + new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + + /** Serializable version identifier. */ + private static final long serialVersionUID = 1313493323784566947L; + + /** Abscissa. */ + private final double x; + + /** Ordinate. */ + private final double y; + + /** Height. */ + private final double z; + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @param z height + * @see #getX() + * @see #getY() + * @see #getZ() + */ + public Vector3D(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Simple constructor. + * Build a vector from its coordinates + * @param v coordinates array + * @exception DimensionMismatchException if array does not have 3 elements + * @see #toArray() + */ + public Vector3D(double[] v) throws DimensionMismatchException { + if (v.length != 3) { + throw new DimensionMismatchException(v.length, 3); + } + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + } + + /** Simple constructor. + * Build a vector from its azimuthal coordinates + * @param alpha azimuth (α) 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 = MathArrays.linearCombination(a1, u1.x, a2, u2.x); + this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y); + this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2, + double a3, Vector3D u3) { + this.x = MathArrays.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x); + this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y); + this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2, + double a3, Vector3D u3, double a4, Vector3D u4) { + this.x = MathArrays.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x, a4, u4.x); + this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y, a4, u4.y); + this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z, a4, u4.z); + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #Vector3D(double, double, double) + */ + public double getX() { + return x; + } + + /** Get the ordinate of the vector. + * @return ordinate of the vector + * @see #Vector3D(double, double, double) + */ + public double getY() { + return y; + } + + /** Get the height of the vector. + * @return height of the vector + * @see #Vector3D(double, double, double) + */ + public double getZ() { + return z; + } + + /** Get the vector coordinates as a dimension 3 array. + * @return vector coordinates + * @see #Vector3D(double[]) + */ + public double[] toArray() { + return new double[] { x, y, z }; + } + + /** {@inheritDoc} */ + public Space getSpace() { + return Euclidean3D.getInstance(); + } + + /** {@inheritDoc} */ + public Vector3D getZero() { + return ZERO; + } + + /** {@inheritDoc} */ + public double getNorm1() { + return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z); + } + + /** {@inheritDoc} */ + public double getNorm() { + // there are no cancellation problems here, so we use the straightforward formula + return FastMath.sqrt (x * x + y * y + z * z); + } + + /** {@inheritDoc} */ + public double getNormSq() { + // there are no cancellation problems here, so we use the straightforward formula + return x * x + y * y + z * z; + } + + /** {@inheritDoc} */ + public double getNormInf() { + return FastMath.max(FastMath.max(FastMath.abs(x), FastMath.abs(y)), FastMath.abs(z)); + } + + /** Get the azimuth of the vector. + * @return azimuth (α) 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()); + } + + /** {@inheritDoc} */ + public Vector3D add(final Vector v) { + final Vector3D v3 = (Vector3D) v; + return new Vector3D(x + v3.x, y + v3.y, z + v3.z); + } + + /** {@inheritDoc} */ + public Vector3D add(double factor, final Vector v) { + return new Vector3D(1, this, factor, (Vector3D) v); + } + + /** {@inheritDoc} */ + public Vector3D subtract(final Vector v) { + final Vector3D v3 = (Vector3D) v; + return new Vector3D(x - v3.x, y - v3.y, z - v3.z); + } + + /** {@inheritDoc} */ + public Vector3D subtract(final double factor, final Vector v) { + return new Vector3D(1, this, -factor, (Vector3D) v); + } + + /** {@inheritDoc} */ + public Vector3D normalize() throws MathArithmeticException { + double s = getNorm(); + if (s == 0) { + throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); + } + return scalarMultiply(1 / s); + } + + /** Get a vector orthogonal to the instance. + *

    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 : + *

    
    +     *   Vector3D k = u.normalize();
    +     *   Vector3D i = k.orthogonal();
    +     *   Vector3D j = Vector3D.crossProduct(k, i);
    +     * 

    + * @return a new normalized vector orthogonal to the instance + * @exception MathArithmeticException if the norm of the instance is null + */ + public Vector3D orthogonal() throws MathArithmeticException { + + double threshold = 0.6 * getNorm(); + if (threshold == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + if (FastMath.abs(x) <= threshold) { + double inverse = 1 / FastMath.sqrt(y * y + z * z); + return new Vector3D(0, inverse * z, -inverse * y); + } else if (FastMath.abs(y) <= threshold) { + double inverse = 1 / FastMath.sqrt(x * x + z * z); + return new Vector3D(-inverse * z, 0, inverse * x); + } + double inverse = 1 / FastMath.sqrt(x * x + y * y); + return new Vector3D(inverse * y, -inverse * x, 0); + + } + + /** Compute the angular separation between two vectors. + *

    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.

    + * @param v1 first vector + * @param v2 second vector + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static double angle(Vector3D v1, Vector3D v2) throws MathArithmeticException { + + double normProduct = v1.getNorm() * v2.getNorm(); + if (normProduct == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + double dot = v1.dotProduct(v2); + double threshold = normProduct * 0.9999; + if ((dot < -threshold) || (dot > threshold)) { + // the vectors are almost aligned, compute using the sine + Vector3D v3 = crossProduct(v1, v2); + if (dot >= 0) { + return FastMath.asin(v3.getNorm() / normProduct); + } + return FastMath.PI - FastMath.asin(v3.getNorm() / normProduct); + } + + // the vectors are sufficiently separated to use the cosine + return FastMath.acos(dot / normProduct); + + } + + /** {@inheritDoc} */ + public Vector3D negate() { + return new Vector3D(-x, -y, -z); + } + + /** {@inheritDoc} */ + public Vector3D scalarMultiply(double a) { + return new Vector3D(a * x, a * y, a * z); + } + + /** {@inheritDoc} */ + public boolean isNaN() { + return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z); + } + + /** {@inheritDoc} */ + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z)); + } + + /** + * Test for the equality of two 3D vectors. + *

    + * If all coordinates of two 3D vectors are exactly the same, and none are + * Double.NaN, the two 3D vectors are considered to be equal. + *

    + *

    + * NaN 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 Double.NaN, the 3D vector is equal to + * {@link #NaN}. + *

    + * + * @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. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 642; + } + return 643 * (164 * MathUtils.hash(x) + 3 * MathUtils.hash(y) + MathUtils.hash(z)); + } + + /** {@inheritDoc} + *

    + * The implementation uses specific multiplication and addition + * algorithms to preserve accuracy and reduce cancellation effects. + * It should be very accurate even for nearly orthogonal vectors. + *

    + * @see MathArrays#linearCombination(double, double, double, double, double, double) + */ + public double dotProduct(final Vector v) { + final Vector3D v3 = (Vector3D) v; + return MathArrays.linearCombination(x, v3.x, y, v3.y, z, v3.z); + } + + /** Compute the cross-product of the instance with another vector. + * @param v other vector + * @return the cross product this ^ v as a new Vector3D + */ + public Vector3D crossProduct(final Vector v) { + final Vector3D v3 = (Vector3D) v; + return new Vector3D(MathArrays.linearCombination(y, v3.z, -z, v3.y), + MathArrays.linearCombination(z, v3.x, -x, v3.z), + MathArrays.linearCombination(x, v3.y, -y, v3.x)); + } + + /** {@inheritDoc} */ + public double distance1(Vector v) { + final Vector3D v3 = (Vector3D) v; + final double dx = FastMath.abs(v3.x - x); + final double dy = FastMath.abs(v3.y - y); + final double dz = FastMath.abs(v3.z - z); + return dx + dy + dz; + } + + /** {@inheritDoc} */ + public double distance(Vector v) { + return distance((Point) v); + } + + /** {@inheritDoc} */ + public double distance(Point v) { + final Vector3D v3 = (Vector3D) v; + final double dx = v3.x - x; + final double dy = v3.y - y; + final double dz = v3.z - z; + return FastMath.sqrt(dx * dx + dy * dy + dz * dz); + } + + /** {@inheritDoc} */ + public double distanceInf(Vector v) { + final Vector3D v3 = (Vector3D) v; + final double dx = FastMath.abs(v3.x - x); + final double dy = FastMath.abs(v3.y - y); + final double dz = FastMath.abs(v3.z - z); + return FastMath.max(FastMath.max(dx, dy), dz); + } + + /** {@inheritDoc} */ + public double distanceSq(Vector v) { + final Vector3D v3 = (Vector3D) v; + final double dx = v3.x - x; + final double dy = v3.y - y; + final double dz = v3.z - z; + return dx * dx + dy * dy + dz * dz; + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the dot product v1.v2 + */ + public static double dotProduct(Vector3D v1, Vector3D v2) { + return v1.dotProduct(v2); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the cross product v1 ^ v2 as a new Vector + */ + public static Vector3D crossProduct(final Vector3D v1, final Vector3D v2) { + return v1.crossProduct(v2); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L1 norm + */ + public static double distance1(Vector3D v1, Vector3D v2) { + return v1.distance1(v2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L2 norm + */ + public static double distance(Vector3D v1, Vector3D v2) { + return v1.distance(v2); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L norm + */ + public static double distanceInf(Vector3D v1, Vector3D v2) { + return v1.distanceInf(v2); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

    + * @param v1 first vector + * @param v2 second vector + * @return the square of the distance between v1 and v2 + */ + public static double distanceSq(Vector3D v1, Vector3D v2) { + return v1.distanceSq(v2); + } + + /** Get a string representation of this vector. + * @return a string representation of this vector + */ + @Override + public String toString() { + return Vector3DFormat.getInstance().format(this); + } + + /** {@inheritDoc} */ + public String toString(final NumberFormat format) { + return new Vector3DFormat(format).format(this); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java new file mode 100644 index 000000000..2f8dc1378 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.VectorFormat; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a 3D vector in components list format "{x; y; z}". + *

    The prefix and suffix "{" and "}" and the separator "; " can be replaced by + * any user-defined strings. The number format for components can be configured.

    + *

    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.

    + *

    Note: using "," as a separator may interfere with the grouping separator + * of the default {@link NumberFormat} for the current locale. Thus it is advised + * to use a {@link NumberFormat} instance with disabled grouping in such a case.

    + * + */ +public class Vector3DFormat extends VectorFormat { + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and separator: + * "{", "}", and "; " and the default number format for components.

    + */ + public Vector3DFormat() { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + public Vector3DFormat(final NumberFormat format) { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + */ + public Vector3DFormat(final String prefix, final String suffix, + final String separator) { + super(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + * @param format the custom format for components. + */ + public Vector3DFormat(final String prefix, final String suffix, + final String separator, final NumberFormat format) { + super(prefix, suffix, separator, format); + } + + /** + * Returns the default 3D vector format for the current locale. + * @return the default 3D vector format. + */ + public static Vector3DFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default 3D vector format for the given locale. + * @param locale the specific locale used by the format. + * @return the 3D vector format specific to the given locale. + */ + public static Vector3DFormat getInstance(final Locale locale) { + return new Vector3DFormat(CompositeFormat.getDefaultNumberFormat(locale)); + } + + /** + * Formats a {@link Vector3D} object to produce a string. + * @param vector the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + @Override + public StringBuffer format(final Vector vector, final StringBuffer toAppendTo, + final FieldPosition pos) { + final Vector3D v3 = (Vector3D) vector; + return format(toAppendTo, pos, v3.getX(), v3.getY(), v3.getZ()); + } + + /** + * Parses a string to produce a {@link Vector3D} object. + * @param source the string to parse + * @return the parsed {@link Vector3D} object. + * @throws MathParseException if the beginning of the specified string + * cannot be parsed. + */ + @Override + public Vector3D parse(final String source) throws MathParseException { + ParsePosition parsePosition = new ParsePosition(0); + Vector3D result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + Vector3D.class); + } + return result; + } + + /** + * Parses a string to produce a {@link Vector3D} object. + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link Vector3D} object. + */ + @Override + public Vector3D parse(final String source, final ParsePosition pos) { + final double[] coordinates = parseCoordinates(3, source, pos); + if (coordinates == null) { + return null; + } + return new Vector3D(coordinates[0], coordinates[1], coordinates[2]); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/package-info.java new file mode 100644 index 000000000..2cd168720 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/threed/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides basic 3D geometry components. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.threed; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java new file mode 100644 index 000000000..b49e36ee8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.enclosing.EnclosingBall; +import com.fr.third.org.apache.commons.math3.geometry.enclosing.SupportBallGenerator; +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Class generating an enclosing ball from its support points. + * @since 3.3 + */ +public class DiskGenerator implements SupportBallGenerator { + + /** {@inheritDoc} */ + public EnclosingBall ballOnSupport(final List support) { + + if (support.size() < 1) { + return new EnclosingBall(Vector2D.ZERO, Double.NEGATIVE_INFINITY); + } else { + final Vector2D vA = support.get(0); + if (support.size() < 2) { + return new EnclosingBall(vA, 0, vA); + } else { + final Vector2D vB = support.get(1); + if (support.size() < 3) { + return new EnclosingBall(new Vector2D(0.5, vA, 0.5, vB), + 0.5 * vA.distance(vB), + vA, vB); + } else { + final Vector2D vC = support.get(2); + // a disk is 2D can be defined as: + // (1) (x - x_0)^2 + (y - y_0)^2 = r^2 + // which can be written: + // (2) (x^2 + y^2) - 2 x_0 x - 2 y_0 y + (x_0^2 + y_0^2 - r^2) = 0 + // or simply: + // (3) (x^2 + y^2) + a x + b y + c = 0 + // with disk center coordinates -a/2, -b/2 + // If the disk exists, a, b and c are a non-zero solution to + // [ (x^2 + y^2 ) x y 1 ] [ 1 ] [ 0 ] + // [ (xA^2 + yA^2) xA yA 1 ] [ a ] [ 0 ] + // [ (xB^2 + yB^2) xB yB 1 ] * [ b ] = [ 0 ] + // [ (xC^2 + yC^2) xC yC 1 ] [ c ] [ 0 ] + // So the determinant of the matrix is zero. Computing this determinant + // by expanding it using the minors m_ij of first row leads to + // (4) m_11 (x^2 + y^2) - m_12 x + m_13 y - m_14 = 0 + // So by identifying equations (2) and (4) we get the coordinates + // of center as: + // x_0 = +m_12 / (2 m_11) + // y_0 = -m_13 / (2 m_11) + // Note that the minors m_11, m_12 and m_13 all have the last column + // filled with 1.0, hence simplifying the computation + final BigFraction[] c2 = new BigFraction[] { + new BigFraction(vA.getX()), new BigFraction(vB.getX()), new BigFraction(vC.getX()) + }; + final BigFraction[] c3 = new BigFraction[] { + new BigFraction(vA.getY()), new BigFraction(vB.getY()), new BigFraction(vC.getY()) + }; + final BigFraction[] c1 = new BigFraction[] { + c2[0].multiply(c2[0]).add(c3[0].multiply(c3[0])), + c2[1].multiply(c2[1]).add(c3[1].multiply(c3[1])), + c2[2].multiply(c2[2]).add(c3[2].multiply(c3[2])) + }; + final BigFraction twoM11 = minor(c2, c3).multiply(2); + final BigFraction m12 = minor(c1, c3); + final BigFraction m13 = minor(c1, c2); + final BigFraction centerX = m12.divide(twoM11); + final BigFraction centerY = m13.divide(twoM11).negate(); + final BigFraction dx = c2[0].subtract(centerX); + final BigFraction dy = c3[0].subtract(centerY); + final BigFraction r2 = dx.multiply(dx).add(dy.multiply(dy)); + return new EnclosingBall(new Vector2D(centerX.doubleValue(), + centerY.doubleValue()), + FastMath.sqrt(r2.doubleValue()), + vA, vB, vC); + } + } + } + } + + /** Compute a dimension 3 minor, when 3d column is known to be filled with 1.0. + * @param c1 first column + * @param c2 second column + * @return value of the minor computed has an exact fraction + */ + private BigFraction minor(final BigFraction[] c1, final BigFraction[] c2) { + return c2[0].multiply(c1[2].subtract(c1[1])). + add(c2[1].multiply(c1[0].subtract(c1[2]))). + add(c2[2].multiply(c1[1].subtract(c1[0]))); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java new file mode 100644 index 000000000..59fce36fe --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** + * This class implements a two-dimensional space. + * @since 3.0 + */ +public class Euclidean2D implements Serializable, Space { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 4793432849757649566L; + + /** Private constructor for the singleton. + */ + private Euclidean2D() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static Euclidean2D getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public int getDimension() { + return 2; + } + + /** {@inheritDoc} */ + public Euclidean1D getSubSpace() { + return Euclidean1D.getInstance(); + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final Euclidean2D INSTANCE = new Euclidean2D(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Line.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Line.java new file mode 100644 index 000000000..13aa1319c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Line.java @@ -0,0 +1,587 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.awt.geom.AffineTransform; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Embedding; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Transform; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents an oriented line in the 2D plane. + + *

    An oriented line can be defined either by prolongating a line + * segment between two points past these points, or by one point and + * an angular direction (in trigonometric orientation).

    + + *

    Since it is oriented the two half planes at its two sides are + * unambiguously identified as a left half plane and a right half + * plane. This can be used to identify the interior and the exterior + * in a simple way by local properties only when part of a line is + * used to define part of a polygon boundary.

    + + *

    A line can also be used to completely define a reference frame + * in the plane. It is sufficient to select one specific point in the + * line (the orthogonal projection of the original reference frame on + * the line) and to use the unit vector in the line direction and the + * orthogonal vector oriented from left half plane to right half + * plane. We define two coordinates by the process, the + * abscissa along the line, and the offset across + * the line. All points of the plane are uniquely identified by these + * two coordinates. The line is the set of points at zero offset, the + * left half plane is the set of points with negative offsets and the + * right half plane is the set of points with positive offsets.

    + + * @since 3.0 + */ +public class Line implements Hyperplane, Embedding { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Angle with respect to the abscissa axis. */ + private double angle; + + /** Cosine of the line angle. */ + private double cos; + + /** Sine of the line angle. */ + private double sin; + + /** Offset of the frame origin. */ + private double originOffset; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** Reverse line. */ + private Line reverse; + + /** Build a line from two points. + *

    The line is oriented from p1 to p2

    + * @param p1 first point + * @param p2 second point + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public Line(final Vector2D p1, final Vector2D p2, final double tolerance) { + reset(p1, p2); + this.tolerance = tolerance; + } + + /** Build a line from a point and an angle. + * @param p point belonging to the line + * @param angle angle of the line with respect to abscissa axis + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public Line(final Vector2D p, final double angle, final double tolerance) { + reset(p, angle); + this.tolerance = tolerance; + } + + /** Build a line from its internal characteristics. + * @param angle angle of the line with respect to abscissa axis + * @param cos cosine of the angle + * @param sin sine of the angle + * @param originOffset offset of the origin + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + private Line(final double angle, final double cos, final double sin, + final double originOffset, final double tolerance) { + this.angle = angle; + this.cos = cos; + this.sin = sin; + this.originOffset = originOffset; + this.tolerance = tolerance; + this.reverse = null; + } + + /** Build a line from two points. + *

    The line is oriented from p1 to p2

    + * @param p1 first point + * @param p2 second point + * @deprecated as of 3.3, replaced with {@link #Line(Vector2D, Vector2D, double)} + */ + @Deprecated + public Line(final Vector2D p1, final Vector2D p2) { + this(p1, p2, DEFAULT_TOLERANCE); + } + + /** Build a line from a point and an angle. + * @param p point belonging to the line + * @param angle angle of the line with respect to abscissa axis + * @deprecated as of 3.3, replaced with {@link #Line(Vector2D, double, double)} + */ + @Deprecated + public Line(final Vector2D p, final double angle) { + this(p, angle, DEFAULT_TOLERANCE); + } + + /** Copy constructor. + *

    The created instance is completely independent from the + * original instance, it is a deep copy.

    + * @param line line to copy + */ + public Line(final Line line) { + angle = MathUtils.normalizeAngle(line.angle, FastMath.PI); + cos = line.cos; + sin = line.sin; + originOffset = line.originOffset; + tolerance = line.tolerance; + reverse = null; + } + + /** {@inheritDoc} */ + public Line copySelf() { + return new Line(this); + } + + /** Reset the instance as if built from two points. + *

    The line is oriented from p1 to p2

    + * @param p1 first point + * @param p2 second point + */ + public void reset(final Vector2D p1, final Vector2D p2) { + unlinkReverse(); + final double dx = p2.getX() - p1.getX(); + final double dy = p2.getY() - p1.getY(); + final double d = FastMath.hypot(dx, dy); + if (d == 0.0) { + angle = 0.0; + cos = 1.0; + sin = 0.0; + originOffset = p1.getY(); + } else { + angle = FastMath.PI + FastMath.atan2(-dy, -dx); + cos = dx / d; + sin = dy / d; + originOffset = MathArrays.linearCombination(p2.getX(), p1.getY(), -p1.getX(), p2.getY()) / d; + } + } + + /** Reset the instance as if built from a line and an angle. + * @param p point belonging to the line + * @param alpha angle of the line with respect to abscissa axis + */ + public void reset(final Vector2D p, final double alpha) { + unlinkReverse(); + this.angle = MathUtils.normalizeAngle(alpha, FastMath.PI); + cos = FastMath.cos(this.angle); + sin = FastMath.sin(this.angle); + originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX()); + } + + /** Revert the instance. + */ + public void revertSelf() { + unlinkReverse(); + if (angle < FastMath.PI) { + angle += FastMath.PI; + } else { + angle -= FastMath.PI; + } + cos = -cos; + sin = -sin; + originOffset = -originOffset; + } + + /** Unset the link between an instance and its reverse. + */ + private void unlinkReverse() { + if (reverse != null) { + reverse.reverse = null; + } + reverse = null; + } + + /** Get the reverse of the instance. + *

    Get a line with reversed orientation with respect to the + * instance.

    + *

    + * As long as neither the instance nor its reverse are modified + * (i.e. as long as none of the {@link #reset(Vector2D, Vector2D)}, + * {@link #reset(Vector2D, double)}, {@link #revertSelf()}, + * {@link #setAngle(double)} or {@link #setOriginOffset(double)} + * methods are called), then the line and its reverse remain linked + * together so that {@code line.getReverse().getReverse() == line}. + * When one of the line is modified, the link is deleted as both + * instance becomes independent. + *

    + * @return a new line, with orientation opposite to the instance orientation + */ + public Line getReverse() { + if (reverse == null) { + reverse = new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI), + -cos, -sin, -originOffset, tolerance); + reverse.reverse = this; + } + return reverse; + } + + /** Transform a space point into a sub-space point. + * @param vector n-dimension point of the space + * @return (n-1)-dimension point of the sub-space corresponding to + * the specified space point + */ + public Vector1D toSubSpace(Vector vector) { + return toSubSpace((Point) vector); + } + + /** Transform a sub-space point into a space point. + * @param vector (n-1)-dimension point of the sub-space + * @return n-dimension point of the space corresponding to the + * specified sub-space point + */ + public Vector2D toSpace(Vector vector) { + return toSpace((Point) vector); + } + + /** {@inheritDoc} */ + public Vector1D toSubSpace(final Point point) { + Vector2D p2 = (Vector2D) point; + return new Vector1D(MathArrays.linearCombination(cos, p2.getX(), sin, p2.getY())); + } + + /** {@inheritDoc} */ + public Vector2D toSpace(final Point point) { + final double abscissa = ((Vector1D) point).getX(); + return new Vector2D(MathArrays.linearCombination(abscissa, cos, -originOffset, sin), + MathArrays.linearCombination(abscissa, sin, originOffset, cos)); + } + + /** Get the intersection point of the instance and another line. + * @param other other line + * @return intersection point of the instance and the other line + * or null if there are no intersection points + */ + public Vector2D intersection(final Line other) { + final double d = MathArrays.linearCombination(sin, other.cos, -other.sin, cos); + if (FastMath.abs(d) < tolerance) { + return null; + } + return new Vector2D(MathArrays.linearCombination(cos, other.originOffset, -other.cos, originOffset) / d, + MathArrays.linearCombination(sin, other.originOffset, -other.sin, originOffset) / d); + } + + /** {@inheritDoc} + * @since 3.3 + */ + public Point project(Point point) { + return toSpace(toSubSpace(point)); + } + + /** {@inheritDoc} + * @since 3.3 + */ + public double getTolerance() { + return tolerance; + } + + /** {@inheritDoc} */ + public SubLine wholeHyperplane() { + return new SubLine(this, new IntervalsSet(tolerance)); + } + + /** Build a region covering the whole space. + * @return a region containing the instance (really a {@link + * PolygonsSet PolygonsSet} instance) + */ + public PolygonsSet wholeSpace() { + return new PolygonsSet(tolerance); + } + + /** Get the offset (oriented distance) of a parallel line. + *

    This method should be called only for parallel lines otherwise + * the result is not meaningful.

    + *

    The offset is 0 if both lines are the same, it is + * positive if the line is on the right side of the instance and + * negative if it is on the left side, according to its natural + * orientation.

    + * @param line line to check + * @return offset of the line + */ + public double getOffset(final Line line) { + return originOffset + + (MathArrays.linearCombination(cos, line.cos, sin, line.sin) > 0 ? -line.originOffset : line.originOffset); + } + + /** Get the offset (oriented distance) of a vector. + * @param vector vector to check + * @return offset of the vector + */ + public double getOffset(Vector vector) { + return getOffset((Point) vector); + } + + /** {@inheritDoc} */ + public double getOffset(final Point point) { + Vector2D p2 = (Vector2D) point; + return MathArrays.linearCombination(sin, p2.getX(), -cos, p2.getY(), 1.0, originOffset); + } + + /** {@inheritDoc} */ + public boolean sameOrientationAs(final Hyperplane other) { + final Line otherL = (Line) other; + return MathArrays.linearCombination(sin, otherL.sin, cos, otherL.cos) >= 0.0; + } + + /** Get one point from the plane. + * @param abscissa desired abscissa for the point + * @param offset desired offset for the point + * @return one point in the plane, with given abscissa and offset + * relative to the line + */ + public Vector2D getPointAt(final Vector1D abscissa, final double offset) { + final double x = abscissa.getX(); + final double dOffset = offset - originOffset; + return new Vector2D(MathArrays.linearCombination(x, cos, dOffset, sin), + MathArrays.linearCombination(x, sin, -dOffset, cos)); + } + + /** Check if the line contains a point. + * @param p point to check + * @return true if p belongs to the line + */ + public boolean contains(final Vector2D p) { + return FastMath.abs(getOffset(p)) < tolerance; + } + + /** Compute the distance between the instance and a point. + *

    This is a shortcut for invoking FastMath.abs(getOffset(p)), + * and provides consistency with what is in the + * Line class.

    + * + * @param p to check + * @return distance between the instance and the point + * @since 3.1 + */ + public double distance(final Vector2D p) { + return FastMath.abs(getOffset(p)); + } + + /** Check the instance is parallel to another line. + * @param line other line to check + * @return true if the instance is parallel to the other line + * (they can have either the same or opposite orientations) + */ + public boolean isParallelTo(final Line line) { + return FastMath.abs(MathArrays.linearCombination(sin, line.cos, -cos, line.sin)) < tolerance; + } + + /** Translate the line to force it passing by a point. + * @param p point by which the line should pass + */ + public void translateToPoint(final Vector2D p) { + originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX()); + } + + /** Get the angle of the line. + * @return the angle of the line with respect to the abscissa axis + */ + public double getAngle() { + return MathUtils.normalizeAngle(angle, FastMath.PI); + } + + /** Set the angle of the line. + * @param angle new angle of the line with respect to the abscissa axis + */ + public void setAngle(final double angle) { + unlinkReverse(); + this.angle = MathUtils.normalizeAngle(angle, FastMath.PI); + cos = FastMath.cos(this.angle); + sin = FastMath.sin(this.angle); + } + + /** Get the offset of the origin. + * @return the offset of the origin + */ + public double getOriginOffset() { + return originOffset; + } + + /** Set the offset of the origin. + * @param offset offset of the origin + */ + public void setOriginOffset(final double offset) { + unlinkReverse(); + originOffset = offset; + } + + /** Get a {@link Transform + * Transform} embedding an affine transform. + * @param transform affine transform to embed (must be inversible + * otherwise the {@link + * Transform#apply(Hyperplane) + * apply(Hyperplane)} method would work only for some lines, and + * fail for other ones) + * @return a new transform that can be applied to either {@link + * Vector2D Vector2D}, {@link Line Line} or {@link + * SubHyperplane + * SubHyperplane} instances + * @exception MathIllegalArgumentException if the transform is non invertible + * @deprecated as of 3.6, replaced with {@link #getTransform(double, double, double, double, double, double)} + */ + @Deprecated + public static Transform getTransform(final AffineTransform transform) + throws MathIllegalArgumentException { + final double[] m = new double[6]; + transform.getMatrix(m); + return new LineTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + /** Get a {@link Transform + * Transform} embedding an affine transform. + * @param cXX transform factor between input abscissa and output abscissa + * @param cYX transform factor between input abscissa and output ordinate + * @param cXY transform factor between input ordinate and output abscissa + * @param cYY transform factor between input ordinate and output ordinate + * @param cX1 transform addendum for output abscissa + * @param cY1 transform addendum for output ordinate + * @return a new transform that can be applied to either {@link + * Vector2D Vector2D}, {@link Line Line} or {@link + * SubHyperplane + * SubHyperplane} instances + * @exception MathIllegalArgumentException if the transform is non invertible + * @since 3.6 + */ + public static Transform getTransform(final double cXX, + final double cYX, + final double cXY, + final double cYY, + final double cX1, + final double cY1) + throws MathIllegalArgumentException { + return new LineTransform(cXX, cYX, cXY, cYY, cX1, cY1); + } + + /** Class embedding an affine transform. + *

    This class is used in order to apply an affine transform to a + * line. Using a specific object allow to perform some computations + * on the transform only once even if the same transform is to be + * applied to a large number of lines (for example to a large + * polygon)./

    + */ + private static class LineTransform implements Transform { + + /** Transform factor between input abscissa and output abscissa. */ + private double cXX; + + /** Transform factor between input abscissa and output ordinate. */ + private double cYX; + + /** Transform factor between input ordinate and output abscissa. */ + private double cXY; + + /** Transform factor between input ordinate and output ordinate. */ + private double cYY; + + /** Transform addendum for output abscissa. */ + private double cX1; + + /** Transform addendum for output ordinate. */ + private double cY1; + + /** cXY * cY1 - cYY * cX1. */ + private double c1Y; + + /** cXX * cY1 - cYX * cX1. */ + private double c1X; + + /** cXX * cYY - cYX * cXY. */ + private double c11; + + /** Build an affine line transform from a n {@code AffineTransform}. + * @param cXX transform factor between input abscissa and output abscissa + * @param cYX transform factor between input abscissa and output ordinate + * @param cXY transform factor between input ordinate and output abscissa + * @param cYY transform factor between input ordinate and output ordinate + * @param cX1 transform addendum for output abscissa + * @param cY1 transform addendum for output ordinate + * @exception MathIllegalArgumentException if the transform is non invertible + * @since 3.6 + */ + LineTransform(final double cXX, final double cYX, final double cXY, + final double cYY, final double cX1, final double cY1) + throws MathIllegalArgumentException { + + this.cXX = cXX; + this.cYX = cYX; + this.cXY = cXY; + this.cYY = cYY; + this.cX1 = cX1; + this.cY1 = cY1; + + c1Y = MathArrays.linearCombination(cXY, cY1, -cYY, cX1); + c1X = MathArrays.linearCombination(cXX, cY1, -cYX, cX1); + c11 = MathArrays.linearCombination(cXX, cYY, -cYX, cXY); + + if (FastMath.abs(c11) < 1.0e-20) { + throw new MathIllegalArgumentException(LocalizedFormats.NON_INVERTIBLE_TRANSFORM); + } + + } + + /** {@inheritDoc} */ + public Vector2D apply(final Point point) { + final Vector2D p2D = (Vector2D) point; + final double x = p2D.getX(); + final double y = p2D.getY(); + return new Vector2D(MathArrays.linearCombination(cXX, x, cXY, y, cX1, 1), + MathArrays.linearCombination(cYX, x, cYY, y, cY1, 1)); + } + + /** {@inheritDoc} */ + public Line apply(final Hyperplane hyperplane) { + final Line line = (Line) hyperplane; + final double rOffset = MathArrays.linearCombination(c1X, line.cos, c1Y, line.sin, c11, line.originOffset); + final double rCos = MathArrays.linearCombination(cXX, line.cos, cXY, line.sin); + final double rSin = MathArrays.linearCombination(cYX, line.cos, cYY, line.sin); + final double inv = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos); + return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos), + inv * rCos, inv * rSin, + inv * rOffset, line.tolerance); + } + + /** {@inheritDoc} */ + public SubHyperplane apply(final SubHyperplane sub, + final Hyperplane original, + final Hyperplane transformed) { + final OrientedPoint op = (OrientedPoint) sub.getHyperplane(); + final Line originalLine = (Line) original; + final Line transformedLine = (Line) transformed; + final Vector1D newLoc = + transformedLine.toSubSpace(apply(originalLine.toSpace(op.getLocation()))); + return new OrientedPoint(newLoc, op.isDirect(), originalLine.tolerance).wholeHyperplane(); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java new file mode 100644 index 000000000..f780c30bb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.RegionFactory; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; + +/** This class represent a tree of nested 2D boundary loops. + + *

    This class is used for piecewise polygons construction. + * Polygons are built using the outline edges as + * representative of boundaries, the orientation of these lines are + * meaningful. However, we want to allow the user to specify its + * outline loops without having to take care of this orientation. This + * class is devoted to correct mis-oriented loops.

    + + *

    Orientation is computed assuming the piecewise polygon is finite, + * i.e. the outermost loops have their exterior side facing points at + * infinity, and hence are oriented counter-clockwise. The orientation of + * internal loops is computed as the reverse of the orientation of + * their immediate surrounding loop.

    + + * @since 3.0 + */ +class NestedLoops { + + /** Boundary loop. */ + private Vector2D[] loop; + + /** Surrounded loops. */ + private List surrounded; + + /** Polygon enclosing a finite region. */ + private Region polygon; + + /** Indicator for original loop orientation. */ + private boolean originalIsClockwise; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** Simple Constructor. + *

    Build an empty tree of nested loops. This instance will become + * the root node of a complete tree, it is not associated with any + * loop by itself, the outermost loops are in the root tree child + * nodes.

    + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + NestedLoops(final double tolerance) { + this.surrounded = new ArrayList(); + this.tolerance = tolerance; + } + + /** Constructor. + *

    Build a tree node with neither parent nor children

    + * @param loop boundary loop (will be reversed in place if needed) + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if an outline has an open boundary loop + * @since 3.3 + */ + private NestedLoops(final Vector2D[] loop, final double tolerance) + throws MathIllegalArgumentException { + + if (loop[0] == null) { + throw new MathIllegalArgumentException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN); + } + + this.loop = loop; + this.surrounded = new ArrayList(); + this.tolerance = tolerance; + + // build the polygon defined by the loop + final ArrayList> edges = new ArrayList>(); + Vector2D current = loop[loop.length - 1]; + for (int i = 0; i < loop.length; ++i) { + final Vector2D previous = current; + current = loop[i]; + final Line line = new Line(previous, current, tolerance); + final IntervalsSet region = + new IntervalsSet(line.toSubSpace((Point) previous).getX(), + line.toSubSpace((Point) current).getX(), + tolerance); + edges.add(new SubLine(line, region)); + } + polygon = new PolygonsSet(edges, tolerance); + + // ensure the polygon encloses a finite region of the plane + if (Double.isInfinite(polygon.getSize())) { + polygon = new RegionFactory().getComplement(polygon); + originalIsClockwise = false; + } else { + originalIsClockwise = true; + } + + } + + /** Add a loop in a tree. + * @param bLoop boundary loop (will be reversed in place if needed) + * @exception MathIllegalArgumentException if an outline has crossing + * boundary loops or open boundary loops + */ + public void add(final Vector2D[] bLoop) throws MathIllegalArgumentException { + add(new NestedLoops(bLoop, tolerance)); + } + + /** Add a loop in a tree. + * @param node boundary loop (will be reversed in place if needed) + * @exception MathIllegalArgumentException if an outline has boundary + * loops that cross each other + */ + private void add(final NestedLoops node) throws MathIllegalArgumentException { + + // check if we can go deeper in the tree + for (final NestedLoops child : surrounded) { + if (child.polygon.contains(node.polygon)) { + child.add(node); + return; + } + } + + // check if we can absorb some of the instance children + for (final Iterator iterator = surrounded.iterator(); iterator.hasNext();) { + final NestedLoops child = iterator.next(); + if (node.polygon.contains(child.polygon)) { + node.surrounded.add(child); + iterator.remove(); + } + } + + // we should be separate from the remaining children + RegionFactory factory = new RegionFactory(); + for (final NestedLoops child : surrounded) { + if (!factory.intersection(node.polygon, child.polygon).isEmpty()) { + throw new MathIllegalArgumentException(LocalizedFormats.CROSSING_BOUNDARY_LOOPS); + } + } + + surrounded.add(node); + + } + + /** Correct the orientation of the loops contained in the tree. + *

    This is this method that really inverts the loops that where + * provided through the {@link #add(Vector2D[]) add} method if + * they are mis-oriented

    + */ + public void correctOrientation() { + for (NestedLoops child : surrounded) { + child.setClockWise(true); + } + } + + /** Set the loop orientation. + * @param clockwise if true, the loop should be set to clockwise + * orientation + */ + private void setClockWise(final boolean clockwise) { + + if (originalIsClockwise ^ clockwise) { + // we need to inverse the original loop + int min = -1; + int max = loop.length; + while (++min < --max) { + final Vector2D tmp = loop[min]; + loop[min] = loop[max]; + loop[max] = tmp; + } + } + + // go deeper in the tree + for (final NestedLoops child : surrounded) { + child.setClockWise(!clockwise); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java new file mode 100644 index 000000000..cec67e908 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java @@ -0,0 +1,1161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Interval; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Side; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** This class represents a 2D region: a set of polygons. + * @since 3.0 + */ +public class PolygonsSet extends AbstractRegion { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Vertices organized as boundary loops. */ + private Vector2D[][] vertices; + + /** Build a polygons set representing the whole plane. + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolygonsSet(final double tolerance) { + super(tolerance); + } + + /** Build a polygons set from a BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + *

    + * This constructor is aimed at expert use, as building the tree may + * be a difficult task. It is not intended for general use and for + * performances reasons does not check thoroughly its input, as this would + * require walking the full tree each time. Failing to provide a tree with + * the proper attributes, will therefore generate problems like + * {@link NullPointerException} or {@link ClassCastException} only later on. + * This limitation is known and explains why this constructor is for expert + * use only. The caller does have the responsibility to provided correct arguments. + *

    + * @param tree inside/outside BSP tree representing the region + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolygonsSet(final BSPTree tree, final double tolerance) { + super(tree, tolerance); + } + + /** Build a polygons set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoint polygons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolygonsSet(final Collection> boundary, final double tolerance) { + super(boundary, tolerance); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolygonsSet(final double xMin, final double xMax, + final double yMin, final double yMax, + final double tolerance) { + super(boxBoundary(xMin, xMax, yMin, yMax, tolerance), tolerance); + } + + /** Build a polygon from a simple list of vertices. + *

    The boundary is provided as a list of points considering to + * represent the vertices of a simple loop. The interior part of the + * region is on the left side of this path and the exterior is on its + * right side.

    + *

    This constructor does not handle polygons with a boundary + * forming several disconnected paths (such as polygons with holes).

    + *

    For cases where this simple constructor applies, it is expected to + * be numerically more robust than the {@link #PolygonsSet(Collection) general + * constructor} using {@link SubHyperplane subhyperplanes}.

    + *

    If the list is empty, the region will represent the whole + * space.

    + *

    + * Polygons with thin pikes or dents are inherently difficult to handle because + * they involve lines with almost opposite directions at some vertices. Polygons + * whose vertices come from some physical measurement with noise are also + * difficult because an edge that should be straight may be broken in lots of + * different pieces with almost equal directions. In both cases, computing the + * lines intersections is not numerically robust due to the almost 0 or almost + * π angle. Such cases need to carefully adjust the {@code hyperplaneThickness} + * parameter. A too small value would often lead to completely wrong polygons + * with large area wrongly identified as inside or outside. Large values are + * often much safer. As a rule of thumb, a value slightly below the size of the + * most accurate detail needed is a good value for the {@code hyperplaneThickness} + * parameter. + *

    + * @param hyperplaneThickness tolerance below which points are considered to + * belong to the hyperplane (which is therefore more a slab) + * @param vertices vertices of the simple loop boundary + */ + public PolygonsSet(final double hyperplaneThickness, final Vector2D ... vertices) { + super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness); + } + + /** Build a polygons set representing the whole real line. + * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double)} + */ + @Deprecated + public PolygonsSet() { + this(DEFAULT_TOLERANCE); + } + + /** Build a polygons set from a BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the region + * @deprecated as of 3.3, replaced with {@link #PolygonsSet(BSPTree, double)} + */ + @Deprecated + public PolygonsSet(final BSPTree tree) { + this(tree, DEFAULT_TOLERANCE); + } + + /** Build a polygons set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoint polygons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @deprecated as of 3.3, replaced with {@link #PolygonsSet(Collection, double)} + */ + @Deprecated + public PolygonsSet(final Collection> boundary) { + this(boundary, DEFAULT_TOLERANCE); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double, double, double, double, double)} + */ + @Deprecated + public PolygonsSet(final double xMin, final double xMax, + final double yMin, final double yMax) { + this(xMin, xMax, yMin, yMax, DEFAULT_TOLERANCE); + } + + /** Create a list of hyperplanes representing the boundary of a box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param tolerance tolerance below which points are considered identical + * @return boundary of the box + */ + private static Line[] boxBoundary(final double xMin, final double xMax, + final double yMin, final double yMax, + final double tolerance) { + if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance)) { + // too thin box, build an empty polygons set + return null; + } + final Vector2D minMin = new Vector2D(xMin, yMin); + final Vector2D minMax = new Vector2D(xMin, yMax); + final Vector2D maxMin = new Vector2D(xMax, yMin); + final Vector2D maxMax = new Vector2D(xMax, yMax); + return new Line[] { + new Line(minMin, maxMin, tolerance), + new Line(maxMin, maxMax, tolerance), + new Line(maxMax, minMax, tolerance), + new Line(minMax, minMin, tolerance) + }; + } + + /** Build the BSP tree of a polygons set from a simple list of vertices. + *

    The boundary is provided as a list of points considering to + * represent the vertices of a simple loop. The interior part of the + * region is on the left side of this path and the exterior is on its + * right side.

    + *

    This constructor does not handle polygons with a boundary + * forming several disconnected paths (such as polygons with holes).

    + *

    For cases where this simple constructor applies, it is expected to + * be numerically more robust than the {@link #PolygonsSet(Collection) general + * constructor} using {@link SubHyperplane subhyperplanes}.

    + * @param hyperplaneThickness tolerance below which points are consider to + * belong to the hyperplane (which is therefore more a slab) + * @param vertices vertices of the simple loop boundary + * @return the BSP tree of the input vertices + */ + private static BSPTree verticesToTree(final double hyperplaneThickness, + final Vector2D ... vertices) { + + final int n = vertices.length; + if (n == 0) { + // the tree represents the whole space + return new BSPTree(Boolean.TRUE); + } + + // build the vertices + final Vertex[] vArray = new Vertex[n]; + for (int i = 0; i < n; ++i) { + vArray[i] = new Vertex(vertices[i]); + } + + // build the edges + List edges = new ArrayList(n); + for (int i = 0; i < n; ++i) { + + // get the endpoints of the edge + final Vertex start = vArray[i]; + final Vertex end = vArray[(i + 1) % n]; + + // get the line supporting the edge, taking care not to recreate it + // if it was already created earlier due to another edge being aligned + // with the current one + Line line = start.sharedLineWith(end); + if (line == null) { + line = new Line(start.getLocation(), end.getLocation(), hyperplaneThickness); + } + + // create the edge and store it + edges.add(new Edge(start, end, line)); + + // check if another vertex also happens to be on this line + for (final Vertex vertex : vArray) { + if (vertex != start && vertex != end && + FastMath.abs(line.getOffset((Point) vertex.getLocation())) <= hyperplaneThickness) { + vertex.bindWith(line); + } + } + + } + + // build the tree top-down + final BSPTree tree = new BSPTree(); + insertEdges(hyperplaneThickness, tree, edges); + + return tree; + + } + + /** Recursively build a tree by inserting cut sub-hyperplanes. + * @param hyperplaneThickness tolerance below which points are consider to + * belong to the hyperplane (which is therefore more a slab) + * @param node current tree node (it is a leaf node at the beginning + * of the call) + * @param edges list of edges to insert in the cell defined by this node + * (excluding edges not belonging to the cell defined by this node) + */ + private static void insertEdges(final double hyperplaneThickness, + final BSPTree node, + final List edges) { + + // find an edge with an hyperplane that can be inserted in the node + int index = 0; + Edge inserted =null; + while (inserted == null && index < edges.size()) { + inserted = edges.get(index++); + if (inserted.getNode() == null) { + if (node.insertCut(inserted.getLine())) { + inserted.setNode(node); + } else { + inserted = null; + } + } else { + inserted = null; + } + } + + if (inserted == null) { + // no suitable edge was found, the node remains a leaf node + // we need to set its inside/outside boolean indicator + final BSPTree parent = node.getParent(); + if (parent == null || node == parent.getMinus()) { + node.setAttribute(Boolean.TRUE); + } else { + node.setAttribute(Boolean.FALSE); + } + return; + } + + // we have split the node by inserting an edge as a cut sub-hyperplane + // distribute the remaining edges in the two sub-trees + final List plusList = new ArrayList(); + final List minusList = new ArrayList(); + for (final Edge edge : edges) { + if (edge != inserted) { + final double startOffset = inserted.getLine().getOffset((Point) edge.getStart().getLocation()); + final double endOffset = inserted.getLine().getOffset((Point) edge.getEnd().getLocation()); + Side startSide = (FastMath.abs(startOffset) <= hyperplaneThickness) ? + Side.HYPER : ((startOffset < 0) ? Side.MINUS : Side.PLUS); + Side endSide = (FastMath.abs(endOffset) <= hyperplaneThickness) ? + Side.HYPER : ((endOffset < 0) ? Side.MINUS : Side.PLUS); + switch (startSide) { + case PLUS: + if (endSide == Side.MINUS) { + // we need to insert a split point on the hyperplane + final Vertex splitPoint = edge.split(inserted.getLine()); + minusList.add(splitPoint.getOutgoing()); + plusList.add(splitPoint.getIncoming()); + } else { + plusList.add(edge); + } + break; + case MINUS: + if (endSide == Side.PLUS) { + // we need to insert a split point on the hyperplane + final Vertex splitPoint = edge.split(inserted.getLine()); + minusList.add(splitPoint.getIncoming()); + plusList.add(splitPoint.getOutgoing()); + } else { + minusList.add(edge); + } + break; + default: + if (endSide == Side.PLUS) { + plusList.add(edge); + } else if (endSide == Side.MINUS) { + minusList.add(edge); + } + break; + } + } + } + + // recurse through lower levels + if (!plusList.isEmpty()) { + insertEdges(hyperplaneThickness, node.getPlus(), plusList); + } else { + node.getPlus().setAttribute(Boolean.FALSE); + } + if (!minusList.isEmpty()) { + insertEdges(hyperplaneThickness, node.getMinus(), minusList); + } else { + node.getMinus().setAttribute(Boolean.TRUE); + } + + } + + /** Internal class for holding vertices while they are processed to build a BSP tree. */ + private static class Vertex { + + /** Vertex location. */ + private final Vector2D location; + + /** Incoming edge. */ + private Edge incoming; + + /** Outgoing edge. */ + private Edge outgoing; + + /** Lines bound with this vertex. */ + private final List lines; + + /** Build a non-processed vertex not owned by any node yet. + * @param location vertex location + */ + Vertex(final Vector2D location) { + this.location = location; + this.incoming = null; + this.outgoing = null; + this.lines = new ArrayList(); + } + + /** Get Vertex location. + * @return vertex location + */ + public Vector2D getLocation() { + return location; + } + + /** Bind a line considered to contain this vertex. + * @param line line to bind with this vertex + */ + public void bindWith(final Line line) { + lines.add(line); + } + + /** Get the common line bound with both the instance and another vertex, if any. + *

    + * When two vertices are both bound to the same line, this means they are + * already handled by node associated with this line, so there is no need + * to create a cut hyperplane for them. + *

    + * @param vertex other vertex to check instance against + * @return line bound with both the instance and another vertex, or null if the + * two vertices do not share a line yet + */ + public Line sharedLineWith(final Vertex vertex) { + for (final Line line1 : lines) { + for (final Line line2 : vertex.lines) { + if (line1 == line2) { + return line1; + } + } + } + return null; + } + + /** Set incoming edge. + *

    + * The line supporting the incoming edge is automatically bound + * with the instance. + *

    + * @param incoming incoming edge + */ + public void setIncoming(final Edge incoming) { + this.incoming = incoming; + bindWith(incoming.getLine()); + } + + /** Get incoming edge. + * @return incoming edge + */ + public Edge getIncoming() { + return incoming; + } + + /** Set outgoing edge. + *

    + * The line supporting the outgoing edge is automatically bound + * with the instance. + *

    + * @param outgoing outgoing edge + */ + public void setOutgoing(final Edge outgoing) { + this.outgoing = outgoing; + bindWith(outgoing.getLine()); + } + + /** Get outgoing edge. + * @return outgoing edge + */ + public Edge getOutgoing() { + return outgoing; + } + + } + + /** Internal class for holding edges while they are processed to build a BSP tree. */ + private static class Edge { + + /** Start vertex. */ + private final Vertex start; + + /** End vertex. */ + private final Vertex end; + + /** Line supporting the edge. */ + private final Line line; + + /** Node whose cut hyperplane contains this edge. */ + private BSPTree node; + + /** Build an edge not contained in any node yet. + * @param start start vertex + * @param end end vertex + * @param line line supporting the edge + */ + Edge(final Vertex start, final Vertex end, final Line line) { + + this.start = start; + this.end = end; + this.line = line; + this.node = null; + + // connect the vertices back to the edge + start.setOutgoing(this); + end.setIncoming(this); + + } + + /** Get start vertex. + * @return start vertex + */ + public Vertex getStart() { + return start; + } + + /** Get end vertex. + * @return end vertex + */ + public Vertex getEnd() { + return end; + } + + /** Get the line supporting this edge. + * @return line supporting this edge + */ + public Line getLine() { + return line; + } + + /** Set the node whose cut hyperplane contains this edge. + * @param node node whose cut hyperplane contains this edge + */ + public void setNode(final BSPTree node) { + this.node = node; + } + + /** Get the node whose cut hyperplane contains this edge. + * @return node whose cut hyperplane contains this edge + * (null if edge has not yet been inserted into the BSP tree) + */ + public BSPTree getNode() { + return node; + } + + /** Split the edge. + *

    + * Once split, this edge is not referenced anymore by the vertices, + * it is replaced by the two half-edges and an intermediate splitting + * vertex is introduced to connect these two halves. + *

    + * @param splitLine line splitting the edge in two halves + * @return split vertex (its incoming and outgoing edges are the two halves) + */ + public Vertex split(final Line splitLine) { + final Vertex splitVertex = new Vertex(line.intersection(splitLine)); + splitVertex.bindWith(splitLine); + final Edge startHalf = new Edge(start, splitVertex, line); + final Edge endHalf = new Edge(splitVertex, end, line); + startHalf.node = node; + endHalf.node = node; + return splitVertex; + } + + } + + /** {@inheritDoc} */ + @Override + public PolygonsSet buildNew(final BSPTree tree) { + return new PolygonsSet(tree, getTolerance()); + } + + /** {@inheritDoc} */ + @Override + protected void computeGeometricalProperties() { + + final Vector2D[][] v = getVertices(); + + if (v.length == 0) { + final BSPTree tree = getTree(false); + if (tree.getCut() == null && (Boolean) tree.getAttribute()) { + // the instance covers the whole space + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point) Vector2D.NaN); + } else { + setSize(0); + setBarycenter((Point) new Vector2D(0, 0)); + } + } else if (v[0][0] == null) { + // there is at least one open-loop: the polygon is infinite + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point) Vector2D.NaN); + } else { + // all loops are closed, we compute some integrals around the shape + + double sum = 0; + double sumX = 0; + double sumY = 0; + + for (Vector2D[] loop : v) { + double x1 = loop[loop.length - 1].getX(); + double y1 = loop[loop.length - 1].getY(); + for (final Vector2D point : loop) { + final double x0 = x1; + final double y0 = y1; + x1 = point.getX(); + y1 = point.getY(); + final double factor = x0 * y1 - y0 * x1; + sum += factor; + sumX += factor * (x0 + x1); + sumY += factor * (y0 + y1); + } + } + + if (sum < 0) { + // the polygon as a finite outside surrounded by an infinite inside + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point) Vector2D.NaN); + } else { + setSize(sum / 2); + setBarycenter((Point) new Vector2D(sumX / (3 * sum), sumY / (3 * sum))); + } + + } + + } + + /** Get the vertices of the polygon. + *

    The polygon boundary can be represented as an array of loops, + * each loop being itself an array of vertices.

    + *

    In order to identify open loops which start and end by + * infinite edges, the open loops arrays start with a null point. In + * this case, the first non null point and the last point of the + * array do not represent real vertices, they are dummy points + * intended only to get the direction of the first and last edge. An + * open loop consisting of a single infinite line will therefore be + * represented by a three elements array with one null point + * followed by two dummy points. The open loops are always the first + * ones in the loops array.

    + *

    If the polygon has no boundary at all, a zero length loop + * array will be returned.

    + *

    All line segments in the various loops have the inside of the + * region on their left side and the outside on their right side + * when moving in the underlying line direction. This means that + * closed loops surrounding finite areas obey the direct + * trigonometric orientation.

    + * @return vertices of the polygon, organized as oriented boundary + * loops with the open loops first (the returned value is guaranteed + * to be non-null) + */ + public Vector2D[][] getVertices() { + if (vertices == null) { + if (getTree(false).getCut() == null) { + vertices = new Vector2D[0][]; + } else { + + // build the unconnected segments + final SegmentsBuilder visitor = new SegmentsBuilder(getTolerance()); + getTree(true).visit(visitor); + final List segments = visitor.getSegments(); + + // connect all segments, using topological criteria first + // and using Euclidean distance only as a last resort + int pending = segments.size(); + pending -= naturalFollowerConnections(segments); + if (pending > 0) { + pending -= splitEdgeConnections(segments); + } + if (pending > 0) { + pending -= closeVerticesConnections(segments); + } + + // create the segment loops + final ArrayList> loops = new ArrayList>(); + for (ConnectableSegment s = getUnprocessed(segments); s != null; s = getUnprocessed(segments)) { + final List loop = followLoop(s); + if (loop != null) { + if (loop.get(0).getStart() == null) { + // this is an open loop, we put it on the front + loops.add(0, loop); + } else { + // this is a closed loop, we put it on the back + loops.add(loop); + } + } + } + + // transform the loops in an array of arrays of points + vertices = new Vector2D[loops.size()][]; + int i = 0; + + for (final List loop : loops) { + if (loop.size() < 2 || + (loop.size() == 2 && loop.get(0).getStart() == null && loop.get(1).getEnd() == null)) { + // single infinite line + final Line line = loop.get(0).getLine(); + vertices[i++] = new Vector2D[] { + null, + line.toSpace((Point) new Vector1D(-Float.MAX_VALUE)), + line.toSpace((Point) new Vector1D(+Float.MAX_VALUE)) + }; + } else if (loop.get(0).getStart() == null) { + // open loop with at least one real point + final Vector2D[] array = new Vector2D[loop.size() + 2]; + int j = 0; + for (Segment segment : loop) { + + if (j == 0) { + // null point and first dummy point + double x = segment.getLine().toSubSpace((Point) segment.getEnd()).getX(); + x -= FastMath.max(1.0, FastMath.abs(x / 2)); + array[j++] = null; + array[j++] = segment.getLine().toSpace((Point) new Vector1D(x)); + } + + if (j < (array.length - 1)) { + // current point + array[j++] = segment.getEnd(); + } + + if (j == (array.length - 1)) { + // last dummy point + double x = segment.getLine().toSubSpace((Point) segment.getStart()).getX(); + x += FastMath.max(1.0, FastMath.abs(x / 2)); + array[j++] = segment.getLine().toSpace((Point) new Vector1D(x)); + } + + } + vertices[i++] = array; + } else { + final Vector2D[] array = new Vector2D[loop.size()]; + int j = 0; + for (Segment segment : loop) { + array[j++] = segment.getStart(); + } + vertices[i++] = array; + } + } + + } + } + + return vertices.clone(); + + } + + /** Connect the segments using only natural follower information. + * @param segments segments complete segments list + * @return number of connections performed + */ + private int naturalFollowerConnections(final List segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null) { + final BSPTree node = segment.getNode(); + final BSPTree end = segment.getEndNode(); + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && + candidateNext.getNode() == end && + candidateNext.getStartNode() == node) { + // connect the two segments + segment.setNext(candidateNext); + candidateNext.setPrevious(segment); + ++connected; + break; + } + } + } + } + return connected; + } + + /** Connect the segments resulting from a line splitting a straight edge. + * @param segments segments complete segments list + * @return number of connections performed + */ + private int splitEdgeConnections(final List segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null) { + final Hyperplane hyperplane = segment.getNode().getCut().getHyperplane(); + final BSPTree end = segment.getEndNode(); + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && + candidateNext.getNode().getCut().getHyperplane() == hyperplane && + candidateNext.getStartNode() == end) { + // connect the two segments + segment.setNext(candidateNext); + candidateNext.setPrevious(segment); + ++connected; + break; + } + } + } + } + return connected; + } + + /** Connect the segments using Euclidean distance. + *

    + * This connection heuristic should be used last, as it relies + * only on a fuzzy distance criterion. + *

    + * @param segments segments complete segments list + * @return number of connections performed + */ + private int closeVerticesConnections(final List segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null && segment.getEnd() != null) { + final Vector2D end = segment.getEnd(); + ConnectableSegment selectedNext = null; + double min = Double.POSITIVE_INFINITY; + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && candidateNext.getStart() != null) { + final double distance = Vector2D.distance(end, candidateNext.getStart()); + if (distance < min) { + selectedNext = candidateNext; + min = distance; + } + } + } + if (min <= getTolerance()) { + // connect the two segments + segment.setNext(selectedNext); + selectedNext.setPrevious(segment); + ++connected; + } + } + } + return connected; + } + + /** Get first unprocessed segment from a list. + * @param segments segments list + * @return first segment that has not been processed yet + * or null if all segments have been processed + */ + private ConnectableSegment getUnprocessed(final List segments) { + for (final ConnectableSegment segment : segments) { + if (!segment.isProcessed()) { + return segment; + } + } + return null; + } + + /** Build the loop containing a segment. + *

    + * The segment put in the loop will be marked as processed. + *

    + * @param defining segment used to define the loop + * @return loop containing the segment (may be null if the loop is a + * degenerated infinitely thin 2 points loop + */ + private List followLoop(final ConnectableSegment defining) { + + final List loop = new ArrayList(); + loop.add(defining); + defining.setProcessed(true); + + // add segments in connection order + ConnectableSegment next = defining.getNext(); + while (next != defining && next != null) { + loop.add(next); + next.setProcessed(true); + next = next.getNext(); + } + + if (next == null) { + // the loop is open and we have found its end, + // we need to find its start too + ConnectableSegment previous = defining.getPrevious(); + while (previous != null) { + loop.add(0, previous); + previous.setProcessed(true); + previous = previous.getPrevious(); + } + } + + // filter out spurious vertices + filterSpuriousVertices(loop); + + if (loop.size() == 2 && loop.get(0).getStart() != null) { + // this is a degenerated infinitely thin closed loop, we simply ignore it + return null; + } else { + return loop; + } + + } + + /** Filter out spurious vertices on straight lines (at machine precision). + * @param loop segments loop to filter (will be modified in-place) + */ + private void filterSpuriousVertices(final List loop) { + for (int i = 0; i < loop.size(); ++i) { + final Segment previous = loop.get(i); + int j = (i + 1) % loop.size(); + final Segment next = loop.get(j); + if (next != null && + Precision.equals(previous.getLine().getAngle(), next.getLine().getAngle(), Precision.EPSILON)) { + // the vertex between the two edges is a spurious one + // replace the two segments by a single one + loop.set(j, new Segment(previous.getStart(), next.getEnd(), previous.getLine())); + loop.remove(i--); + } + } + } + + /** Private extension of Segment allowing connection. */ + private static class ConnectableSegment extends Segment { + + /** Node containing segment. */ + private final BSPTree node; + + /** Node whose intersection with current node defines start point. */ + private final BSPTree startNode; + + /** Node whose intersection with current node defines end point. */ + private final BSPTree endNode; + + /** Previous segment. */ + private ConnectableSegment previous; + + /** Next segment. */ + private ConnectableSegment next; + + /** Indicator for completely processed segments. */ + private boolean processed; + + /** Build a segment. + * @param start start point of the segment + * @param end end point of the segment + * @param line line containing the segment + * @param node node containing the segment + * @param startNode node whose intersection with current node defines start point + * @param endNode node whose intersection with current node defines end point + */ + ConnectableSegment(final Vector2D start, final Vector2D end, final Line line, + final BSPTree node, + final BSPTree startNode, + final BSPTree endNode) { + super(start, end, line); + this.node = node; + this.startNode = startNode; + this.endNode = endNode; + this.previous = null; + this.next = null; + this.processed = false; + } + + /** Get the node containing segment. + * @return node containing segment + */ + public BSPTree getNode() { + return node; + } + + /** Get the node whose intersection with current node defines start point. + * @return node whose intersection with current node defines start point + */ + public BSPTree getStartNode() { + return startNode; + } + + /** Get the node whose intersection with current node defines end point. + * @return node whose intersection with current node defines end point + */ + public BSPTree getEndNode() { + return endNode; + } + + /** Get the previous segment. + * @return previous segment + */ + public ConnectableSegment getPrevious() { + return previous; + } + + /** Set the previous segment. + * @param previous previous segment + */ + public void setPrevious(final ConnectableSegment previous) { + this.previous = previous; + } + + /** Get the next segment. + * @return next segment + */ + public ConnectableSegment getNext() { + return next; + } + + /** Set the next segment. + * @param next previous segment + */ + public void setNext(final ConnectableSegment next) { + this.next = next; + } + + /** Set the processed flag. + * @param processed processed flag to set + */ + public void setProcessed(final boolean processed) { + this.processed = processed; + } + + /** Check if the segment has been processed. + * @return true if the segment has been processed + */ + public boolean isProcessed() { + return processed; + } + + } + + /** Visitor building segments. */ + private static class SegmentsBuilder implements BSPTreeVisitor { + + /** Tolerance for close nodes connection. */ + private final double tolerance; + + /** Built segments. */ + private final List segments; + + /** Simple constructor. + * @param tolerance tolerance for close nodes connection + */ + SegmentsBuilder(final double tolerance) { + this.tolerance = tolerance; + this.segments = new ArrayList(); + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); + final Iterable> splitters = attribute.getSplitters(); + if (attribute.getPlusOutside() != null) { + addContribution(attribute.getPlusOutside(), node, splitters, false); + } + if (attribute.getPlusInside() != null) { + addContribution(attribute.getPlusInside(), node, splitters, true); + } + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + } + + /** Add the contribution of a boundary facet. + * @param sub boundary facet + * @param node node containing segment + * @param splitters splitters for the boundary facet + * @param reversed if true, the facet has the inside on its plus side + */ + private void addContribution(final SubHyperplane sub, + final BSPTree node, + final Iterable> splitters, + final boolean reversed) { + @SuppressWarnings("unchecked") + final AbstractSubHyperplane absSub = + (AbstractSubHyperplane) sub; + final Line line = (Line) sub.getHyperplane(); + final List intervals = ((IntervalsSet) absSub.getRemainingRegion()).asList(); + for (final Interval i : intervals) { + + // find the 2D points + final Vector2D startV = Double.isInfinite(i.getInf()) ? + null : (Vector2D) line.toSpace((Point) new Vector1D(i.getInf())); + final Vector2D endV = Double.isInfinite(i.getSup()) ? + null : (Vector2D) line.toSpace((Point) new Vector1D(i.getSup())); + + // recover the connectivity information + final BSPTree startN = selectClosest(startV, splitters); + final BSPTree endN = selectClosest(endV, splitters); + + if (reversed) { + segments.add(new ConnectableSegment(endV, startV, line.getReverse(), + node, endN, startN)); + } else { + segments.add(new ConnectableSegment(startV, endV, line, + node, startN, endN)); + } + + } + } + + /** Select the node whose cut sub-hyperplane is closest to specified point. + * @param point reference point + * @param candidates candidate nodes + * @return node closest to point, or null if no node is closer than tolerance + */ + private BSPTree selectClosest(final Vector2D point, final Iterable> candidates) { + + BSPTree selected = null; + double min = Double.POSITIVE_INFINITY; + + for (final BSPTree node : candidates) { + final double distance = FastMath.abs(node.getCut().getHyperplane().getOffset(point)); + if (distance < min) { + selected = node; + min = distance; + } + } + + return min <= tolerance ? selected : null; + + } + + /** Get the segments. + * @return built segments + */ + public List getSegments() { + return segments; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Segment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Segment.java new file mode 100644 index 000000000..352056b90 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Segment.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Simple container for a two-points segment. + * @since 3.0 + */ +public class Segment { + + /** Start point of the segment. */ + private final Vector2D start; + + /** End point of the segment. */ + private final Vector2D end; + + /** Line containing the segment. */ + private final Line line; + + /** Build a segment. + * @param start start point of the segment + * @param end end point of the segment + * @param line line containing the segment + */ + public Segment(final Vector2D start, final Vector2D end, final Line line) { + this.start = start; + this.end = end; + this.line = line; + } + + /** Get the start point of the segment. + * @return start point of the segment + */ + public Vector2D getStart() { + return start; + } + + /** Get the end point of the segment. + * @return end point of the segment + */ + public Vector2D getEnd() { + return end; + } + + /** Get the line containing the segment. + * @return line containing the segment + */ + public Line getLine() { + return line; + } + + /** Calculates the shortest distance from a point to this line segment. + *

    + * If the perpendicular extension from the point to the line does not + * cross in the bounds of the line segment, the shortest distance to + * the two end points will be returned. + *

    + * + * Algorithm adapted from: + * + * Thread @ Codeguru + * + * @param p to check + * @return distance between the instance and the point + * @since 3.1 + */ + public double distance(final Vector2D p) { + final double deltaX = end.getX() - start.getX(); + final double deltaY = end.getY() - start.getY(); + + final double r = ((p.getX() - start.getX()) * deltaX + (p.getY() - start.getY()) * deltaY) / + (deltaX * deltaX + deltaY * deltaY); + + // r == 0 => P = startPt + // r == 1 => P = endPt + // r < 0 => P is on the backward extension of the segment + // r > 1 => P is on the forward extension of the segment + // 0 < r < 1 => P is on the segment + + // if point isn't on the line segment, just return the shortest distance to the end points + if (r < 0 || r > 1) { + final double dist1 = getStart().distance((Point) p); + final double dist2 = getEnd().distance((Point) p); + + return FastMath.min(dist1, dist2); + } + else { + // find point on line and see if it is in the line segment + final double px = start.getX() + r * deltaX; + final double py = start.getY() + r * deltaY; + + final Vector2D interPt = new Vector2D(px, py); + return interPt.distance((Point) p); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/SubLine.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/SubLine.java new file mode 100644 index 000000000..56d3a4bcb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/SubLine.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 com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Interval; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.oned.Vector1D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class represents a sub-hyperplane for {@link Line}. + * @since 3.0 + */ +public class SubLine extends AbstractSubHyperplane { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Simple constructor. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + public SubLine(final Hyperplane hyperplane, + final Region remainingRegion) { + super(hyperplane, remainingRegion); + } + + /** Create a sub-line from two endpoints. + * @param start start point + * @param end end point + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public SubLine(final Vector2D start, final Vector2D end, final double tolerance) { + super(new Line(start, end, tolerance), buildIntervalSet(start, end, tolerance)); + } + + /** Create a sub-line from two endpoints. + * @param start start point + * @param end end point + * @deprecated as of 3.3, replaced with {@link #SubLine(Vector2D, Vector2D, double)} + */ + @Deprecated + public SubLine(final Vector2D start, final Vector2D end) { + this(start, end, DEFAULT_TOLERANCE); + } + + /** Create a sub-line from a segment. + * @param segment single segment forming the sub-line + */ + public SubLine(final Segment segment) { + super(segment.getLine(), + buildIntervalSet(segment.getStart(), segment.getEnd(), segment.getLine().getTolerance())); + } + + /** Get the endpoints of the sub-line. + *

    + * A subline may be any arbitrary number of disjoints segments, so the endpoints + * are provided as a list of endpoint pairs. Each element of the list represents + * one segment, and each segment contains a start point at index 0 and an end point + * at index 1. If the sub-line is unbounded in the negative infinity direction, + * the start point of the first segment will have infinite coordinates. If the + * sub-line is unbounded in the positive infinity direction, the end point of the + * last segment will have infinite coordinates. So a sub-line covering the whole + * line will contain just one row and both elements of this row will have infinite + * coordinates. If the sub-line is empty, the returned list will contain 0 segments. + *

    + * @return list of segments endpoints + */ + public List getSegments() { + + final Line line = (Line) getHyperplane(); + final List list = ((IntervalsSet) getRemainingRegion()).asList(); + final List segments = new ArrayList(list.size()); + + for (final Interval interval : list) { + final Vector2D start = line.toSpace((Point) new Vector1D(interval.getInf())); + final Vector2D end = line.toSpace((Point) new Vector1D(interval.getSup())); + segments.add(new Segment(start, end, line)); + } + + return segments; + + } + + /** Get the intersection of the instance and another sub-line. + *

    + * This method is related to the {@link Line#intersection(Line) + * intersection} method in the {@link Line Line} class, but in addition + * to compute the point along infinite lines, it also checks the point + * lies on both sub-line ranges. + *

    + * @param subLine other sub-line which may intersect instance + * @param includeEndPoints if true, endpoints are considered to belong to + * instance (i.e. they are closed sets) and may be returned, otherwise endpoints + * are considered to not belong to instance (i.e. they are open sets) and intersection + * occurring on endpoints lead to null being returned + * @return the intersection point if there is one, null if the sub-lines don't intersect + */ + public Vector2D intersection(final SubLine subLine, final boolean includeEndPoints) { + + // retrieve the underlying lines + Line line1 = (Line) getHyperplane(); + Line line2 = (Line) subLine.getHyperplane(); + + // compute the intersection on infinite line + Vector2D v2D = line1.intersection(line2); + if (v2D == null) { + return null; + } + + // check location of point with respect to first sub-line + Region.Location loc1 = getRemainingRegion().checkPoint(line1.toSubSpace((Point) v2D)); + + // check location of point with respect to second sub-line + Region.Location loc2 = subLine.getRemainingRegion().checkPoint(line2.toSubSpace((Point) v2D)); + + if (includeEndPoints) { + return ((loc1 != Region.Location.OUTSIDE) && (loc2 != Region.Location.OUTSIDE)) ? v2D : null; + } else { + return ((loc1 == Region.Location.INSIDE) && (loc2 == Region.Location.INSIDE)) ? v2D : null; + } + + } + + /** Build an interval set from two points. + * @param start start point + * @param end end point + * @param tolerance tolerance below which points are considered identical + * @return an interval set + */ + private static IntervalsSet buildIntervalSet(final Vector2D start, final Vector2D end, final double tolerance) { + final Line line = new Line(start, end, tolerance); + return new IntervalsSet(line.toSubSpace((Point) start).getX(), + line.toSubSpace((Point) end).getX(), + tolerance); + } + + /** {@inheritDoc} */ + @Override + protected AbstractSubHyperplane buildNew(final Hyperplane hyperplane, + final Region remainingRegion) { + return new SubLine(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public SplitSubHyperplane split(final Hyperplane hyperplane) { + + final Line thisLine = (Line) getHyperplane(); + final Line otherLine = (Line) hyperplane; + final Vector2D crossing = thisLine.intersection(otherLine); + final double tolerance = thisLine.getTolerance(); + + if (crossing == null) { + // the lines are parallel + final double global = otherLine.getOffset(thisLine); + if (global < -tolerance) { + return new SplitSubHyperplane(null, this); + } else if (global > tolerance) { + return new SplitSubHyperplane(this, null); + } else { + return new SplitSubHyperplane(null, null); + } + } + + // the lines do intersect + final boolean direct = FastMath.sin(thisLine.getAngle() - otherLine.getAngle()) < 0; + final Vector1D x = thisLine.toSubSpace((Point) crossing); + final SubHyperplane subPlus = + new OrientedPoint(x, !direct, tolerance).wholeHyperplane(); + final SubHyperplane subMinus = + new OrientedPoint(x, direct, tolerance).wholeHyperplane(); + + final BSPTree splitTree = getRemainingRegion().getTree(false).split(subMinus); + final BSPTree plusTree = getRemainingRegion().isEmpty(splitTree.getPlus()) ? + new BSPTree(Boolean.FALSE) : + new BSPTree(subPlus, new BSPTree(Boolean.FALSE), + splitTree.getPlus(), null); + final BSPTree minusTree = getRemainingRegion().isEmpty(splitTree.getMinus()) ? + new BSPTree(Boolean.FALSE) : + new BSPTree(subMinus, new BSPTree(Boolean.FALSE), + splitTree.getMinus(), null); + return new SplitSubHyperplane(new SubLine(thisLine.copySelf(), new IntervalsSet(plusTree, tolerance)), + new SubLine(thisLine.copySelf(), new IntervalsSet(minusTree, tolerance))); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java new file mode 100644 index 000000000..75be4a0c7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java @@ -0,0 +1,460 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.text.NumberFormat; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents a 2D vector. + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.0 + */ +public class Vector2D implements Vector { + + /** Origin (coordinates: 0, 0). */ + public static final Vector2D ZERO = new Vector2D(0, 0); + + // CHECKSTYLE: stop ConstantName + /** A vector with all coordinates set to NaN. */ + public static final Vector2D NaN = new Vector2D(Double.NaN, Double.NaN); + // CHECKSTYLE: resume ConstantName + + /** A vector with all coordinates set to positive infinity. */ + public static final Vector2D POSITIVE_INFINITY = + new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + + /** A vector with all coordinates set to negative infinity. */ + public static final Vector2D NEGATIVE_INFINITY = + new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + + /** Serializable UID. */ + private static final long serialVersionUID = 266938651998679754L; + + /** Abscissa. */ + private final double x; + + /** Ordinate. */ + private final double y; + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @see #getX() + * @see #getY() + */ + public Vector2D(double x, double y) { + this.x = x; + this.y = y; + } + + /** Simple constructor. + * Build a vector from its coordinates + * @param v coordinates array + * @exception DimensionMismatchException if array does not have 2 elements + * @see #toArray() + */ + public Vector2D(double[] v) throws DimensionMismatchException { + if (v.length != 2) { + throw new DimensionMismatchException(v.length, 2); + } + this.x = v[0]; + this.y = v[1]; + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector2D(double a, Vector2D u) { + this.x = a * u.x; + this.y = a * u.y; + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2) { + this.x = a1 * u1.x + a2 * u2.x; + this.y = a1 * u1.y + a2 * u2.y; + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2, + double a3, Vector2D u3) { + this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x; + this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y; + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2, + double a3, Vector2D u3, double a4, Vector2D u4) { + this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x; + this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y + a4 * u4.y; + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #Vector2D(double, double) + */ + public double getX() { + return x; + } + + /** Get the ordinate of the vector. + * @return ordinate of the vector + * @see #Vector2D(double, double) + */ + public double getY() { + return y; + } + + /** Get the vector coordinates as a dimension 2 array. + * @return vector coordinates + * @see #Vector2D(double[]) + */ + public double[] toArray() { + return new double[] { x, y }; + } + + /** {@inheritDoc} */ + public Space getSpace() { + return Euclidean2D.getInstance(); + } + + /** {@inheritDoc} */ + public Vector2D getZero() { + return ZERO; + } + + /** {@inheritDoc} */ + public double getNorm1() { + return FastMath.abs(x) + FastMath.abs(y); + } + + /** {@inheritDoc} */ + public double getNorm() { + return FastMath.sqrt (x * x + y * y); + } + + /** {@inheritDoc} */ + public double getNormSq() { + return x * x + y * y; + } + + /** {@inheritDoc} */ + public double getNormInf() { + return FastMath.max(FastMath.abs(x), FastMath.abs(y)); + } + + /** {@inheritDoc} */ + public Vector2D add(Vector v) { + Vector2D v2 = (Vector2D) v; + return new Vector2D(x + v2.getX(), y + v2.getY()); + } + + /** {@inheritDoc} */ + public Vector2D add(double factor, Vector v) { + Vector2D v2 = (Vector2D) v; + return new Vector2D(x + factor * v2.getX(), y + factor * v2.getY()); + } + + /** {@inheritDoc} */ + public Vector2D subtract(Vector p) { + Vector2D p3 = (Vector2D) p; + return new Vector2D(x - p3.x, y - p3.y); + } + + /** {@inheritDoc} */ + public Vector2D subtract(double factor, Vector v) { + Vector2D v2 = (Vector2D) v; + return new Vector2D(x - factor * v2.getX(), y - factor * v2.getY()); + } + + /** {@inheritDoc} */ + public Vector2D normalize() throws MathArithmeticException { + double s = getNorm(); + if (s == 0) { + throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); + } + return scalarMultiply(1 / s); + } + + /** Compute the angular separation between two vectors. + *

    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.

    + * @param v1 first vector + * @param v2 second vector + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static double angle(Vector2D v1, Vector2D v2) throws MathArithmeticException { + + double normProduct = v1.getNorm() * v2.getNorm(); + if (normProduct == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + double dot = v1.dotProduct(v2); + double threshold = normProduct * 0.9999; + if ((dot < -threshold) || (dot > threshold)) { + // the vectors are almost aligned, compute using the sine + final double n = FastMath.abs(MathArrays.linearCombination(v1.x, v2.y, -v1.y, v2.x)); + if (dot >= 0) { + return FastMath.asin(n / normProduct); + } + return FastMath.PI - FastMath.asin(n / normProduct); + } + + // the vectors are sufficiently separated to use the cosine + return FastMath.acos(dot / normProduct); + + } + + /** {@inheritDoc} */ + public Vector2D negate() { + return new Vector2D(-x, -y); + } + + /** {@inheritDoc} */ + public Vector2D scalarMultiply(double a) { + return new Vector2D(a * x, a * y); + } + + /** {@inheritDoc} */ + public boolean isNaN() { + return Double.isNaN(x) || Double.isNaN(y); + } + + /** {@inheritDoc} */ + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y)); + } + + /** {@inheritDoc} */ + public double distance1(Vector p) { + Vector2D p3 = (Vector2D) p; + final double dx = FastMath.abs(p3.x - x); + final double dy = FastMath.abs(p3.y - y); + return dx + dy; + } + + /** {@inheritDoc} + */ + public double distance(Vector p) { + return distance((Point) p); + } + + /** {@inheritDoc} */ + public double distance(Point p) { + Vector2D p3 = (Vector2D) p; + final double dx = p3.x - x; + final double dy = p3.y - y; + return FastMath.sqrt(dx * dx + dy * dy); + } + + /** {@inheritDoc} */ + public double distanceInf(Vector p) { + Vector2D p3 = (Vector2D) p; + final double dx = FastMath.abs(p3.x - x); + final double dy = FastMath.abs(p3.y - y); + return FastMath.max(dx, dy); + } + + /** {@inheritDoc} */ + public double distanceSq(Vector p) { + Vector2D p3 = (Vector2D) p; + final double dx = p3.x - x; + final double dy = p3.y - y; + return dx * dx + dy * dy; + } + + /** {@inheritDoc} */ + public double dotProduct(final Vector v) { + final Vector2D v2 = (Vector2D) v; + return MathArrays.linearCombination(x, v2.x, y, v2.y); + } + + /** + * Compute the cross-product of the instance and the given points. + *

    + * The cross product can be used to determine the location of a point + * with regard to the line formed by (p1, p2) and is calculated as: + * \[ + * P = (x_2 - x_1)(y_3 - y_1) - (y_2 - y_1)(x_3 - x_1) + * \] + * with \(p3 = (x_3, y_3)\) being this instance. + *

    + * If the result is 0, the points are collinear, i.e. lie on a single straight line L; + * if it is positive, this point lies to the left, otherwise to the right of the line + * formed by (p1, p2). + * + * @param p1 first point of the line + * @param p2 second point of the line + * @return the cross-product + * + * @see Cross product (Wikipedia) + */ + public double crossProduct(final Vector2D p1, final Vector2D p2) { + final double x1 = p2.getX() - p1.getX(); + final double y1 = getY() - p1.getY(); + final double x2 = getX() - p1.getX(); + final double y2 = p2.getY() - p1.getY(); + return MathArrays.linearCombination(x1, y1, -x2, y2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNorm() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the distance between p1 and p2 according to the L2 norm + */ + public static double distance(Vector2D p1, Vector2D p2) { + return p1.distance(p2); + } + + /** Compute the distance between two vectors according to the L norm. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNormInf() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the distance between p1 and p2 according to the L norm + */ + public static double distanceInf(Vector2D p1, Vector2D p2) { + return p1.distanceInf(p2); + } + + /** Compute the square of the distance between two vectors. + *

    Calling this method is equivalent to calling: + * p1.subtract(p2).getNormSq() except that no intermediate + * vector is built

    + * @param p1 first vector + * @param p2 second vector + * @return the square of the distance between p1 and p2 + */ + public static double distanceSq(Vector2D p1, Vector2D p2) { + return p1.distanceSq(p2); + } + + /** + * Test for the equality of two 2D vectors. + *

    + * If all coordinates of two 2D vectors are exactly the same, and none are + * Double.NaN, the two 2D vectors are considered to be equal. + *

    + *

    + * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) coordinates of the + * 2D vector are equal to Double.NaN, the 2D vector is equal to + * {@link #NaN}. + *

    + * + * @param other Object to test for equality to this + * @return true if two 2D vector objects are equal, false if + * object is null, not an instance of Vector2D, or + * not equal to this Vector2D instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof Vector2D) { + final Vector2D rhs = (Vector2D)other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return (x == rhs.x) && (y == rhs.y); + } + return false; + } + + /** + * Get a hashCode for the 2D vector. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 542; + } + return 122 * (76 * MathUtils.hash(x) + MathUtils.hash(y)); + } + + /** Get a string representation of this vector. + * @return a string representation of this vector + */ + @Override + public String toString() { + return Vector2DFormat.getInstance().format(this); + } + + /** {@inheritDoc} */ + public String toString(final NumberFormat format) { + return new Vector2DFormat(format).format(this); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java new file mode 100644 index 000000000..f3db171a3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.VectorFormat; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a 2D vector in components list format "{x; y}". + *

    The prefix and suffix "{" and "}" and the separator "; " can be replaced by + * any user-defined strings. The number format for components can be configured.

    + *

    White space is ignored at parse time, even if it is in the prefix, suffix + * or separator specifications. So even if the default separator does include a space + * character that is used at format time, both input string "{1;1}" and + * " { 1 ; 1 } " will be parsed without error and the same vector will be + * returned. In the second case, however, the parse position after parsing will be + * just after the closing curly brace, i.e. just before the trailing space.

    + *

    Note: using "," as a separator may interfere with the grouping separator + * of the default {@link NumberFormat} for the current locale. Thus it is advised + * to use a {@link NumberFormat} instance with disabled grouping in such a case.

    + * + * @since 3.0 + */ +public class Vector2DFormat extends VectorFormat { + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and separator: + * "{", "}", and "; " and the default number format for components.

    + */ + public Vector2DFormat() { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + public Vector2DFormat(final NumberFormat format) { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + */ + public Vector2DFormat(final String prefix, final String suffix, + final String separator) { + super(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + * @param format the custom format for components. + */ + public Vector2DFormat(final String prefix, final String suffix, + final String separator, final NumberFormat format) { + super(prefix, suffix, separator, format); + } + + /** + * Returns the default 2D vector format for the current locale. + * @return the default 2D vector format. + */ + public static Vector2DFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default 2D vector format for the given locale. + * @param locale the specific locale used by the format. + * @return the 2D vector format specific to the given locale. + */ + public static Vector2DFormat getInstance(final Locale locale) { + return new Vector2DFormat(CompositeFormat.getDefaultNumberFormat(locale)); + } + + /** {@inheritDoc} */ + @Override + public StringBuffer format(final Vector vector, final StringBuffer toAppendTo, + final FieldPosition pos) { + final Vector2D p2 = (Vector2D) vector; + return format(toAppendTo, pos, p2.getX(), p2.getY()); + } + + /** {@inheritDoc} */ + @Override + public Vector2D parse(final String source) throws MathParseException { + ParsePosition parsePosition = new ParsePosition(0); + Vector2D result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + Vector2D.class); + } + return result; + } + + /** {@inheritDoc} */ + @Override + public Vector2D parse(final String source, final ParsePosition pos) { + final double[] coordinates = parseCoordinates(2, source, pos); + if (coordinates == null) { + return null; + } + return new Vector2D(coordinates[0], coordinates[1]); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java new file mode 100644 index 000000000..e99a57f1d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Abstract base class for convex hull generators in the two-dimensional euclidean space. + * + * @since 3.3 + */ +abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1e-10; + + /** Tolerance below which points are considered identical. */ + private final double tolerance; + + /** + * Indicates if collinear points on the hull shall be present in the output. + * If {@code false}, only the extreme points are added to the hull. + */ + private final boolean includeCollinearPoints; + + /** + * Simple constructor. + *

    + * The default tolerance (1e-10) will be used to determine identical points. + * + * @param includeCollinearPoints indicates if collinear points on the hull shall be + * added as hull vertices + */ + protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints) { + this(includeCollinearPoints, DEFAULT_TOLERANCE); + } + + /** + * Simple constructor. + * + * @param includeCollinearPoints indicates if collinear points on the hull shall be + * added as hull vertices + * @param tolerance tolerance below which points are considered identical + */ + protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints, final double tolerance) { + this.includeCollinearPoints = includeCollinearPoints; + this.tolerance = tolerance; + } + + /** + * Get the tolerance below which points are considered identical. + * @return the tolerance below which points are considered identical + */ + public double getTolerance() { + return tolerance; + } + + /** + * Returns if collinear points on the hull will be added as hull vertices. + * @return {@code true} if collinear points are added as hull vertices, or {@code false} + * if only extreme points are present. + */ + public boolean isIncludeCollinearPoints() { + return includeCollinearPoints; + } + + /** {@inheritDoc} */ + public ConvexHull2D generate(final Collection points) + throws NullArgumentException, ConvergenceException { + // check for null points + MathUtils.checkNotNull(points); + + Collection hullVertices = null; + if (points.size() < 2) { + hullVertices = points; + } else { + hullVertices = findHullVertices(points); + } + + try { + return new ConvexHull2D(hullVertices.toArray(new Vector2D[hullVertices.size()]), + tolerance); + } catch (MathIllegalArgumentException e) { + // the hull vertices may not form a convex hull if the tolerance value is to large + throw new ConvergenceException(); + } + } + + /** + * Find the convex hull vertices from the set of input points. + * @param points the set of input points + * @return the convex hull vertices in CCW winding + */ + protected abstract Collection findHullVertices(Collection points); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java new file mode 100644 index 000000000..5186cee65 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; + +/** + * A simple heuristic to improve the performance of convex hull algorithms. + *

    + * The heuristic is based on the idea of a convex quadrilateral, which is formed by + * four points with the lowest and highest x / y coordinates. Any point that lies inside + * this quadrilateral can not be part of the convex hull and can thus be safely discarded + * before generating the convex hull itself. + *

    + * The complexity of the operation is O(n), and may greatly improve the time it takes to + * construct the convex hull afterwards, depending on the point distribution. + * + * @see + * Akl-Toussaint heuristic (Wikipedia) + * @since 3.3 + */ +public final class AklToussaintHeuristic { + + /** Hide utility constructor. */ + private AklToussaintHeuristic() { + } + + /** + * Returns a point set that is reduced by all points for which it is safe to assume + * that they are not part of the convex hull. + * + * @param points the original point set + * @return a reduced point set, useful as input for convex hull algorithms + */ + public static Collection reducePoints(final Collection points) { + + // find the leftmost point + int size = 0; + Vector2D minX = null; + Vector2D maxX = null; + Vector2D minY = null; + Vector2D maxY = null; + for (Vector2D p : points) { + if (minX == null || p.getX() < minX.getX()) { + minX = p; + } + if (maxX == null || p.getX() > maxX.getX()) { + maxX = p; + } + if (minY == null || p.getY() < minY.getY()) { + minY = p; + } + if (maxY == null || p.getY() > maxY.getY()) { + maxY = p; + } + size++; + } + + if (size < 4) { + return points; + } + + final List quadrilateral = buildQuadrilateral(minY, maxX, maxY, minX); + // if the quadrilateral is not well formed, e.g. only 2 points, do not attempt to reduce + if (quadrilateral.size() < 3) { + return points; + } + + final List reducedPoints = new ArrayList(quadrilateral); + for (final Vector2D p : points) { + // check all points if they are within the quadrilateral + // in which case they can not be part of the convex hull + if (!insideQuadrilateral(p, quadrilateral)) { + reducedPoints.add(p); + } + } + + return reducedPoints; + } + + /** + * Build the convex quadrilateral with the found corner points (with min/max x/y coordinates). + * + * @param points the respective points with min/max x/y coordinate + * @return the quadrilateral + */ + private static List buildQuadrilateral(final Vector2D... points) { + List quadrilateral = new ArrayList(); + for (Vector2D p : points) { + if (!quadrilateral.contains(p)) { + quadrilateral.add(p); + } + } + return quadrilateral; + } + + /** + * Checks if the given point is located within the convex quadrilateral. + * @param point the point to check + * @param quadrilateralPoints the convex quadrilateral, represented by 4 points + * @return {@code true} if the point is inside the quadrilateral, {@code false} otherwise + */ + private static boolean insideQuadrilateral(final Vector2D point, + final List quadrilateralPoints) { + + Vector2D p1 = quadrilateralPoints.get(0); + Vector2D p2 = quadrilateralPoints.get(1); + + if (point.equals(p1) || point.equals(p2)) { + return true; + } + + // get the location of the point relative to the first two vertices + final double last = point.crossProduct(p1, p2); + final int size = quadrilateralPoints.size(); + // loop through the rest of the vertices + for (int i = 1; i < size; i++) { + p1 = p2; + p2 = quadrilateralPoints.get((i + 1) == size ? 0 : i + 1); + + if (point.equals(p1) || point.equals(p2)) { + return true; + } + + // do side of line test: multiply the last location with this location + // if they are the same sign then the operation will yield a positive result + // -x * -y = +xy, x * y = +xy, -x * y = -xy, x * -y = -xy + if (last * point.crossProduct(p1, p2) < 0) { + return false; + } + } + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java new file mode 100644 index 000000000..27dabbf49 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Segment; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.RegionFactory; +import com.fr.third.org.apache.commons.math3.geometry.hull.ConvexHull; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class represents a convex hull in an two-dimensional euclidean space. + * + * @since 3.3 + */ +public class ConvexHull2D implements ConvexHull, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140129L; + + /** Vertices of the hull. */ + private final Vector2D[] vertices; + + /** Tolerance threshold used during creation of the hull vertices. */ + private final double tolerance; + + /** + * Line segments of the hull. + * The array is not serialized and will be created from the vertices on first access. + */ + private transient Segment[] lineSegments; + + /** + * Simple constructor. + * @param vertices the vertices of the convex hull, must be ordered + * @param tolerance tolerance below which points are considered identical + * @throws MathIllegalArgumentException if the vertices do not form a convex hull + */ + public ConvexHull2D(final Vector2D[] vertices, final double tolerance) + throws MathIllegalArgumentException { + + // assign tolerance as it will be used by the isConvex method + this.tolerance = tolerance; + + if (!isConvex(vertices)) { + throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX); + } + + this.vertices = vertices.clone(); + } + + /** + * Checks whether the given hull vertices form a convex hull. + * @param hullVertices the hull vertices + * @return {@code true} if the vertices form a convex hull, {@code false} otherwise + */ + private boolean isConvex(final Vector2D[] hullVertices) { + if (hullVertices.length < 3) { + return true; + } + + int sign = 0; + for (int i = 0; i < hullVertices.length; i++) { + final Vector2D p1 = hullVertices[i == 0 ? hullVertices.length - 1 : i - 1]; + final Vector2D p2 = hullVertices[i]; + final Vector2D p3 = hullVertices[i == hullVertices.length - 1 ? 0 : i + 1]; + + final Vector2D d1 = p2.subtract(p1); + final Vector2D d2 = p3.subtract(p2); + + final double crossProduct = MathArrays.linearCombination(d1.getX(), d2.getY(), -d1.getY(), d2.getX()); + final int cmp = Precision.compareTo(crossProduct, 0.0, tolerance); + // in case of collinear points the cross product will be zero + if (cmp != 0.0) { + if (sign != 0.0 && cmp != sign) { + return false; + } + sign = cmp; + } + } + + return true; + } + + /** {@inheritDoc} */ + public Vector2D[] getVertices() { + return vertices.clone(); + } + + /** + * Get the line segments of the convex hull, ordered. + * @return the line segments of the convex hull + */ + public Segment[] getLineSegments() { + return retrieveLineSegments().clone(); + } + + /** + * Retrieve the line segments from the cached array or create them if needed. + * + * @return the array of line segments + */ + private Segment[] retrieveLineSegments() { + if (lineSegments == null) { + // construct the line segments - handle special cases of 1 or 2 points + final int size = vertices.length; + if (size <= 1) { + this.lineSegments = new Segment[0]; + } else if (size == 2) { + this.lineSegments = new Segment[1]; + final Vector2D p1 = vertices[0]; + final Vector2D p2 = vertices[1]; + this.lineSegments[0] = new Segment(p1, p2, new Line(p1, p2, tolerance)); + } else { + this.lineSegments = new Segment[size]; + Vector2D firstPoint = null; + Vector2D lastPoint = null; + int index = 0; + for (Vector2D point : vertices) { + if (lastPoint == null) { + firstPoint = point; + lastPoint = point; + } else { + this.lineSegments[index++] = + new Segment(lastPoint, point, new Line(lastPoint, point, tolerance)); + lastPoint = point; + } + } + this.lineSegments[index] = + new Segment(lastPoint, firstPoint, new Line(lastPoint, firstPoint, tolerance)); + } + } + return lineSegments; + } + + /** {@inheritDoc} */ + public Region createRegion() throws InsufficientDataException { + if (vertices.length < 3) { + throw new InsufficientDataException(); + } + final RegionFactory factory = new RegionFactory(); + final Segment[] segments = retrieveLineSegments(); + final Line[] lineArray = new Line[segments.length]; + for (int i = 0; i < segments.length; i++) { + lineArray[i] = segments[i].getLine(); + } + return factory.buildConvex(lineArray); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java new file mode 100644 index 000000000..fd1b53e57 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.geometry.hull.ConvexHullGenerator; + +/** + * Interface for convex hull generators in the two-dimensional euclidean space. + * + * @since 3.3 + */ +public interface ConvexHullGenerator2D extends ConvexHullGenerator { + + /** {@inheritDoc} */ + ConvexHull2D generate(Collection points) throws NullArgumentException, ConvergenceException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java new file mode 100644 index 000000000..926dd7614 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Line; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Implements Andrew's monotone chain method to generate the convex hull of a finite set of + * points in the two-dimensional euclidean space. + *

    + * The runtime complexity is O(n log n), with n being the number of input points. If the + * point set is already sorted (by x-coordinate), the runtime complexity is O(n). + *

    + * The implementation is not sensitive to collinear points on the hull. The parameter + * {@code includeCollinearPoints} allows to control the behavior with regard to collinear points. + * If {@code true}, all points on the boundary of the hull will be added to the hull vertices, + * otherwise only the extreme points will be present. By default, collinear points are not added + * as hull vertices. + *

    + * The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine + * identical and collinear points. + * + * @see + * Andrew's monotone chain algorithm (Wikibooks) + * @since 3.3 + */ +public class MonotoneChain extends AbstractConvexHullGenerator2D { + + /** + * Create a new MonotoneChain instance. + */ + public MonotoneChain() { + this(false); + } + + /** + * Create a new MonotoneChain instance. + * @param includeCollinearPoints whether collinear points shall be added as hull vertices + */ + public MonotoneChain(final boolean includeCollinearPoints) { + super(includeCollinearPoints); + } + + /** + * Create a new MonotoneChain instance. + * @param includeCollinearPoints whether collinear points shall be added as hull vertices + * @param tolerance tolerance below which points are considered identical + */ + public MonotoneChain(final boolean includeCollinearPoints, final double tolerance) { + super(includeCollinearPoints, tolerance); + } + + /** {@inheritDoc} */ + @Override + public Collection findHullVertices(final Collection points) { + + final List pointsSortedByXAxis = new ArrayList(points); + + // sort the points in increasing order on the x-axis + Collections.sort(pointsSortedByXAxis, new Comparator() { + /** {@inheritDoc} */ + public int compare(final Vector2D o1, final Vector2D o2) { + final double tolerance = getTolerance(); + // need to take the tolerance value into account, otherwise collinear points + // will not be handled correctly when building the upper/lower hull + final int diff = Precision.compareTo(o1.getX(), o2.getX(), tolerance); + if (diff == 0) { + return Precision.compareTo(o1.getY(), o2.getY(), tolerance); + } else { + return diff; + } + } + }); + + // build lower hull + final List lowerHull = new ArrayList(); + for (Vector2D p : pointsSortedByXAxis) { + updateHull(p, lowerHull); + } + + // build upper hull + final List upperHull = new ArrayList(); + for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) { + final Vector2D p = pointsSortedByXAxis.get(idx); + updateHull(p, upperHull); + } + + // concatenate the lower and upper hulls + // the last point of each list is omitted as it is repeated at the beginning of the other list + final List hullVertices = new ArrayList(lowerHull.size() + upperHull.size() - 2); + for (int idx = 0; idx < lowerHull.size() - 1; idx++) { + hullVertices.add(lowerHull.get(idx)); + } + for (int idx = 0; idx < upperHull.size() - 1; idx++) { + hullVertices.add(upperHull.get(idx)); + } + + // special case: if the lower and upper hull may contain only 1 point if all are identical + if (hullVertices.isEmpty() && ! lowerHull.isEmpty()) { + hullVertices.add(lowerHull.get(0)); + } + + return hullVertices; + } + + /** + * Update the partial hull with the current point. + * + * @param point the current point + * @param hull the partial hull + */ + private void updateHull(final Vector2D point, final List hull) { + final double tolerance = getTolerance(); + + if (hull.size() == 1) { + // ensure that we do not add an identical point + final Vector2D p1 = hull.get(0); + if (p1.distance(point) < tolerance) { + return; + } + } + + while (hull.size() >= 2) { + final int size = hull.size(); + final Vector2D p1 = hull.get(size - 2); + final Vector2D p2 = hull.get(size - 1); + + final double offset = new Line(p1, p2, tolerance).getOffset(point); + if (FastMath.abs(offset) < tolerance) { + // the point is collinear to the line (p1, p2) + + final double distanceToCurrent = p1.distance(point); + if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) { + // the point is assumed to be identical to either p1 or p2 + return; + } + + final double distanceToLast = p1.distance(p2); + if (isIncludeCollinearPoints()) { + final int index = distanceToCurrent < distanceToLast ? size - 1 : size; + hull.add(index, point); + } else { + if (distanceToCurrent > distanceToLast) { + hull.remove(size - 1); + hull.add(point); + } + } + return; + } else if (offset > 0) { + hull.remove(size - 1); + } else { + break; + } + } + hull.add(point); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java new file mode 100644 index 000000000..720769748 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides algorithms to generate the convex hull + * for a set of points in an two-dimensional euclidean space. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.hull; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/package-info.java new file mode 100644 index 000000000..139a633bb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/euclidean/twod/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides basic 2D geometry components. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.euclidean.twod; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHull.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHull.java new file mode 100644 index 000000000..4f1ecfce1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHull.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.hull; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; + +/** + * This class represents a convex hull. + * + * @param Space type. + * @param

    Point type. + * @since 3.3 + */ +public interface ConvexHull> extends Serializable { + + /** + * Get the vertices of the convex hull. + * @return vertices of the convex hull + */ + P[] getVertices(); + + /** + * Returns a new region that is enclosed by the convex hull. + * @return the region enclosed by the convex hull + * @throws InsufficientDataException if the number of vertices is not enough to + * build a region in the respective space + */ + Region createRegion() throws InsufficientDataException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java new file mode 100644 index 000000000..adcc10933 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.hull; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** + * Interface for convex hull generators. + * + * @param Type of the {@link Space} + * @param

    Type of the {@link Point} + * + * @see Convex Hull (Wikipedia) + * @see Convex Hull (MathWorld) + * + * @since 3.3 + */ +public interface ConvexHullGenerator> { + + /** + * Builds the convex hull from the set of input points. + * + * @param points the set of input points + * @return the convex hull + * @throws NullArgumentException if the input collection is {@code null} + * @throws ConvergenceException if generator fails to generate a convex hull for + * the given set of input points + */ + ConvexHull generate(Collection

    points) throws NullArgumentException, ConvergenceException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/package-info.java new file mode 100644 index 000000000..b801531b6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/hull/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides interfaces and classes related to the convex hull problem. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.hull; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/package-info.java new file mode 100644 index 000000000..282c21c46 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package is the top level package for geometry. It provides only a few interfaces + * related to vectorial/affine spaces that are implemented in sub-packages. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java new file mode 100644 index 000000000..29848686e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java @@ -0,0 +1,540 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Vector; + +/** Abstract class for all regions, independently of geometry type or dimension. + + * @param Type of the space. + * @param Type of the sub-space. + + * @since 3.0 + */ +public abstract class AbstractRegion implements Region { + + /** Inside/Outside BSP tree. */ + private BSPTree tree; + + /** Tolerance below which points are considered to belong to hyperplanes. */ + private final double tolerance; + + /** Size of the instance. */ + private double size; + + /** Barycenter. */ + private Point barycenter; + + /** Build a region representing the whole space. + * @param tolerance tolerance below which points are considered identical. + */ + protected AbstractRegion(final double tolerance) { + this.tree = new BSPTree(Boolean.TRUE); + this.tolerance = tolerance; + } + + /** Build a region from an inside/outside BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The + * tree also must have either null internal nodes or + * internal nodes representing the boundary as specified in the + * {@link #getTree getTree} method).

    + * @param tree inside/outside BSP tree representing the region + * @param tolerance tolerance below which points are considered identical. + */ + protected AbstractRegion(final BSPTree tree, final double tolerance) { + this.tree = tree; + this.tolerance = tolerance; + } + + /** Build a Region from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoints polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link #checkPoint(Point) checkPoint} method will not be + * meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @param tolerance tolerance below which points are considered identical. + */ + protected AbstractRegion(final Collection> boundary, final double tolerance) { + + this.tolerance = tolerance; + + if (boundary.size() == 0) { + + // the tree represents the whole space + tree = new BSPTree(Boolean.TRUE); + + } else { + + // sort the boundary elements in decreasing size order + // (we don't want equal size elements to be removed, so + // we use a trick to fool the TreeSet) + final TreeSet> ordered = new TreeSet>(new Comparator>() { + /** {@inheritDoc} */ + public int compare(final SubHyperplane o1, final SubHyperplane o2) { + final double size1 = o1.getSize(); + final double size2 = o2.getSize(); + return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1); + } + }); + ordered.addAll(boundary); + + // build the tree top-down + tree = new BSPTree(); + insertCuts(tree, ordered); + + // set up the inside/outside flags + tree.visit(new BSPTreeVisitor() { + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.PLUS_SUB_MINUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + if (node.getParent() == null || node == node.getParent().getMinus()) { + node.setAttribute(Boolean.TRUE); + } else { + node.setAttribute(Boolean.FALSE); + } + } + }); + + } + + } + + /** Build a convex region from an array of bounding hyperplanes. + * @param hyperplanes array of bounding hyperplanes (if null, an + * empty region will be built) + * @param tolerance tolerance below which points are considered identical. + */ + public AbstractRegion(final Hyperplane[] hyperplanes, final double tolerance) { + this.tolerance = tolerance; + if ((hyperplanes == null) || (hyperplanes.length == 0)) { + tree = new BSPTree(Boolean.FALSE); + } else { + + // use the first hyperplane to build the right class + tree = hyperplanes[0].wholeSpace().getTree(false); + + // chop off parts of the space + BSPTree node = tree; + node.setAttribute(Boolean.TRUE); + for (final Hyperplane hyperplane : hyperplanes) { + if (node.insertCut(hyperplane)) { + node.setAttribute(null); + node.getPlus().setAttribute(Boolean.FALSE); + node = node.getMinus(); + node.setAttribute(Boolean.TRUE); + } + } + + } + + } + + /** {@inheritDoc} */ + public abstract AbstractRegion buildNew(BSPTree newTree); + + /** Get the tolerance below which points are considered to belong to hyperplanes. + * @return tolerance below which points are considered to belong to hyperplanes + */ + public double getTolerance() { + return tolerance; + } + + /** Recursively build a tree by inserting cut sub-hyperplanes. + * @param node current tree node (it is a leaf node at the beginning + * of the call) + * @param boundary collection of edges belonging to the cell defined + * by the node + */ + private void insertCuts(final BSPTree node, final Collection> boundary) { + + final Iterator> iterator = boundary.iterator(); + + // build the current level + Hyperplane inserted = null; + while ((inserted == null) && iterator.hasNext()) { + inserted = iterator.next().getHyperplane(); + if (!node.insertCut(inserted.copySelf())) { + inserted = null; + } + } + + if (!iterator.hasNext()) { + return; + } + + // distribute the remaining edges in the two sub-trees + final ArrayList> plusList = new ArrayList>(); + final ArrayList> minusList = new ArrayList>(); + while (iterator.hasNext()) { + final SubHyperplane other = iterator.next(); + final SubHyperplane.SplitSubHyperplane split = other.split(inserted); + switch (split.getSide()) { + case PLUS: + plusList.add(other); + break; + case MINUS: + minusList.add(other); + break; + case BOTH: + plusList.add(split.getPlus()); + minusList.add(split.getMinus()); + break; + default: + // ignore the sub-hyperplanes belonging to the cut hyperplane + } + } + + // recurse through lower levels + insertCuts(node.getPlus(), plusList); + insertCuts(node.getMinus(), minusList); + + } + + /** {@inheritDoc} */ + public AbstractRegion copySelf() { + return buildNew(tree.copySelf()); + } + + /** {@inheritDoc} */ + public boolean isEmpty() { + return isEmpty(tree); + } + + /** {@inheritDoc} */ + public boolean isEmpty(final BSPTree node) { + + // we use a recursive function rather than the BSPTreeVisitor + // interface because we can stop visiting the tree as soon as we + // have found an inside cell + + if (node.getCut() == null) { + // if we find an inside node, the region is not empty + return !((Boolean) node.getAttribute()); + } + + // check both sides of the sub-tree + return isEmpty(node.getMinus()) && isEmpty(node.getPlus()); + + } + + /** {@inheritDoc} */ + public boolean isFull() { + return isFull(tree); + } + + /** {@inheritDoc} */ + public boolean isFull(final BSPTree node) { + + // we use a recursive function rather than the BSPTreeVisitor + // interface because we can stop visiting the tree as soon as we + // have found an outside cell + + if (node.getCut() == null) { + // if we find an outside node, the region does not cover full space + return (Boolean) node.getAttribute(); + } + + // check both sides of the sub-tree + return isFull(node.getMinus()) && isFull(node.getPlus()); + + } + + /** {@inheritDoc} */ + public boolean contains(final Region region) { + return new RegionFactory().difference(region, this).isEmpty(); + } + + /** {@inheritDoc} + * @since 3.3 + */ + public BoundaryProjection projectToBoundary(final Point point) { + final BoundaryProjector projector = new BoundaryProjector(point); + getTree(true).visit(projector); + return projector.getProjection(); + } + + /** Check a point with respect to the region. + * @param point point to check + * @return a code representing the point status: either {@link + * Region.Location#INSIDE}, {@link Region.Location#OUTSIDE} or + * {@link Region.Location#BOUNDARY} + */ + public Location checkPoint(final Vector point) { + return checkPoint((Point) point); + } + + /** {@inheritDoc} */ + public Location checkPoint(final Point point) { + return checkPoint(tree, point); + } + + /** Check a point with respect to the region starting at a given node. + * @param node root node of the region + * @param point point to check + * @return a code representing the point status: either {@link + * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE + * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY} + */ + protected Location checkPoint(final BSPTree node, final Vector point) { + return checkPoint(node, (Point) point); + } + + /** Check a point with respect to the region starting at a given node. + * @param node root node of the region + * @param point point to check + * @return a code representing the point status: either {@link + * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE + * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY} + */ + protected Location checkPoint(final BSPTree node, final Point point) { + final BSPTree cell = node.getCell(point, tolerance); + if (cell.getCut() == null) { + // the point is in the interior of a cell, just check the attribute + return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE; + } + + // the point is on a cut-sub-hyperplane, is it on a boundary ? + final Location minusCode = checkPoint(cell.getMinus(), point); + final Location plusCode = checkPoint(cell.getPlus(), point); + return (minusCode == plusCode) ? minusCode : Location.BOUNDARY; + + } + + /** {@inheritDoc} */ + public BSPTree getTree(final boolean includeBoundaryAttributes) { + if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) { + // compute the boundary attributes + tree.visit(new BoundaryBuilder()); + } + return tree; + } + + /** {@inheritDoc} */ + public double getBoundarySize() { + final BoundarySizeVisitor visitor = new BoundarySizeVisitor(); + getTree(true).visit(visitor); + return visitor.getSize(); + } + + /** {@inheritDoc} */ + public double getSize() { + if (barycenter == null) { + computeGeometricalProperties(); + } + return size; + } + + /** Set the size of the instance. + * @param size size of the instance + */ + protected void setSize(final double size) { + this.size = size; + } + + /** {@inheritDoc} */ + public Point getBarycenter() { + if (barycenter == null) { + computeGeometricalProperties(); + } + return barycenter; + } + + /** Set the barycenter of the instance. + * @param barycenter barycenter of the instance + */ + protected void setBarycenter(final Vector barycenter) { + setBarycenter((Point) barycenter); + } + + /** Set the barycenter of the instance. + * @param barycenter barycenter of the instance + */ + protected void setBarycenter(final Point barycenter) { + this.barycenter = barycenter; + } + + /** Compute some geometrical properties. + *

    The properties to compute are the barycenter and the size.

    + */ + protected abstract void computeGeometricalProperties(); + + /** {@inheritDoc} */ + @Deprecated + public Side side(final Hyperplane hyperplane) { + final InsideFinder finder = new InsideFinder(this); + finder.recurseSides(tree, hyperplane.wholeHyperplane()); + return finder.plusFound() ? + (finder.minusFound() ? Side.BOTH : Side.PLUS) : + (finder.minusFound() ? Side.MINUS : Side.HYPER); + } + + /** {@inheritDoc} */ + public SubHyperplane intersection(final SubHyperplane sub) { + return recurseIntersection(tree, sub); + } + + /** Recursively compute the parts of a sub-hyperplane that are + * contained in the region. + * @param node current BSP tree node + * @param sub sub-hyperplane traversing the region + * @return filtered sub-hyperplane + */ + private SubHyperplane recurseIntersection(final BSPTree node, final SubHyperplane sub) { + + if (node.getCut() == null) { + return (Boolean) node.getAttribute() ? sub.copySelf() : null; + } + + final Hyperplane hyperplane = node.getCut().getHyperplane(); + final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane); + if (split.getPlus() != null) { + if (split.getMinus() != null) { + // both sides + final SubHyperplane plus = recurseIntersection(node.getPlus(), split.getPlus()); + final SubHyperplane minus = recurseIntersection(node.getMinus(), split.getMinus()); + if (plus == null) { + return minus; + } else if (minus == null) { + return plus; + } else { + return plus.reunite(minus); + } + } else { + // only on plus side + return recurseIntersection(node.getPlus(), sub); + } + } else if (split.getMinus() != null) { + // only on minus side + return recurseIntersection(node.getMinus(), sub); + } else { + // on hyperplane + return recurseIntersection(node.getPlus(), + recurseIntersection(node.getMinus(), sub)); + } + + } + + /** Transform a region. + *

    Applying a transform to a region consist in applying the + * transform to all the hyperplanes of the underlying BSP tree and + * of the boundary (and also to the sub-hyperplanes embedded in + * these hyperplanes) and to the barycenter. The instance is not + * modified, a new instance is built.

    + * @param transform transform to apply + * @return a new region, resulting from the application of the + * transform to the instance + */ + public AbstractRegion applyTransform(final Transform transform) { + + // transform the tree, except for boundary attribute splitters + final Map, BSPTree> map = new HashMap, BSPTree>(); + final BSPTree transformedTree = recurseTransform(getTree(false), transform, map); + + // set up the boundary attributes splitters + for (final Map.Entry, BSPTree> entry : map.entrySet()) { + if (entry.getKey().getCut() != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute original = (BoundaryAttribute) entry.getKey().getAttribute(); + if (original != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute transformed = (BoundaryAttribute) entry.getValue().getAttribute(); + for (final BSPTree splitter : original.getSplitters()) { + transformed.getSplitters().add(map.get(splitter)); + } + } + } + } + + return buildNew(transformedTree); + + } + + /** Recursively transform an inside/outside BSP-tree. + * @param node current BSP tree node + * @param transform transform to apply + * @param map transformed nodes map + * @return a new tree + */ + @SuppressWarnings("unchecked") + private BSPTree recurseTransform(final BSPTree node, final Transform transform, + final Map, BSPTree> map) { + + final BSPTree transformedNode; + if (node.getCut() == null) { + transformedNode = new BSPTree(node.getAttribute()); + } else { + + final SubHyperplane sub = node.getCut(); + final SubHyperplane tSub = ((AbstractSubHyperplane) sub).applyTransform(transform); + BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); + if (attribute != null) { + final SubHyperplane tPO = (attribute.getPlusOutside() == null) ? + null : ((AbstractSubHyperplane) attribute.getPlusOutside()).applyTransform(transform); + final SubHyperplane tPI = (attribute.getPlusInside() == null) ? + null : ((AbstractSubHyperplane) attribute.getPlusInside()).applyTransform(transform); + // we start with an empty list of splitters, it will be filled in out of recursion + attribute = new BoundaryAttribute(tPO, tPI, new NodesSet()); + } + + transformedNode = new BSPTree(tSub, + recurseTransform(node.getPlus(), transform, map), + recurseTransform(node.getMinus(), transform, map), + attribute); + } + + map.put(node, transformedNode); + return transformedNode; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java new file mode 100644 index 000000000..a76c61ff6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This class implements the dimension-independent parts of {@link SubHyperplane}. + + *

    sub-hyperplanes are obtained when parts of an {@link + * Hyperplane hyperplane} are chopped off by other hyperplanes that + * intersect it. The remaining part is a convex region. Such objects + * appear in {@link BSPTree BSP trees} as the intersection of a cut + * hyperplane with the convex region which it splits, the chopping + * hyperplanes are the cut hyperplanes closer to the tree root.

    + + * @param Type of the embedding space. + * @param Type of the embedded sub-space. + + * @since 3.0 + */ +public abstract class AbstractSubHyperplane + implements SubHyperplane { + + /** Underlying hyperplane. */ + private final Hyperplane hyperplane; + + /** Remaining region of the hyperplane. */ + private final Region remainingRegion; + + /** Build a sub-hyperplane from an hyperplane and a region. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + protected AbstractSubHyperplane(final Hyperplane hyperplane, + final Region remainingRegion) { + this.hyperplane = hyperplane; + this.remainingRegion = remainingRegion; + } + + /** Build a sub-hyperplane from an hyperplane and a region. + * @param hyper underlying hyperplane + * @param remaining remaining region of the hyperplane + * @return a new sub-hyperplane + */ + protected abstract AbstractSubHyperplane buildNew(final Hyperplane hyper, + final Region remaining); + + /** {@inheritDoc} */ + public AbstractSubHyperplane copySelf() { + return buildNew(hyperplane.copySelf(), remainingRegion); + } + + /** Get the underlying hyperplane. + * @return underlying hyperplane + */ + public Hyperplane getHyperplane() { + return hyperplane; + } + + /** Get the remaining region of the hyperplane. + *

    The returned region is expressed in the canonical hyperplane + * frame and has the hyperplane dimension. For example a chopped + * hyperplane in the 3D euclidean is a 2D plane and the + * corresponding region is a convex 2D polygon.

    + * @return remaining region of the hyperplane + */ + public Region getRemainingRegion() { + return remainingRegion; + } + + /** {@inheritDoc} */ + public double getSize() { + return remainingRegion.getSize(); + } + + /** {@inheritDoc} */ + public AbstractSubHyperplane reunite(final SubHyperplane other) { + @SuppressWarnings("unchecked") + AbstractSubHyperplane o = (AbstractSubHyperplane) other; + return buildNew(hyperplane, + new RegionFactory().union(remainingRegion, o.remainingRegion)); + } + + /** Apply a transform to the instance. + *

    The instance must be a (D-1)-dimension sub-hyperplane with + * respect to the transform not a (D-2)-dimension + * sub-hyperplane the transform knows how to transform by + * itself. The transform will consist in transforming first the + * hyperplane and then the all region using the various methods + * provided by the transform.

    + * @param transform D-dimension transform to apply + * @return the transformed instance + */ + public AbstractSubHyperplane applyTransform(final Transform transform) { + final Hyperplane tHyperplane = transform.apply(hyperplane); + + // transform the tree, except for boundary attribute splitters + final Map, BSPTree> map = new HashMap, BSPTree>(); + final BSPTree tTree = + recurseTransform(remainingRegion.getTree(false), tHyperplane, transform, map); + + // set up the boundary attributes splitters + for (final Map.Entry, BSPTree> entry : map.entrySet()) { + if (entry.getKey().getCut() != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute original = (BoundaryAttribute) entry.getKey().getAttribute(); + if (original != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute transformed = (BoundaryAttribute) entry.getValue().getAttribute(); + for (final BSPTree splitter : original.getSplitters()) { + transformed.getSplitters().add(map.get(splitter)); + } + } + } + } + + return buildNew(tHyperplane, remainingRegion.buildNew(tTree)); + + } + + /** Recursively transform a BSP-tree from a sub-hyperplane. + * @param node current BSP tree node + * @param transformed image of the instance hyperplane by the transform + * @param transform transform to apply + * @param map transformed nodes map + * @return a new tree + */ + private BSPTree recurseTransform(final BSPTree node, + final Hyperplane transformed, + final Transform transform, + final Map, BSPTree> map) { + + final BSPTree transformedNode; + if (node.getCut() == null) { + transformedNode = new BSPTree(node.getAttribute()); + } else { + + @SuppressWarnings("unchecked") + BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); + if (attribute != null) { + final SubHyperplane tPO = (attribute.getPlusOutside() == null) ? + null : transform.apply(attribute.getPlusOutside(), hyperplane, transformed); + final SubHyperplane tPI = (attribute.getPlusInside() == null) ? + null : transform.apply(attribute.getPlusInside(), hyperplane, transformed); + // we start with an empty list of splitters, it will be filled in out of recursion + attribute = new BoundaryAttribute(tPO, tPI, new NodesSet()); + } + + transformedNode = new BSPTree(transform.apply(node.getCut(), hyperplane, transformed), + recurseTransform(node.getPlus(), transformed, transform, map), + recurseTransform(node.getMinus(), transformed, transform, map), + attribute); + } + + map.put(node, transformedNode); + return transformedNode; + + } + + /** {@inheritDoc} */ + @Deprecated + public Side side(Hyperplane hyper) { + return split(hyper).getSide(); + } + + /** {@inheritDoc} */ + public abstract SplitSubHyperplane split(Hyperplane hyper); + + /** {@inheritDoc} */ + public boolean isEmpty() { + return remainingRegion.isEmpty(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTree.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTree.java new file mode 100644 index 000000000..3acaa7bc7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTree.java @@ -0,0 +1,821 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class represent a Binary Space Partition tree. + + *

    BSP trees are an efficient way to represent space partitions and + * to associate attributes with each cell. Each node in a BSP tree + * represents a convex region which is partitioned in two convex + * sub-regions at each side of a cut hyperplane. The root tree + * contains the complete space.

    + + *

    The main use of such partitions is to use a boolean attribute to + * define an inside/outside property, hence representing arbitrary + * polytopes (line segments in 1D, polygons in 2D and polyhedrons in + * 3D) and to operate on them.

    + + *

    Another example would be to represent Voronoi tesselations, the + * attribute of each cell holding the defining point of the cell.

    + + *

    The application-defined attributes are shared among copied + * instances and propagated to split parts. These attributes are not + * used by the BSP-tree algorithms themselves, so the application can + * use them for any purpose. Since the tree visiting method holds + * internal and leaf nodes differently, it is possible to use + * different classes for internal nodes attributes and leaf nodes + * attributes. This should be used with care, though, because if the + * tree is modified in any way after attributes have been set, some + * internal nodes may become leaf nodes and some leaf nodes may become + * internal nodes.

    + + *

    One of the main sources for the development of this package was + * Bruce Naylor, John Amanatides and William Thibault paper Merging + * BSP Trees Yields Polyhedral Set Operations Proc. Siggraph '90, + * Computer Graphics 24(4), August 1990, pp 115-124, published by the + * Association for Computing Machinery (ACM).

    + + * @param Type of the space. + + * @since 3.0 + */ +public class BSPTree { + + /** Cut sub-hyperplane. */ + private SubHyperplane cut; + + /** Tree at the plus side of the cut hyperplane. */ + private BSPTree plus; + + /** Tree at the minus side of the cut hyperplane. */ + private BSPTree minus; + + /** Parent tree. */ + private BSPTree parent; + + /** Application-defined attribute. */ + private Object attribute; + + /** Build a tree having only one root cell representing the whole space. + */ + public BSPTree() { + cut = null; + plus = null; + minus = null; + parent = null; + attribute = null; + } + + /** Build a tree having only one root cell representing the whole space. + * @param attribute attribute of the tree (may be null) + */ + public BSPTree(final Object attribute) { + cut = null; + plus = null; + minus = null; + parent = null; + this.attribute = attribute; + } + + /** Build a BSPTree from its underlying elements. + *

    This method does not perform any verification on + * consistency of its arguments, it should therefore be used only + * when then caller knows what it is doing.

    + *

    This method is mainly useful to build trees + * bottom-up. Building trees top-down is realized with the help of + * method {@link #insertCut insertCut}.

    + * @param cut cut sub-hyperplane for the tree + * @param plus plus side sub-tree + * @param minus minus side sub-tree + * @param attribute attribute associated with the node (may be null) + * @see #insertCut + */ + public BSPTree(final SubHyperplane cut, final BSPTree plus, final BSPTree minus, + final Object attribute) { + this.cut = cut; + this.plus = plus; + this.minus = minus; + this.parent = null; + this.attribute = attribute; + plus.parent = this; + minus.parent = this; + } + + /** Insert a cut sub-hyperplane in a node. + *

    The sub-tree starting at this node will be completely + * overwritten. The new cut sub-hyperplane will be built from the + * intersection of the provided hyperplane with the cell. If the + * hyperplane does intersect the cell, the cell will have two + * children cells with {@code null} attributes on each side of + * the inserted cut sub-hyperplane. If the hyperplane does not + * intersect the cell then no cut hyperplane will be + * inserted and the cell will be changed to a leaf cell. The + * attribute of the node is never changed.

    + *

    This method is mainly useful when called on leaf nodes + * (i.e. nodes for which {@link #getCut getCut} returns + * {@code null}), in this case it provides a way to build a + * tree top-down (whereas the {@link #BSPTree(SubHyperplane, + * BSPTree, BSPTree, Object) 4 arguments constructor} is devoted to + * build trees bottom-up).

    + * @param hyperplane hyperplane to insert, it will be chopped in + * order to fit in the cell defined by the parent nodes of the + * instance + * @return true if a cut sub-hyperplane has been inserted (i.e. if + * the cell now has two leaf child nodes) + * @see #BSPTree(SubHyperplane, BSPTree, BSPTree, Object) + */ + public boolean insertCut(final Hyperplane hyperplane) { + + if (cut != null) { + plus.parent = null; + minus.parent = null; + } + + final SubHyperplane chopped = fitToCell(hyperplane.wholeHyperplane()); + if (chopped == null || chopped.isEmpty()) { + cut = null; + plus = null; + minus = null; + return false; + } + + cut = chopped; + plus = new BSPTree(); + plus.parent = this; + minus = new BSPTree(); + minus.parent = this; + return true; + + } + + /** Copy the instance. + *

    The instance created is completely independent of the original + * one. A deep copy is used, none of the underlying objects are + * shared (except for the nodes attributes and immutable + * objects).

    + * @return a new tree, copy of the instance + */ + public BSPTree copySelf() { + + if (cut == null) { + return new BSPTree(attribute); + } + + return new BSPTree(cut.copySelf(), plus.copySelf(), minus.copySelf(), + attribute); + + } + + /** Get the cut sub-hyperplane. + * @return cut sub-hyperplane, null if this is a leaf tree + */ + public SubHyperplane getCut() { + return cut; + } + + /** Get the tree on the plus side of the cut hyperplane. + * @return tree on the plus side of the cut hyperplane, null if this + * is a leaf tree + */ + public BSPTree getPlus() { + return plus; + } + + /** Get the tree on the minus side of the cut hyperplane. + * @return tree on the minus side of the cut hyperplane, null if this + * is a leaf tree + */ + public BSPTree getMinus() { + return minus; + } + + /** Get the parent node. + * @return parent node, null if the node has no parents + */ + public BSPTree getParent() { + return parent; + } + + /** Associate an attribute with the instance. + * @param attribute attribute to associate with the node + * @see #getAttribute + */ + public void setAttribute(final Object attribute) { + this.attribute = attribute; + } + + /** Get the attribute associated with the instance. + * @return attribute associated with the node or null if no + * attribute has been explicitly set using the {@link #setAttribute + * setAttribute} method + * @see #setAttribute + */ + public Object getAttribute() { + return attribute; + } + + /** Visit the BSP tree nodes. + * @param visitor object visiting the tree nodes + */ + public void visit(final BSPTreeVisitor visitor) { + if (cut == null) { + visitor.visitLeafNode(this); + } else { + switch (visitor.visitOrder(this)) { + case PLUS_MINUS_SUB: + plus.visit(visitor); + minus.visit(visitor); + visitor.visitInternalNode(this); + break; + case PLUS_SUB_MINUS: + plus.visit(visitor); + visitor.visitInternalNode(this); + minus.visit(visitor); + break; + case MINUS_PLUS_SUB: + minus.visit(visitor); + plus.visit(visitor); + visitor.visitInternalNode(this); + break; + case MINUS_SUB_PLUS: + minus.visit(visitor); + visitor.visitInternalNode(this); + plus.visit(visitor); + break; + case SUB_PLUS_MINUS: + visitor.visitInternalNode(this); + plus.visit(visitor); + minus.visit(visitor); + break; + case SUB_MINUS_PLUS: + visitor.visitInternalNode(this); + minus.visit(visitor); + plus.visit(visitor); + break; + default: + throw new MathInternalError(); + } + + } + } + + /** Fit a sub-hyperplane inside the cell defined by the instance. + *

    Fitting is done by chopping off the parts of the + * sub-hyperplane that lie outside of the cell using the + * cut-hyperplanes of the parent nodes of the instance.

    + * @param sub sub-hyperplane to fit + * @return a new sub-hyperplane, guaranteed to have no part outside + * of the instance cell + */ + private SubHyperplane fitToCell(final SubHyperplane sub) { + SubHyperplane s = sub; + for (BSPTree tree = this; tree.parent != null && s != null; tree = tree.parent) { + if (tree == tree.parent.plus) { + s = s.split(tree.parent.cut.getHyperplane()).getPlus(); + } else { + s = s.split(tree.parent.cut.getHyperplane()).getMinus(); + } + } + return s; + } + + /** Get the cell to which a point belongs. + *

    If the returned cell is a leaf node the points belongs to the + * interior of the node, if the cell is an internal node the points + * belongs to the node cut sub-hyperplane.

    + * @param point point to check + * @return the tree cell to which the point belongs + * @deprecated as of 3.3, replaced with {@link #getCell(Point, double)} + */ + @Deprecated + public BSPTree getCell(final Vector point) { + return getCell((Point) point, 1.0e-10); + } + + /** Get the cell to which a point belongs. + *

    If the returned cell is a leaf node the points belongs to the + * interior of the node, if the cell is an internal node the points + * belongs to the node cut sub-hyperplane.

    + * @param point point to check + * @param tolerance tolerance below which points close to a cut hyperplane + * are considered to belong to the hyperplane itself + * @return the tree cell to which the point belongs + */ + public BSPTree getCell(final Point point, final double tolerance) { + + if (cut == null) { + return this; + } + + // position of the point with respect to the cut hyperplane + final double offset = cut.getHyperplane().getOffset(point); + + if (FastMath.abs(offset) < tolerance) { + return this; + } else if (offset <= 0) { + // point is on the minus side of the cut hyperplane + return minus.getCell(point, tolerance); + } else { + // point is on the plus side of the cut hyperplane + return plus.getCell(point, tolerance); + } + + } + + /** Get the cells whose cut sub-hyperplanes are close to the point. + * @param point point to check + * @param maxOffset offset below which a cut sub-hyperplane is considered + * close to the point (in absolute value) + * @return close cells (may be empty if all cut sub-hyperplanes are farther + * than maxOffset from the point) + */ + public List> getCloseCuts(final Point point, final double maxOffset) { + final List> close = new ArrayList>(); + recurseCloseCuts(point, maxOffset, close); + return close; + } + + /** Get the cells whose cut sub-hyperplanes are close to the point. + * @param point point to check + * @param maxOffset offset below which a cut sub-hyperplane is considered + * close to the point (in absolute value) + * @param close list to fill + */ + private void recurseCloseCuts(final Point point, final double maxOffset, + final List> close) { + if (cut != null) { + + // position of the point with respect to the cut hyperplane + final double offset = cut.getHyperplane().getOffset(point); + + if (offset < -maxOffset) { + // point is on the minus side of the cut hyperplane + minus.recurseCloseCuts(point, maxOffset, close); + } else if (offset > maxOffset) { + // point is on the plus side of the cut hyperplane + plus.recurseCloseCuts(point, maxOffset, close); + } else { + // point is close to the cut hyperplane + close.add(this); + minus.recurseCloseCuts(point, maxOffset, close); + plus.recurseCloseCuts(point, maxOffset, close); + } + + } + } + + /** Perform condensation on a tree. + *

    The condensation operation is not recursive, it must be called + * explicitly from leaves to root.

    + */ + private void condense() { + if ((cut != null) && (plus.cut == null) && (minus.cut == null) && + (((plus.attribute == null) && (minus.attribute == null)) || + ((plus.attribute != null) && plus.attribute.equals(minus.attribute)))) { + attribute = (plus.attribute == null) ? minus.attribute : plus.attribute; + cut = null; + plus = null; + minus = null; + } + } + + /** Merge a BSP tree with the instance. + *

    All trees are modified (parts of them are reused in the new + * tree), it is the responsibility of the caller to ensure a copy + * has been done before if any of the former tree should be + * preserved, no such copy is done here!

    + *

    The algorithm used here is directly derived from the one + * described in the Naylor, Amanatides and Thibault paper (section + * III, Binary Partitioning of a BSP Tree).

    + * @param tree other tree to merge with the instance (will be + * unusable after the operation, as well as the + * instance itself) + * @param leafMerger object implementing the final merging phase + * (this is where the semantic of the operation occurs, generally + * depending on the attribute of the leaf node) + * @return a new tree, result of instance <op> + * tree, this value can be ignored if parentTree is not null + * since all connections have already been established + */ + public BSPTree merge(final BSPTree tree, final LeafMerger leafMerger) { + return merge(tree, leafMerger, null, false); + } + + /** Merge a BSP tree with the instance. + * @param tree other tree to merge with the instance (will be + * unusable after the operation, as well as the + * instance itself) + * @param leafMerger object implementing the final merging phase + * (this is where the semantic of the operation occurs, generally + * depending on the attribute of the leaf node) + * @param parentTree parent tree to connect to (may be null) + * @param isPlusChild if true and if parentTree is not null, the + * resulting tree should be the plus child of its parent, ignored if + * parentTree is null + * @return a new tree, result of instance <op> + * tree, this value can be ignored if parentTree is not null + * since all connections have already been established + */ + private BSPTree merge(final BSPTree tree, final LeafMerger leafMerger, + final BSPTree parentTree, final boolean isPlusChild) { + if (cut == null) { + // cell/tree operation + return leafMerger.merge(this, tree, parentTree, isPlusChild, true); + } else if (tree.cut == null) { + // tree/cell operation + return leafMerger.merge(tree, this, parentTree, isPlusChild, false); + } else { + // tree/tree operation + final BSPTree merged = tree.split(cut); + if (parentTree != null) { + merged.parent = parentTree; + if (isPlusChild) { + parentTree.plus = merged; + } else { + parentTree.minus = merged; + } + } + + // merging phase + plus.merge(merged.plus, leafMerger, merged, true); + minus.merge(merged.minus, leafMerger, merged, false); + merged.condense(); + if (merged.cut != null) { + merged.cut = merged.fitToCell(merged.cut.getHyperplane().wholeHyperplane()); + } + + return merged; + + } + } + + /** This interface gather the merging operations between a BSP tree + * leaf and another BSP tree. + *

    As explained in Bruce Naylor, John Amanatides and William + * Thibault paper Merging + * BSP Trees Yields Polyhedral Set Operations, + * the operations on {@link BSPTree BSP trees} can be expressed as a + * generic recursive merging operation where only the final part, + * when one of the operand is a leaf, is specific to the real + * operation semantics. For example, a tree representing a region + * using a boolean attribute to identify inside cells and outside + * cells would use four different objects to implement the final + * merging phase of the four set operations union, intersection, + * difference and symmetric difference (exclusive or).

    + * @param Type of the space. + */ + public interface LeafMerger { + + /** Merge a leaf node and a tree node. + *

    This method is called at the end of a recursive merging + * resulting from a {@code tree1.merge(tree2, leafMerger)} + * call, when one of the sub-trees involved is a leaf (i.e. when + * its cut-hyperplane is null). This is the only place where the + * precise semantics of the operation are required. For all upper + * level nodes in the tree, the merging operation is only a + * generic partitioning algorithm.

    + *

    Since the final operation may be non-commutative, it is + * important to know if the leaf node comes from the instance tree + * ({@code tree1}) or the argument tree + * ({@code tree2}). The third argument of the method is + * devoted to this. It can be ignored for commutative + * operations.

    + *

    The {@link BSPTree#insertInTree BSPTree.insertInTree} method + * may be useful to implement this method.

    + * @param leaf leaf node (its cut hyperplane is guaranteed to be + * null) + * @param tree tree node (its cut hyperplane may be null or not) + * @param parentTree parent tree to connect to (may be null) + * @param isPlusChild if true and if parentTree is not null, the + * resulting tree should be the plus child of its parent, ignored if + * parentTree is null + * @param leafFromInstance if true, the leaf node comes from the + * instance tree ({@code tree1}) and the tree node comes from + * the argument tree ({@code tree2}) + * @return the BSP tree resulting from the merging (may be one of + * the arguments) + */ + BSPTree merge(BSPTree leaf, BSPTree tree, BSPTree parentTree, + boolean isPlusChild, boolean leafFromInstance); + + } + + /** This interface handles the corner cases when an internal node cut sub-hyperplane vanishes. + *

    + * Such cases happens for example when a cut sub-hyperplane is inserted into + * another tree (during a merge operation), and is split in several parts, + * some of which becomes smaller than the tolerance. The corresponding node + * as then no cut sub-hyperplane anymore, but does have children. This interface + * specifies how to handle this situation. + * setting + *

    + * @param Type of the space. + * @since 3.4 + */ + public interface VanishingCutHandler { + + /** Fix a node with both vanished cut and children. + * @param node node to fix + * @return fixed node + */ + BSPTree fixNode(BSPTree node); + + } + + /** Split a BSP tree by an external sub-hyperplane. + *

    Split a tree in two halves, on each side of the + * sub-hyperplane. The instance is not modified.

    + *

    The tree returned is not upward-consistent: despite all of its + * sub-trees cut sub-hyperplanes (including its own cut + * sub-hyperplane) are bounded to the current cell, it is not + * attached to any parent tree yet. This tree is intended to be + * later inserted into an higher level tree.

    + *

    The algorithm used here is the one given in Naylor, Amanatides + * and Thibault paper (section III, Binary Partitioning of a BSP + * Tree).

    + * @param sub partitioning sub-hyperplane, must be already clipped + * to the convex region represented by the instance, will be used as + * the cut sub-hyperplane of the returned tree + * @return a tree having the specified sub-hyperplane as its cut + * sub-hyperplane, the two parts of the split instance as its two + * sub-trees and a null parent + */ + public BSPTree split(final SubHyperplane sub) { + + if (cut == null) { + return new BSPTree(sub, copySelf(), new BSPTree(attribute), null); + } + + final Hyperplane cHyperplane = cut.getHyperplane(); + final Hyperplane sHyperplane = sub.getHyperplane(); + final SubHyperplane.SplitSubHyperplane subParts = sub.split(cHyperplane); + switch (subParts.getSide()) { + case PLUS : + { // the partitioning sub-hyperplane is entirely in the plus sub-tree + final BSPTree split = plus.split(sub); + if (cut.split(sHyperplane).getSide() == Side.PLUS) { + split.plus = + new BSPTree(cut.copySelf(), split.plus, minus.copySelf(), attribute); + split.plus.condense(); + split.plus.parent = split; + } else { + split.minus = + new BSPTree(cut.copySelf(), split.minus, minus.copySelf(), attribute); + split.minus.condense(); + split.minus.parent = split; + } + return split; + } + case MINUS : + { // the partitioning sub-hyperplane is entirely in the minus sub-tree + final BSPTree split = minus.split(sub); + if (cut.split(sHyperplane).getSide() == Side.PLUS) { + split.plus = + new BSPTree(cut.copySelf(), plus.copySelf(), split.plus, attribute); + split.plus.condense(); + split.plus.parent = split; + } else { + split.minus = + new BSPTree(cut.copySelf(), plus.copySelf(), split.minus, attribute); + split.minus.condense(); + split.minus.parent = split; + } + return split; + } + case BOTH : + { + final SubHyperplane.SplitSubHyperplane cutParts = cut.split(sHyperplane); + final BSPTree split = + new BSPTree(sub, plus.split(subParts.getPlus()), minus.split(subParts.getMinus()), + null); + split.plus.cut = cutParts.getPlus(); + split.minus.cut = cutParts.getMinus(); + final BSPTree tmp = split.plus.minus; + split.plus.minus = split.minus.plus; + split.plus.minus.parent = split.plus; + split.minus.plus = tmp; + split.minus.plus.parent = split.minus; + split.plus.condense(); + split.minus.condense(); + return split; + } + default : + return cHyperplane.sameOrientationAs(sHyperplane) ? + new BSPTree(sub, plus.copySelf(), minus.copySelf(), attribute) : + new BSPTree(sub, minus.copySelf(), plus.copySelf(), attribute); + } + + } + + /** Insert the instance into another tree. + *

    The instance itself is modified so its former parent should + * not be used anymore.

    + * @param parentTree parent tree to connect to (may be null) + * @param isPlusChild if true and if parentTree is not null, the + * resulting tree should be the plus child of its parent, ignored if + * parentTree is null + * @see LeafMerger + * @deprecated as of 3.4, replaced with {@link #insertInTree(BSPTree, boolean, VanishingCutHandler)} + */ + @Deprecated + public void insertInTree(final BSPTree parentTree, final boolean isPlusChild) { + insertInTree(parentTree, isPlusChild, new VanishingCutHandler() { + /** {@inheritDoc} */ + public BSPTree fixNode(BSPTree node) { + // the cut should not be null + throw new MathIllegalStateException(LocalizedFormats.NULL_NOT_ALLOWED); + } + }); + } + + /** Insert the instance into another tree. + *

    The instance itself is modified so its former parent should + * not be used anymore.

    + * @param parentTree parent tree to connect to (may be null) + * @param isPlusChild if true and if parentTree is not null, the + * resulting tree should be the plus child of its parent, ignored if + * parentTree is null + * @param vanishingHandler handler to use for handling very rare corner + * cases of vanishing cut sub-hyperplanes in internal nodes during merging + * @see LeafMerger + * @since 3.4 + */ + public void insertInTree(final BSPTree parentTree, final boolean isPlusChild, + final VanishingCutHandler vanishingHandler) { + + // set up parent/child links + parent = parentTree; + if (parentTree != null) { + if (isPlusChild) { + parentTree.plus = this; + } else { + parentTree.minus = this; + } + } + + // make sure the inserted tree lies in the cell defined by its parent nodes + if (cut != null) { + + // explore the parent nodes from here towards tree root + for (BSPTree tree = this; tree.parent != null; tree = tree.parent) { + + // this is an hyperplane of some parent node + final Hyperplane hyperplane = tree.parent.cut.getHyperplane(); + + // chop off the parts of the inserted tree that extend + // on the wrong side of this parent hyperplane + if (tree == tree.parent.plus) { + cut = cut.split(hyperplane).getPlus(); + plus.chopOffMinus(hyperplane, vanishingHandler); + minus.chopOffMinus(hyperplane, vanishingHandler); + } else { + cut = cut.split(hyperplane).getMinus(); + plus.chopOffPlus(hyperplane, vanishingHandler); + minus.chopOffPlus(hyperplane, vanishingHandler); + } + + if (cut == null) { + // the cut sub-hyperplane has vanished + final BSPTree fixed = vanishingHandler.fixNode(this); + cut = fixed.cut; + plus = fixed.plus; + minus = fixed.minus; + attribute = fixed.attribute; + if (cut == null) { + break; + } + } + + } + + // since we may have drop some parts of the inserted tree, + // perform a condensation pass to keep the tree structure simple + condense(); + + } + + } + + /** Prune a tree around a cell. + *

    + * This method can be used to extract a convex cell from a tree. + * The original cell may either be a leaf node or an internal node. + * If it is an internal node, it's subtree will be ignored (i.e. the + * extracted cell will be a leaf node in all cases). The original + * tree to which the original cell belongs is not touched at all, + * a new independent tree will be built. + *

    + * @param cellAttribute attribute to set for the leaf node + * corresponding to the initial instance cell + * @param otherLeafsAttributes attribute to set for the other leaf + * nodes + * @param internalAttributes attribute to set for the internal nodes + * @return a new tree (the original tree is left untouched) containing + * a single branch with the cell as a leaf node, and other leaf nodes + * as the remnants of the pruned branches + * @since 3.3 + */ + public BSPTree pruneAroundConvexCell(final Object cellAttribute, + final Object otherLeafsAttributes, + final Object internalAttributes) { + + // build the current cell leaf + BSPTree tree = new BSPTree(cellAttribute); + + // build the pruned tree bottom-up + for (BSPTree current = this; current.parent != null; current = current.parent) { + final SubHyperplane parentCut = current.parent.cut.copySelf(); + final BSPTree sibling = new BSPTree(otherLeafsAttributes); + if (current == current.parent.plus) { + tree = new BSPTree(parentCut, tree, sibling, internalAttributes); + } else { + tree = new BSPTree(parentCut, sibling, tree, internalAttributes); + } + } + + return tree; + + } + + /** Chop off parts of the tree. + *

    The instance is modified in place, all the parts that are on + * the minus side of the chopping hyperplane are discarded, only the + * parts on the plus side remain.

    + * @param hyperplane chopping hyperplane + * @param vanishingHandler handler to use for handling very rare corner + * cases of vanishing cut sub-hyperplanes in internal nodes during merging + */ + private void chopOffMinus(final Hyperplane hyperplane, final VanishingCutHandler vanishingHandler) { + if (cut != null) { + + cut = cut.split(hyperplane).getPlus(); + plus.chopOffMinus(hyperplane, vanishingHandler); + minus.chopOffMinus(hyperplane, vanishingHandler); + + if (cut == null) { + // the cut sub-hyperplane has vanished + final BSPTree fixed = vanishingHandler.fixNode(this); + cut = fixed.cut; + plus = fixed.plus; + minus = fixed.minus; + attribute = fixed.attribute; + } + + } + } + + /** Chop off parts of the tree. + *

    The instance is modified in place, all the parts that are on + * the plus side of the chopping hyperplane are discarded, only the + * parts on the minus side remain.

    + * @param hyperplane chopping hyperplane + * @param vanishingHandler handler to use for handling very rare corner + * cases of vanishing cut sub-hyperplanes in internal nodes during merging + */ + private void chopOffPlus(final Hyperplane hyperplane, final VanishingCutHandler vanishingHandler) { + if (cut != null) { + + cut = cut.split(hyperplane).getMinus(); + plus.chopOffPlus(hyperplane, vanishingHandler); + minus.chopOffPlus(hyperplane, vanishingHandler); + + if (cut == null) { + // the cut sub-hyperplane has vanished + final BSPTree fixed = vanishingHandler.fixNode(this); + cut = fixed.cut; + plus = fixed.plus; + minus = fixed.minus; + attribute = fixed.attribute; + } + + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java new file mode 100644 index 000000000..a8437ad0c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This interface is used to visit {@link BSPTree BSP tree} nodes. + + *

    Navigation through {@link BSPTree BSP trees} can be done using + * two different point of views:

    + *
      + *
    • + * the first one is in a node-oriented way using the {@link + * BSPTree#getPlus}, {@link BSPTree#getMinus} and {@link + * BSPTree#getParent} methods. Terminal nodes without associated + * {@link SubHyperplane sub-hyperplanes} can be visited this way, + * there is no constraint in the visit order, and it is possible + * to visit either all nodes or only a subset of the nodes + *
    • + *
    • + * the second one is in a sub-hyperplane-oriented way using + * classes implementing this interface which obeys the visitor + * design pattern. The visit order is provided by the visitor as + * each node is first encountered. Each node is visited exactly + * once. + *
    • + *
    + + * @param Type of the space. + + * @see BSPTree + * @see SubHyperplane + + * @since 3.0 + */ +public interface BSPTreeVisitor { + + /** Enumerate for visit order with respect to plus sub-tree, minus sub-tree and cut sub-hyperplane. */ + enum Order { + /** Indicator for visit order plus sub-tree, then minus sub-tree, + * and last cut sub-hyperplane. + */ + PLUS_MINUS_SUB, + + /** Indicator for visit order plus sub-tree, then cut sub-hyperplane, + * and last minus sub-tree. + */ + PLUS_SUB_MINUS, + + /** Indicator for visit order minus sub-tree, then plus sub-tree, + * and last cut sub-hyperplane. + */ + MINUS_PLUS_SUB, + + /** Indicator for visit order minus sub-tree, then cut sub-hyperplane, + * and last plus sub-tree. + */ + MINUS_SUB_PLUS, + + /** Indicator for visit order cut sub-hyperplane, then plus sub-tree, + * and last minus sub-tree. + */ + SUB_PLUS_MINUS, + + /** Indicator for visit order cut sub-hyperplane, then minus sub-tree, + * and last plus sub-tree. + */ + SUB_MINUS_PLUS; + } + + /** Determine the visit order for this node. + *

    Before attempting to visit an internal node, this method is + * called to determine the desired ordering of the visit. It is + * guaranteed that this method will be called before {@link + * #visitInternalNode visitInternalNode} for a given node, it will be + * called exactly once for each internal node.

    + * @param node BSP node guaranteed to have a non null cut sub-hyperplane + * @return desired visit order, must be one of + * {@link Order#PLUS_MINUS_SUB}, {@link Order#PLUS_SUB_MINUS}, + * {@link Order#MINUS_PLUS_SUB}, {@link Order#MINUS_SUB_PLUS}, + * {@link Order#SUB_PLUS_MINUS}, {@link Order#SUB_MINUS_PLUS} + */ + Order visitOrder(BSPTree node); + + /** Visit a BSP tree node node having a non-null sub-hyperplane. + *

    It is guaranteed that this method will be called after {@link + * #visitOrder visitOrder} has been called for a given node, + * it wil be called exactly once for each internal node.

    + * @param node BSP node guaranteed to have a non null cut sub-hyperplane + * @see #visitLeafNode + */ + void visitInternalNode(BSPTree node); + + /** Visit a leaf BSP tree node node having a null sub-hyperplane. + * @param node leaf BSP node having a null sub-hyperplane + * @see #visitInternalNode + */ + void visitLeafNode(BSPTree node); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java new file mode 100644 index 000000000..d419f9965 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Class holding boundary attributes. + *

    This class is used for the attributes associated with the + * nodes of region boundary shell trees returned by the {@link + * Region#getTree(boolean) Region.getTree(includeBoundaryAttributes)} + * when the boolean {@code includeBoundaryAttributes} parameter is + * set to {@code true}. It contains the parts of the node cut + * sub-hyperplane that belong to the boundary.

    + *

    This class is a simple placeholder, it does not provide any + * processing methods.

    + * @param Type of the space. + * @see Region#getTree + * @since 3.0 + */ +public class BoundaryAttribute { + + /** Part of the node cut sub-hyperplane that belongs to the + * boundary and has the outside of the region on the plus side of + * its underlying hyperplane (may be null). + */ + private final SubHyperplane plusOutside; + + /** Part of the node cut sub-hyperplane that belongs to the + * boundary and has the inside of the region on the plus side of + * its underlying hyperplane (may be null). + */ + private final SubHyperplane plusInside; + + /** Sub-hyperplanes that were used to split the boundary part. */ + private final NodesSet splitters; + + /** Simple constructor. + * @param plusOutside part of the node cut sub-hyperplane that + * belongs to the boundary and has the outside of the region on + * the plus side of its underlying hyperplane (may be null) + * @param plusInside part of the node cut sub-hyperplane that + * belongs to the boundary and has the inside of the region on the + * plus side of its underlying hyperplane (may be null) + * @deprecated as of 3.4, the constructor has been replaced by a new one + * which is not public anymore, as it is intended to be used only by + * {@link BoundaryBuilder} + */ + @Deprecated + public BoundaryAttribute(final SubHyperplane plusOutside, + final SubHyperplane plusInside) { + this(plusOutside, plusInside, null); + } + + /** Simple constructor. + * @param plusOutside part of the node cut sub-hyperplane that + * belongs to the boundary and has the outside of the region on + * the plus side of its underlying hyperplane (may be null) + * @param plusInside part of the node cut sub-hyperplane that + * belongs to the boundary and has the inside of the region on the + * plus side of its underlying hyperplane (may be null) + * @param splitters sub-hyperplanes that were used to + * split the boundary part (may be null) + * @since 3.4 + */ + BoundaryAttribute(final SubHyperplane plusOutside, + final SubHyperplane plusInside, + final NodesSet splitters) { + this.plusOutside = plusOutside; + this.plusInside = plusInside; + this.splitters = splitters; + } + + /** Get the part of the node cut sub-hyperplane that belongs to the + * boundary and has the outside of the region on the plus side of + * its underlying hyperplane. + * @return part of the node cut sub-hyperplane that belongs to the + * boundary and has the outside of the region on the plus side of + * its underlying hyperplane + */ + public SubHyperplane getPlusOutside() { + return plusOutside; + } + + /** Get the part of the node cut sub-hyperplane that belongs to the + * boundary and has the inside of the region on the plus side of + * its underlying hyperplane. + * @return part of the node cut sub-hyperplane that belongs to the + * boundary and has the inside of the region on the plus side of + * its underlying hyperplane + */ + public SubHyperplane getPlusInside() { + return plusInside; + } + + /** Get the sub-hyperplanes that were used to split the boundary part. + * @return sub-hyperplanes that were used to split the boundary part + */ + public NodesSet getSplitters() { + return splitters; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java new file mode 100644 index 000000000..013e872fd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Visitor building boundary shell tree. + *

    + * The boundary shell is represented as {@link BoundaryAttribute boundary attributes} + * at each internal node. + *

    + * @param Type of the space. + * @since 3.4 + */ +class BoundaryBuilder implements BSPTreeVisitor { + + /** {@inheritDoc} */ + public Order visitOrder(BSPTree node) { + return Order.PLUS_MINUS_SUB; + } + + /** {@inheritDoc} */ + public void visitInternalNode(BSPTree node) { + + SubHyperplane plusOutside = null; + SubHyperplane plusInside = null; + NodesSet splitters = null; + + // characterize the cut sub-hyperplane, + // first with respect to the plus sub-tree + final Characterization plusChar = new Characterization(node.getPlus(), node.getCut().copySelf()); + + if (plusChar.touchOutside()) { + // plusChar.outsideTouching() corresponds to a subset of the cut sub-hyperplane + // known to have outside cells on its plus side, we want to check if parts + // of this subset do have inside cells on their minus side + final Characterization minusChar = new Characterization(node.getMinus(), plusChar.outsideTouching()); + if (minusChar.touchInside()) { + // this part belongs to the boundary, + // it has the outside on its plus side and the inside on its minus side + plusOutside = minusChar.insideTouching(); + splitters = new NodesSet(); + splitters.addAll(minusChar.getInsideSplitters()); + splitters.addAll(plusChar.getOutsideSplitters()); + } + } + + if (plusChar.touchInside()) { + // plusChar.insideTouching() corresponds to a subset of the cut sub-hyperplane + // known to have inside cells on its plus side, we want to check if parts + // of this subset do have outside cells on their minus side + final Characterization minusChar = new Characterization(node.getMinus(), plusChar.insideTouching()); + if (minusChar.touchOutside()) { + // this part belongs to the boundary, + // it has the inside on its plus side and the outside on its minus side + plusInside = minusChar.outsideTouching(); + if (splitters == null) { + splitters = new NodesSet(); + } + splitters.addAll(minusChar.getOutsideSplitters()); + splitters.addAll(plusChar.getInsideSplitters()); + } + } + + if (splitters != null) { + // the parent nodes are natural splitters for boundary sub-hyperplanes + for (BSPTree up = node.getParent(); up != null; up = up.getParent()) { + splitters.add(up); + } + } + + // set the boundary attribute at non-leaf nodes + node.setAttribute(new BoundaryAttribute(plusOutside, plusInside, splitters)); + + } + + /** {@inheritDoc} */ + public void visitLeafNode(BSPTree node) { + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java new file mode 100644 index 000000000..0448402e5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Class holding the result of point projection on region boundary. + *

    This class is a simple placeholder, it does not provide any + * processing methods.

    + *

    Instances of this class are guaranteed to be immutable

    + * @param Type of the space. + * @since 3.3 + * @see AbstractRegion#projectToBoundary(Point) + */ +public class BoundaryProjection { + + /** Original point. */ + private final Point original; + + /** Projected point. */ + private final Point projected; + + /** Offset of the point with respect to the boundary it is projected on. */ + private final double offset; + + /** Constructor from raw elements. + * @param original original point + * @param projected projected point + * @param offset offset of the point with respect to the boundary it is projected on + */ + public BoundaryProjection(final Point original, final Point projected, final double offset) { + this.original = original; + this.projected = projected; + this.offset = offset; + } + + /** Get the original point. + * @return original point + */ + public Point getOriginal() { + return original; + } + + /** Projected point. + * @return projected point, or null if there are no boundary + */ + public Point getProjected() { + return projected; + } + + /** Offset of the point with respect to the boundary it is projected on. + *

    + * The offset with respect to the boundary is negative if the {@link + * #getOriginal() original point} is inside the region, and positive otherwise. + *

    + *

    + * If there are no boundary, the value is set to either {@code + * Double.POSITIVE_INFINITY} if the region is empty (i.e. all points are + * outside of the region) or {@code Double.NEGATIVE_INFINITY} if the region + * covers the whole space (i.e. all points are inside of the region). + *

    + * @return offset of the point with respect to the boundary it is projected on + */ + public double getOffset() { + return offset; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java new file mode 100644 index 000000000..deb491327 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region.Location; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Local tree visitor to compute projection on boundary. + * @param Type of the space. + * @param Type of the sub-space. + * @since 3.3 + */ +class BoundaryProjector implements BSPTreeVisitor { + + /** Original point. */ + private final Point original; + + /** Current best projected point. */ + private Point projected; + + /** Leaf node closest to the test point. */ + private BSPTree leaf; + + /** Current offset. */ + private double offset; + + /** Simple constructor. + * @param original original point + */ + BoundaryProjector(final Point original) { + this.original = original; + this.projected = null; + this.leaf = null; + this.offset = Double.POSITIVE_INFINITY; + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + // we want to visit the tree so that the first encountered + // leaf is the one closest to the test point + if (node.getCut().getHyperplane().getOffset(original) <= 0) { + return Order.MINUS_SUB_PLUS; + } else { + return Order.PLUS_SUB_MINUS; + } + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + + // project the point on the cut sub-hyperplane + final Hyperplane hyperplane = node.getCut().getHyperplane(); + final double signedOffset = hyperplane.getOffset(original); + if (FastMath.abs(signedOffset) < offset) { + + // project point + final Point regular = hyperplane.project(original); + + // get boundary parts + final List> boundaryParts = boundaryRegions(node); + + // check if regular projection really belongs to the boundary + boolean regularFound = false; + for (final Region part : boundaryParts) { + if (!regularFound && belongsToPart(regular, hyperplane, part)) { + // the projected point lies in the boundary + projected = regular; + offset = FastMath.abs(signedOffset); + regularFound = true; + } + } + + if (!regularFound) { + // the regular projected point is not on boundary, + // so we have to check further if a singular point + // (i.e. a vertex in 2D case) is a possible projection + for (final Region part : boundaryParts) { + final Point spI = singularProjection(regular, hyperplane, part); + if (spI != null) { + final double distance = original.distance(spI); + if (distance < offset) { + projected = spI; + offset = distance; + } + } + } + + } + + } + + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + if (leaf == null) { + // this is the first leaf we visit, + // it is the closest one to the original point + leaf = node; + } + } + + /** Get the projection. + * @return projection + */ + public BoundaryProjection getProjection() { + + // fix offset sign + offset = FastMath.copySign(offset, (Boolean) leaf.getAttribute() ? -1 : +1); + + return new BoundaryProjection(original, projected, offset); + + } + + /** Extract the regions of the boundary on an internal node. + * @param node internal node + * @return regions in the node sub-hyperplane + */ + private List> boundaryRegions(final BSPTree node) { + + final List> regions = new ArrayList>(2); + + @SuppressWarnings("unchecked") + final BoundaryAttribute ba = (BoundaryAttribute) node.getAttribute(); + addRegion(ba.getPlusInside(), regions); + addRegion(ba.getPlusOutside(), regions); + + return regions; + + } + + /** Add a boundary region to a list. + * @param sub sub-hyperplane defining the region + * @param list to fill up + */ + private void addRegion(final SubHyperplane sub, final List> list) { + if (sub != null) { + @SuppressWarnings("unchecked") + final Region region = ((AbstractSubHyperplane) sub).getRemainingRegion(); + if (region != null) { + list.add(region); + } + } + } + + /** Check if a projected point lies on a boundary part. + * @param point projected point to check + * @param hyperplane hyperplane into which the point was projected + * @param part boundary part + * @return true if point lies on the boundary part + */ + private boolean belongsToPart(final Point point, final Hyperplane hyperplane, + final Region part) { + + // there is a non-null sub-space, we can dive into smaller dimensions + @SuppressWarnings("unchecked") + final Embedding embedding = (Embedding) hyperplane; + return part.checkPoint(embedding.toSubSpace(point)) != Location.OUTSIDE; + + } + + /** Get the projection to the closest boundary singular point. + * @param point projected point to check + * @param hyperplane hyperplane into which the point was projected + * @param part boundary part + * @return projection to a singular point of boundary part (may be null) + */ + private Point singularProjection(final Point point, final Hyperplane hyperplane, + final Region part) { + + // there is a non-null sub-space, we can dive into smaller dimensions + @SuppressWarnings("unchecked") + final Embedding embedding = (Embedding) hyperplane; + final BoundaryProjection bp = part.projectToBoundary(embedding.toSubSpace(point)); + + // back to initial dimension + return (bp.getProjected() == null) ? null : embedding.toSpace(bp.getProjected()); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java new file mode 100644 index 000000000..9e8fce0f5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Visitor computing the boundary size. + * @param Type of the space. + * @since 3.0 + */ +class BoundarySizeVisitor implements BSPTreeVisitor { + + /** Size of the boundary. */ + private double boundarySize; + + /** Simple constructor. + */ + BoundarySizeVisitor() { + boundarySize = 0; + } + + /** {@inheritDoc}*/ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc}*/ + public void visitInternalNode(final BSPTree node) { + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = + (BoundaryAttribute) node.getAttribute(); + if (attribute.getPlusOutside() != null) { + boundarySize += attribute.getPlusOutside().getSize(); + } + if (attribute.getPlusInside() != null) { + boundarySize += attribute.getPlusInside().getSize(); + } + } + + /** {@inheritDoc}*/ + public void visitLeafNode(final BSPTree node) { + } + + /** Get the size of the boundary. + * @return size of the boundary + */ + public double getSize() { + return boundarySize; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Characterization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Characterization.java new file mode 100644 index 000000000..b0e899e61 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Characterization.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Cut sub-hyperplanes characterization with respect to inside/outside cells. + * @see BoundaryBuilder + * @param Type of the space. + * @since 3.4 + */ +class Characterization { + + /** Part of the cut sub-hyperplane that touch outside cells. */ + private SubHyperplane outsideTouching; + + /** Part of the cut sub-hyperplane that touch inside cells. */ + private SubHyperplane insideTouching; + + /** Nodes that were used to split the outside touching part. */ + private final NodesSet outsideSplitters; + + /** Nodes that were used to split the outside touching part. */ + private final NodesSet insideSplitters; + + /** Simple constructor. + *

    Characterization consists in splitting the specified + * sub-hyperplane into several parts lying in inside and outside + * cells of the tree. The principle is to compute characterization + * twice for each cut sub-hyperplane in the tree, once on the plus + * node and once on the minus node. The parts that have the same flag + * (inside/inside or outside/outside) do not belong to the boundary + * while parts that have different flags (inside/outside or + * outside/inside) do belong to the boundary.

    + * @param node current BSP tree node + * @param sub sub-hyperplane to characterize + */ + Characterization(final BSPTree node, final SubHyperplane sub) { + outsideTouching = null; + insideTouching = null; + outsideSplitters = new NodesSet(); + insideSplitters = new NodesSet(); + characterize(node, sub, new ArrayList>()); + } + + /** Filter the parts of an hyperplane belonging to the boundary. + *

    The filtering consist in splitting the specified + * sub-hyperplane into several parts lying in inside and outside + * cells of the tree. The principle is to call this method twice for + * each cut sub-hyperplane in the tree, once on the plus node and + * once on the minus node. The parts that have the same flag + * (inside/inside or outside/outside) do not belong to the boundary + * while parts that have different flags (inside/outside or + * outside/inside) do belong to the boundary.

    + * @param node current BSP tree node + * @param sub sub-hyperplane to characterize + * @param splitters nodes that did split the current one + */ + private void characterize(final BSPTree node, final SubHyperplane sub, + final List> splitters) { + if (node.getCut() == null) { + // we have reached a leaf node + final boolean inside = (Boolean) node.getAttribute(); + if (inside) { + addInsideTouching(sub, splitters); + } else { + addOutsideTouching(sub, splitters); + } + } else { + final Hyperplane hyperplane = node.getCut().getHyperplane(); + final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane); + switch (split.getSide()) { + case PLUS: + characterize(node.getPlus(), sub, splitters); + break; + case MINUS: + characterize(node.getMinus(), sub, splitters); + break; + case BOTH: + splitters.add(node); + characterize(node.getPlus(), split.getPlus(), splitters); + characterize(node.getMinus(), split.getMinus(), splitters); + splitters.remove(splitters.size() - 1); + break; + default: + // this should not happen + throw new MathInternalError(); + } + } + } + + /** Add a part of the cut sub-hyperplane known to touch an outside cell. + * @param sub part of the cut sub-hyperplane known to touch an outside cell + * @param splitters sub-hyperplanes that did split the current one + */ + private void addOutsideTouching(final SubHyperplane sub, + final List> splitters) { + if (outsideTouching == null) { + outsideTouching = sub; + } else { + outsideTouching = outsideTouching.reunite(sub); + } + outsideSplitters.addAll(splitters); + } + + /** Add a part of the cut sub-hyperplane known to touch an inside cell. + * @param sub part of the cut sub-hyperplane known to touch an inside cell + * @param splitters sub-hyperplanes that did split the current one + */ + private void addInsideTouching(final SubHyperplane sub, + final List> splitters) { + if (insideTouching == null) { + insideTouching = sub; + } else { + insideTouching = insideTouching.reunite(sub); + } + insideSplitters.addAll(splitters); + } + + /** Check if the cut sub-hyperplane touches outside cells. + * @return true if the cut sub-hyperplane touches outside cells + */ + public boolean touchOutside() { + return outsideTouching != null && !outsideTouching.isEmpty(); + } + + /** Get all the parts of the cut sub-hyperplane known to touch outside cells. + * @return parts of the cut sub-hyperplane known to touch outside cells + * (may be null or empty) + */ + public SubHyperplane outsideTouching() { + return outsideTouching; + } + + /** Get the nodes that were used to split the outside touching part. + *

    + * Splitting nodes are internal nodes (i.e. they have a non-null + * cut sub-hyperplane). + *

    + * @return nodes that were used to split the outside touching part + */ + public NodesSet getOutsideSplitters() { + return outsideSplitters; + } + + /** Check if the cut sub-hyperplane touches inside cells. + * @return true if the cut sub-hyperplane touches inside cells + */ + public boolean touchInside() { + return insideTouching != null && !insideTouching.isEmpty(); + } + + /** Get all the parts of the cut sub-hyperplane known to touch inside cells. + * @return parts of the cut sub-hyperplane known to touch inside cells + * (may be null or empty) + */ + public SubHyperplane insideTouching() { + return insideTouching; + } + + /** Get the nodes that were used to split the inside touching part. + *

    + * Splitting nodes are internal nodes (i.e. they have a non-null + * cut sub-hyperplane). + *

    + * @return nodes that were used to split the inside touching part + */ + public NodesSet getInsideSplitters() { + return insideSplitters; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Embedding.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Embedding.java new file mode 100644 index 000000000..d210e4425 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Embedding.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 com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Line; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This interface defines mappers between a space and one of its sub-spaces. + + *

    Sub-spaces are the lower dimensions subsets of a n-dimensions + * space. The (n-1)-dimension sub-spaces are specific sub-spaces known + * as {@link Hyperplane hyperplanes}. This interface can be used regardless + * of the dimensions differences. As an example, {@link + * Line Line} in 3D + * implements Embedding<{@link + * Vector3D Vector3D}, {link + * Vector1D Vector1D>, i.e. it + * maps directly dimensions 3 and 1.

    + + *

    In the 3D euclidean space, hyperplanes are 2D planes, and the 1D + * sub-spaces are lines.

    + + *

    + * Note that this interface is not intended to be implemented + * by Apache Commons Math users, it is only intended to be implemented + * within the library itself. New methods may be added even for minor + * versions, which breaks compatibility for external implementations. + *

    + + * @param Type of the embedding space. + * @param Type of the embedded sub-space. + + * @see Hyperplane + * @since 3.0 + */ +public interface Embedding { + + /** Transform a space point into a sub-space point. + * @param point n-dimension point of the space + * @return (n-1)-dimension point of the sub-space corresponding to + * the specified space point + * @see #toSpace + */ + Point toSubSpace(Point point); + + /** Transform a sub-space point into a space point. + * @param point (n-1)-dimension point of the sub-space + * @return n-dimension point of the space corresponding to the + * specified sub-space point + * @see #toSubSpace + */ + Point toSpace(Point point); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Hyperplane.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Hyperplane.java new file mode 100644 index 000000000..151d55237 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Hyperplane.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This interface represents an hyperplane of a space. + + *

    The most prominent place where hyperplane appears in space + * partitioning is as cutters. Each partitioning node in a {@link + * BSPTree BSP tree} has a cut {@link SubHyperplane sub-hyperplane} + * which is either an hyperplane or a part of an hyperplane. In an + * n-dimensions euclidean space, an hyperplane is an (n-1)-dimensions + * hyperplane (for example a traditional plane in the 3D euclidean + * space). They can be more exotic objects in specific fields, for + * example a circle on the surface of the unit sphere.

    + + *

    + * Note that this interface is not intended to be implemented + * by Apache Commons Math users, it is only intended to be implemented + * within the library itself. New methods may be added even for minor + * versions, which breaks compatibility for external implementations. + *

    + + * @param Type of the space. + + * @since 3.0 + */ +public interface Hyperplane { + + /** Copy the instance. + *

    The instance created is completely independant of the original + * one. A deep copy is used, none of the underlying objects are + * shared (except for immutable objects).

    + * @return a new hyperplane, copy of the instance + */ + Hyperplane copySelf(); + + /** Get the offset (oriented distance) of a point. + *

    The offset is 0 if the point is on the underlying hyperplane, + * it is positive if the point is on one particular side of the + * hyperplane, and it is negative if the point is on the other side, + * according to the hyperplane natural orientation.

    + * @param point point to check + * @return offset of the point + */ + double getOffset(Point point); + + /** Project a point to the hyperplane. + * @param point point to project + * @return projected point + * @since 3.3 + */ + Point project(Point point); + + /** Get the tolerance below which points are considered to belong to the hyperplane. + * @return tolerance below which points are considered to belong to the hyperplane + * @since 3.3 + */ + double getTolerance(); + + /** Check if the instance has the same orientation as another hyperplane. + *

    This method is expected to be called on parallel hyperplanes. The + * method should not re-check for parallelism, only for + * orientation, typically by testing something like the sign of the + * dot-products of normals.

    + * @param other other hyperplane to check against the instance + * @return true if the instance and the other hyperplane have + * the same orientation + */ + boolean sameOrientationAs(Hyperplane other); + + /** Build a sub-hyperplane covering the whole hyperplane. + * @return a sub-hyperplane covering the whole hyperplane + */ + SubHyperplane wholeHyperplane(); + + /** Build a region covering the whole space. + * @return a region containing the instance + */ + Region wholeSpace(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/InsideFinder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/InsideFinder.java new file mode 100644 index 000000000..69e356f36 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/InsideFinder.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Utility class checking if inside nodes can be found + * on the plus and minus sides of an hyperplane. + * @param Type of the space. + * @since 3.4 + */ +class InsideFinder { + + /** Region on which to operate. */ + private final Region region; + + /** Indicator of inside leaf nodes found on the plus side. */ + private boolean plusFound; + + /** Indicator of inside leaf nodes found on the plus side. */ + private boolean minusFound; + + /** Simple constructor. + * @param region region on which to operate + */ + InsideFinder(final Region region) { + this.region = region; + plusFound = false; + minusFound = false; + } + + /** Search recursively for inside leaf nodes on each side of the given hyperplane. + + *

    The algorithm used here is directly derived from the one + * described in section III (Binary Partitioning of a BSP + * Tree) of the Bruce Naylor, John Amanatides and William + * Thibault paper Merging + * BSP Trees Yields Polyhedral Set Operations Proc. Siggraph + * '90, Computer Graphics 24(4), August 1990, pp 115-124, published + * by the Association for Computing Machinery (ACM)..

    + + * @param node current BSP tree node + * @param sub sub-hyperplane + */ + public void recurseSides(final BSPTree node, final SubHyperplane sub) { + + if (node.getCut() == null) { + if ((Boolean) node.getAttribute()) { + // this is an inside cell expanding across the hyperplane + plusFound = true; + minusFound = true; + } + return; + } + + final Hyperplane hyperplane = node.getCut().getHyperplane(); + final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane); + switch (split.getSide()) { + case PLUS : + // the sub-hyperplane is entirely in the plus sub-tree + if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) { + if (!region.isEmpty(node.getMinus())) { + plusFound = true; + } + } else { + if (!region.isEmpty(node.getMinus())) { + minusFound = true; + } + } + if (!(plusFound && minusFound)) { + recurseSides(node.getPlus(), sub); + } + break; + case MINUS : + // the sub-hyperplane is entirely in the minus sub-tree + if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) { + if (!region.isEmpty(node.getPlus())) { + plusFound = true; + } + } else { + if (!region.isEmpty(node.getPlus())) { + minusFound = true; + } + } + if (!(plusFound && minusFound)) { + recurseSides(node.getMinus(), sub); + } + break; + case BOTH : + // the sub-hyperplane extends in both sub-trees + + // explore first the plus sub-tree + recurseSides(node.getPlus(), split.getPlus()); + + // if needed, explore the minus sub-tree + if (!(plusFound && minusFound)) { + recurseSides(node.getMinus(), split.getMinus()); + } + break; + default : + // the sub-hyperplane and the cut sub-hyperplane share the same hyperplane + if (node.getCut().getHyperplane().sameOrientationAs(sub.getHyperplane())) { + if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) { + plusFound = true; + } + if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) { + minusFound = true; + } + } else { + if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) { + minusFound = true; + } + if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) { + plusFound = true; + } + } + } + + } + + /** Check if inside leaf nodes have been found on the plus side. + * @return true if inside leaf nodes have been found on the plus side + */ + public boolean plusFound() { + return plusFound; + } + + /** Check if inside leaf nodes have been found on the minus side. + * @return true if inside leaf nodes have been found on the minus side + */ + public boolean minusFound() { + return minusFound; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/NodesSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/NodesSet.java new file mode 100644 index 000000000..8e8dd3118 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/NodesSet.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** Set of {@link BSPTree BSP tree} nodes. + * @see BoundaryAttribute + * @param Type of the space. + * @since 3.4 + */ +public class NodesSet implements Iterable> { + + /** List of sub-hyperplanes. */ + private List> list; + + /** Simple constructor. + */ + public NodesSet() { + list = new ArrayList>(); + } + + /** Add a node if not already known. + * @param node node to add + */ + public void add(final BSPTree node) { + + for (final BSPTree existing : list) { + if (node == existing) { + // the node is already known, don't add it + return; + } + } + + // the node was not known, add it + list.add(node); + + } + + /** Add nodes if they are not already known. + * @param iterator nodes iterator + */ + public void addAll(final Iterable> iterator) { + for (final BSPTree node : iterator) { + add(node); + } + } + + /** {@inheritDoc} */ + public Iterator> iterator() { + return list.iterator(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Region.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Region.java new file mode 100644 index 000000000..94244375d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Region.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.Point; + +/** This interface represents a region of a space as a partition. + + *

    Region are subsets of a space, they can be infinite (whole + * space, half space, infinite stripe ...) or finite (polygons in 2D, + * polyhedrons in 3D ...). Their main characteristic is to separate + * points that are considered to be inside the region from + * points considered to be outside of it. In between, there + * may be points on the boundary of the region.

    + + *

    This implementation is limited to regions for which the boundary + * is composed of several {@link SubHyperplane sub-hyperplanes}, + * including regions with no boundary at all: the whole space and the + * empty region. They are not necessarily finite and not necessarily + * path-connected. They can contain holes.

    + + *

    Regions can be combined using the traditional sets operations : + * union, intersection, difference and symetric difference (exclusive + * or) for the binary operations, complement for the unary + * operation.

    + + *

    + * Note that this interface is not intended to be implemented + * by Apache Commons Math users, it is only intended to be implemented + * within the library itself. New methods may be added even for minor + * versions, which breaks compatibility for external implementations. + *

    + + * @param Type of the space. + + * @since 3.0 + */ +public interface Region { + + /** Enumerate for the location of a point with respect to the region. */ + enum Location { + /** Code for points inside the partition. */ + INSIDE, + + /** Code for points outside of the partition. */ + OUTSIDE, + + /** Code for points on the partition boundary. */ + BOUNDARY; + } + + /** Build a region using the instance as a prototype. + *

    This method allow to create new instances without knowing + * exactly the type of the region. It is an application of the + * prototype design pattern.

    + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The + * tree also must have either null internal nodes or + * internal nodes representing the boundary as specified in the + * {@link #getTree getTree} method).

    + * @param newTree inside/outside BSP tree representing the new region + * @return the built region + */ + Region buildNew(BSPTree newTree); + + /** Copy the instance. + *

    The instance created is completely independant of the original + * one. A deep copy is used, none of the underlying objects are + * shared (except for the underlying tree {@code Boolean} + * attributes and immutable objects).

    + * @return a new region, copy of the instance + */ + Region copySelf(); + + /** Check if the instance is empty. + * @return true if the instance is empty + */ + boolean isEmpty(); + + /** Check if the sub-tree starting at a given node is empty. + * @param node root node of the sub-tree (must have {@link + * Region Region} tree semantics, i.e. the leaf nodes must have + * {@code Boolean} attributes representing an inside/outside + * property) + * @return true if the sub-tree starting at the given node is empty + */ + boolean isEmpty(final BSPTree node); + + /** Check if the instance covers the full space. + * @return true if the instance covers the full space + */ + boolean isFull(); + + /** Check if the sub-tree starting at a given node covers the full space. + * @param node root node of the sub-tree (must have {@link + * Region Region} tree semantics, i.e. the leaf nodes must have + * {@code Boolean} attributes representing an inside/outside + * property) + * @return true if the sub-tree starting at the given node covers the full space + */ + boolean isFull(final BSPTree node); + + /** Check if the instance entirely contains another region. + * @param region region to check against the instance + * @return true if the instance contains the specified tree + */ + boolean contains(final Region region); + + /** Check a point with respect to the region. + * @param point point to check + * @return a code representing the point status: either {@link + * Location#INSIDE}, {@link Location#OUTSIDE} or {@link Location#BOUNDARY} + */ + Location checkPoint(final Point point); + + /** Project a point on the boundary of the region. + * @param point point to check + * @return projection of the point on the boundary + * @since 3.3 + */ + BoundaryProjection projectToBoundary(final Point point); + + /** Get the underlying BSP tree. + + *

    Regions are represented by an underlying inside/outside BSP + * tree whose leaf attributes are {@code Boolean} instances + * representing inside leaf cells if the attribute value is + * {@code true} and outside leaf cells if the attribute is + * {@code false}. These leaf attributes are always present and + * guaranteed to be non null.

    + + *

    In addition to the leaf attributes, the internal nodes which + * correspond to cells split by cut sub-hyperplanes may contain + * {@link BoundaryAttribute BoundaryAttribute} objects representing + * the parts of the corresponding cut sub-hyperplane that belong to + * the boundary. When the boundary attributes have been computed, + * all internal nodes are guaranteed to have non-null + * attributes, however some {@link BoundaryAttribute + * BoundaryAttribute} instances may have their {@link + * BoundaryAttribute#getPlusInside() getPlusInside} and {@link + * BoundaryAttribute#getPlusOutside() getPlusOutside} methods both + * returning null if the corresponding cut sub-hyperplane does not + * have any parts belonging to the boundary.

    + + *

    Since computing the boundary is not always required and can be + * time-consuming for large trees, these internal nodes attributes + * are computed using lazy evaluation only when required by setting + * the {@code includeBoundaryAttributes} argument to + * {@code true}. Once computed, these attributes remain in the + * tree, which implies that in this case, further calls to the + * method for the same region will always include these attributes + * regardless of the value of the + * {@code includeBoundaryAttributes} argument.

    + + * @param includeBoundaryAttributes if true, the boundary attributes + * at internal nodes are guaranteed to be included (they may be + * included even if the argument is false, if they have already been + * computed due to a previous call) + * @return underlying BSP tree + * @see BoundaryAttribute + */ + BSPTree getTree(final boolean includeBoundaryAttributes); + + /** Get the size of the boundary. + * @return the size of the boundary (this is 0 in 1D, a length in + * 2D, an area in 3D ...) + */ + double getBoundarySize(); + + /** Get the size of the instance. + * @return the size of the instance (this is a length in 1D, an area + * in 2D, a volume in 3D ...) + */ + double getSize(); + + /** Get the barycenter of the instance. + * @return an object representing the barycenter + */ + Point getBarycenter(); + + /** Compute the relative position of the instance with respect to an + * hyperplane. + * @param hyperplane reference hyperplane + * @return one of {@link Side#PLUS Side.PLUS}, {@link Side#MINUS + * Side.MINUS}, {@link Side#BOTH Side.BOTH} or {@link Side#HYPER + * Side.HYPER} (the latter result can occur only if the tree + * contains only one cut hyperplane) + * @deprecated as of 3.6, this method which was only intended for + * internal use is not used anymore + */ + @Deprecated + Side side(final Hyperplane hyperplane); + + /** Get the parts of a sub-hyperplane that are contained in the region. + *

    The parts of the sub-hyperplane that belong to the boundary are + * not included in the resulting parts.

    + * @param sub sub-hyperplane traversing the region + * @return filtered sub-hyperplane + */ + SubHyperplane intersection(final SubHyperplane sub); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/RegionFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/RegionFactory.java new file mode 100644 index 000000000..d046c28b9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/RegionFactory.java @@ -0,0 +1,376 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region.Location; + +/** This class is a factory for {@link Region}. + + * @param Type of the space. + + * @since 3.0 + */ +public class RegionFactory { + + /** Visitor removing internal nodes attributes. */ + private final NodesCleaner nodeCleaner; + + /** Simple constructor. + */ + public RegionFactory() { + nodeCleaner = new NodesCleaner(); + } + + /** Build a convex region from a collection of bounding hyperplanes. + * @param hyperplanes collection of bounding hyperplanes + * @return a new convex region, or null if the collection is empty + */ + public Region buildConvex(final Hyperplane ... hyperplanes) { + if ((hyperplanes == null) || (hyperplanes.length == 0)) { + return null; + } + + // use the first hyperplane to build the right class + final Region region = hyperplanes[0].wholeSpace(); + + // chop off parts of the space + BSPTree node = region.getTree(false); + node.setAttribute(Boolean.TRUE); + for (final Hyperplane hyperplane : hyperplanes) { + if (node.insertCut(hyperplane)) { + node.setAttribute(null); + node.getPlus().setAttribute(Boolean.FALSE); + node = node.getMinus(); + node.setAttribute(Boolean.TRUE); + } else { + // the hyperplane could not be inserted in the current leaf node + // either it is completely outside (which means the input hyperplanes + // are wrong), or it is parallel to a previous hyperplane + SubHyperplane s = hyperplane.wholeHyperplane(); + for (BSPTree tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) { + final Hyperplane other = tree.getParent().getCut().getHyperplane(); + final SubHyperplane.SplitSubHyperplane split = s.split(other); + switch (split.getSide()) { + case HYPER : + // the hyperplane is parallel to a previous hyperplane + if (!hyperplane.sameOrientationAs(other)) { + // this hyperplane is opposite to the other one, + // the region is thinner than the tolerance, we consider it empty + return getComplement(hyperplanes[0].wholeSpace()); + } + // the hyperplane is an extension of an already known hyperplane, we just ignore it + break; + case PLUS : + // the hyperplane is outside of the current convex zone, + // the input hyperplanes are inconsistent + throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX_HYPERPLANES); + default : + s = split.getMinus(); + } + } + } + } + + return region; + + } + + /** Compute the union of two regions. + * @param region1 first region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @param region2 second region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @return a new region, result of {@code region1 union region2} + */ + public Region union(final Region region1, final Region region2) { + final BSPTree tree = + region1.getTree(false).merge(region2.getTree(false), new UnionMerger()); + tree.visit(nodeCleaner); + return region1.buildNew(tree); + } + + /** Compute the intersection of two regions. + * @param region1 first region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @param region2 second region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @return a new region, result of {@code region1 intersection region2} + */ + public Region intersection(final Region region1, final Region region2) { + final BSPTree tree = + region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger()); + tree.visit(nodeCleaner); + return region1.buildNew(tree); + } + + /** Compute the symmetric difference (exclusive or) of two regions. + * @param region1 first region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @param region2 second region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @return a new region, result of {@code region1 xor region2} + */ + public Region xor(final Region region1, final Region region2) { + final BSPTree tree = + region1.getTree(false).merge(region2.getTree(false), new XorMerger()); + tree.visit(nodeCleaner); + return region1.buildNew(tree); + } + + /** Compute the difference of two regions. + * @param region1 first region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @param region2 second region (will be unusable after the operation as + * parts of it will be reused in the new region) + * @return a new region, result of {@code region1 minus region2} + */ + public Region difference(final Region region1, final Region region2) { + final BSPTree tree = + region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2)); + tree.visit(nodeCleaner); + return region1.buildNew(tree); + } + + /** Get the complement of the region (exchanged interior/exterior). + * @param region region to complement, it will not modified, a new + * region independent region will be built + * @return a new region, complement of the specified one + */ + /** Get the complement of the region (exchanged interior/exterior). + * @param region region to complement, it will not modified, a new + * region independent region will be built + * @return a new region, complement of the specified one + */ + public Region getComplement(final Region region) { + return region.buildNew(recurseComplement(region.getTree(false))); + } + + /** Recursively build the complement of a BSP tree. + * @param node current node of the original tree + * @return new tree, complement of the node + */ + private BSPTree recurseComplement(final BSPTree node) { + + // transform the tree, except for boundary attribute splitters + final Map, BSPTree> map = new HashMap, BSPTree>(); + final BSPTree transformedTree = recurseComplement(node, map); + + // set up the boundary attributes splitters + for (final Map.Entry, BSPTree> entry : map.entrySet()) { + if (entry.getKey().getCut() != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute original = (BoundaryAttribute) entry.getKey().getAttribute(); + if (original != null) { + @SuppressWarnings("unchecked") + BoundaryAttribute transformed = (BoundaryAttribute) entry.getValue().getAttribute(); + for (final BSPTree splitter : original.getSplitters()) { + transformed.getSplitters().add(map.get(splitter)); + } + } + } + } + + return transformedTree; + + } + + /** Recursively build the complement of a BSP tree. + * @param node current node of the original tree + * @param map transformed nodes map + * @return new tree, complement of the node + */ + private BSPTree recurseComplement(final BSPTree node, + final Map, BSPTree> map) { + + final BSPTree transformedNode; + if (node.getCut() == null) { + transformedNode = new BSPTree(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE); + } else { + + @SuppressWarnings("unchecked") + BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); + if (attribute != null) { + final SubHyperplane plusOutside = + (attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf(); + final SubHyperplane plusInside = + (attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf(); + // we start with an empty list of splitters, it will be filled in out of recursion + attribute = new BoundaryAttribute(plusOutside, plusInside, new NodesSet()); + } + + transformedNode = new BSPTree(node.getCut().copySelf(), + recurseComplement(node.getPlus(), map), + recurseComplement(node.getMinus(), map), + attribute); + } + + map.put(node, transformedNode); + return transformedNode; + + } + + /** BSP tree leaf merger computing union of two regions. */ + private class UnionMerger implements BSPTree.LeafMerger { + /** {@inheritDoc} */ + public BSPTree merge(final BSPTree leaf, final BSPTree tree, + final BSPTree parentTree, + final boolean isPlusChild, final boolean leafFromInstance) { + if ((Boolean) leaf.getAttribute()) { + // the leaf node represents an inside cell + leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true)); + return leaf; + } + // the leaf node represents an outside cell + tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false)); + return tree; + } + } + + /** BSP tree leaf merger computing intersection of two regions. */ + private class IntersectionMerger implements BSPTree.LeafMerger { + /** {@inheritDoc} */ + public BSPTree merge(final BSPTree leaf, final BSPTree tree, + final BSPTree parentTree, + final boolean isPlusChild, final boolean leafFromInstance) { + if ((Boolean) leaf.getAttribute()) { + // the leaf node represents an inside cell + tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true)); + return tree; + } + // the leaf node represents an outside cell + leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false)); + return leaf; + } + } + + /** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */ + private class XorMerger implements BSPTree.LeafMerger { + /** {@inheritDoc} */ + public BSPTree merge(final BSPTree leaf, final BSPTree tree, + final BSPTree parentTree, final boolean isPlusChild, + final boolean leafFromInstance) { + BSPTree t = tree; + if ((Boolean) leaf.getAttribute()) { + // the leaf node represents an inside cell + t = recurseComplement(t); + } + t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true)); + return t; + } + } + + /** BSP tree leaf merger computing difference of two regions. */ + private class DifferenceMerger implements BSPTree.LeafMerger, BSPTree.VanishingCutHandler { + + /** Region to subtract from. */ + private final Region region1; + + /** Region to subtract. */ + private final Region region2; + + /** Simple constructor. + * @param region1 region to subtract from + * @param region2 region to subtract + */ + DifferenceMerger(final Region region1, final Region region2) { + this.region1 = region1.copySelf(); + this.region2 = region2.copySelf(); + } + + /** {@inheritDoc} */ + public BSPTree merge(final BSPTree leaf, final BSPTree tree, + final BSPTree parentTree, final boolean isPlusChild, + final boolean leafFromInstance) { + if ((Boolean) leaf.getAttribute()) { + // the leaf node represents an inside cell + final BSPTree argTree = + recurseComplement(leafFromInstance ? tree : leaf); + argTree.insertInTree(parentTree, isPlusChild, this); + return argTree; + } + // the leaf node represents an outside cell + final BSPTree instanceTree = + leafFromInstance ? leaf : tree; + instanceTree.insertInTree(parentTree, isPlusChild, this); + return instanceTree; + } + + /** {@inheritDoc} */ + public BSPTree fixNode(final BSPTree node) { + // get a representative point in the degenerate cell + final BSPTree cell = node.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null); + final Region r = region1.buildNew(cell); + final Point p = r.getBarycenter(); + return new BSPTree(region1.checkPoint(p) == Location.INSIDE && + region2.checkPoint(p) == Location.OUTSIDE); + } + + } + + /** Visitor removing internal nodes attributes. */ + private class NodesCleaner implements BSPTreeVisitor { + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.PLUS_SUB_MINUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + node.setAttribute(null); + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + } + + } + + /** Handler replacing nodes with vanishing cuts with leaf nodes. */ + private class VanishingToLeaf implements BSPTree.VanishingCutHandler { + + /** Inside/outside indocator to use for ambiguous nodes. */ + private final boolean inside; + + /** Simple constructor. + * @param inside inside/outside indicator to use for ambiguous nodes + */ + VanishingToLeaf(final boolean inside) { + this.inside = inside; + } + + /** {@inheritDoc} */ + public BSPTree fixNode(final BSPTree node) { + if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) { + // no ambiguity + return new BSPTree(node.getPlus().getAttribute()); + } else { + // ambiguous node + return new BSPTree(inside); + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Side.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Side.java new file mode 100644 index 000000000..8bb6f92d2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Side.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +/** Enumerate representing the location of an element with respect to an + * {@link Hyperplane hyperplane} of a space. + * @since 3.0 + */ +public enum Side { + + /** Code for the plus side of the hyperplane. */ + PLUS, + + /** Code for the minus side of the hyperplane. */ + MINUS, + + /** Code for elements crossing the hyperplane from plus to minus side. */ + BOTH, + + /** Code for the hyperplane itself. */ + HYPER; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java new file mode 100644 index 000000000..257e7b7e8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** This interface represents the remaining parts of an hyperplane after + * other parts have been chopped off. + + *

    sub-hyperplanes are obtained when parts of an {@link + * Hyperplane hyperplane} are chopped off by other hyperplanes that + * intersect it. The remaining part is a convex region. Such objects + * appear in {@link BSPTree BSP trees} as the intersection of a cut + * hyperplane with the convex region which it splits, the chopping + * hyperplanes are the cut hyperplanes closer to the tree root.

    + + *

    + * Note that this interface is not intended to be implemented + * by Apache Commons Math users, it is only intended to be implemented + * within the library itself. New methods may be added even for minor + * versions, which breaks compatibility for external implementations. + *

    + + * @param Type of the embedding space. + + * @since 3.0 + */ +public interface SubHyperplane { + + /** Copy the instance. + *

    The instance created is completely independent of the original + * one. A deep copy is used, none of the underlying objects are + * shared (except for the nodes attributes and immutable + * objects).

    + * @return a new sub-hyperplane, copy of the instance + */ + SubHyperplane copySelf(); + + /** Get the underlying hyperplane. + * @return underlying hyperplane + */ + Hyperplane getHyperplane(); + + /** Check if the instance is empty. + * @return true if the instance is empty + */ + boolean isEmpty(); + + /** Get the size of the instance. + * @return the size of the instance (this is a length in 1D, an area + * in 2D, a volume in 3D ...) + */ + double getSize(); + + /** Compute the relative position of the instance with respect + * to an hyperplane. + * @param hyperplane hyperplane to check instance against + * @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH}, + * {@link Side#HYPER} + * @deprecated as of 3.6, replaced with {@link #split(Hyperplane)}.{@link SplitSubHyperplane#getSide()} + */ + @Deprecated + Side side(Hyperplane hyperplane); + + /** Split the instance in two parts by an hyperplane. + * @param hyperplane splitting hyperplane + * @return an object containing both the part of the instance + * on the plus side of the hyperplane and the part of the + * instance on the minus side of the hyperplane + */ + SplitSubHyperplane split(Hyperplane hyperplane); + + /** Compute the union of the instance and another sub-hyperplane. + * @param other other sub-hyperplane to union (must be in the + * same hyperplane as the instance) + * @return a new sub-hyperplane, union of the instance and other + */ + SubHyperplane reunite(SubHyperplane other); + + /** Class holding the results of the {@link #split split} method. + * @param Type of the embedding space. + */ + class SplitSubHyperplane { + + /** Part of the sub-hyperplane on the plus side of the splitting hyperplane. */ + private final SubHyperplane plus; + + /** Part of the sub-hyperplane on the minus side of the splitting hyperplane. */ + private final SubHyperplane minus; + + /** Build a SplitSubHyperplane from its parts. + * @param plus part of the sub-hyperplane on the plus side of the + * splitting hyperplane + * @param minus part of the sub-hyperplane on the minus side of the + * splitting hyperplane + */ + public SplitSubHyperplane(final SubHyperplane plus, + final SubHyperplane minus) { + this.plus = plus; + this.minus = minus; + } + + /** Get the part of the sub-hyperplane on the plus side of the splitting hyperplane. + * @return part of the sub-hyperplane on the plus side of the splitting hyperplane + */ + public SubHyperplane getPlus() { + return plus; + } + + /** Get the part of the sub-hyperplane on the minus side of the splitting hyperplane. + * @return part of the sub-hyperplane on the minus side of the splitting hyperplane + */ + public SubHyperplane getMinus() { + return minus; + } + + /** Get the side of the split sub-hyperplane with respect to its splitter. + * @return {@link Side#PLUS} if only {@link #getPlus()} is neither null nor empty, + * {@link Side#MINUS} if only {@link #getMinus()} is neither null nor empty, + * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()} + * are neither null nor empty or {@link Side#HYPER} if both {@link #getPlus()} and + * {@link #getMinus()} are either null or empty + * @since 3.6 + */ + public Side getSide() { + if (plus != null && !plus.isEmpty()) { + if (minus != null && !minus.isEmpty()) { + return Side.BOTH; + } else { + return Side.PLUS; + } + } else if (minus != null && !minus.isEmpty()) { + return Side.MINUS; + } else { + return Side.HYPER; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Transform.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Transform.java new file mode 100644 index 000000000..af0a1768e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/Transform.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; + + +/** This interface represents an inversible affine transform in a space. + *

    Inversible affine transform include for example scalings, + * translations, rotations.

    + + *

    Transforms are dimension-specific. The consistency rules between + * the three {@code apply} methods are the following ones for a + * transformed defined for dimension D:

    + *
      + *
    • + * the transform can be applied to a point in the + * D-dimension space using its {@link #apply(Point)} + * method + *
    • + *
    • + * the transform can be applied to a (D-1)-dimension + * hyperplane in the D-dimension space using its + * {@link #apply(Hyperplane)} method + *
    • + *
    • + * the transform can be applied to a (D-2)-dimension + * sub-hyperplane in a (D-1)-dimension hyperplane using + * its {@link #apply(SubHyperplane, Hyperplane, Hyperplane)} + * method + *
    • + *
    + + * @param Type of the embedding space. + * @param Type of the embedded sub-space. + + * @since 3.0 + */ +public interface Transform { + + /** Transform a point of a space. + * @param point point to transform + * @return a new object representing the transformed point + */ + Point apply(Point point); + + /** Transform an hyperplane of a space. + * @param hyperplane hyperplane to transform + * @return a new object representing the transformed hyperplane + */ + Hyperplane apply(Hyperplane hyperplane); + + /** Transform a sub-hyperplane embedded in an hyperplane. + * @param sub sub-hyperplane to transform + * @param original hyperplane in which the sub-hyperplane is + * defined (this is the original hyperplane, the transform has + * not been applied to it) + * @param transformed hyperplane in which the sub-hyperplane is + * defined (this is the transformed hyperplane, the transform + * has been applied to it) + * @return a new object representing the transformed sub-hyperplane + */ + SubHyperplane apply(SubHyperplane sub, Hyperplane original, Hyperplane transformed); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/package-info.java new file mode 100644 index 000000000..bef55e63c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/package-info.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * This package provides classes to implement Binary Space Partition trees. + * + *

    + * {@link com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree BSP trees} + * are an efficient way to represent parts of space and in particular + * polytopes (line segments in 1D, polygons in 2D and polyhedrons in 3D) + * and to operate on them. The main principle is to recursively subdivide + * the space using simple hyperplanes (points in 1D, lines in 2D, planes + * in 3D). + *

    + * + *

    + * We start with a tree composed of a single node without any cut + * hyperplane: it represents the complete space, which is a convex + * part. If we add a cut hyperplane to this node, this represents a + * partition with the hyperplane at the node level and two half spaces at + * each side of the cut hyperplane. These half-spaces are represented by + * two child nodes without any cut hyperplanes associated, the plus child + * which represents the half space on the plus side of the cut hyperplane + * and the minus child on the other side. Continuing the subdivisions, we + * end up with a tree having internal nodes that are associated with a + * cut hyperplane and leaf nodes without any hyperplane which correspond + * to convex parts. + *

    + * + *

    + * When BSP trees are used to represent polytopes, the convex parts are + * known to be completely inside or outside the polytope as long as there + * is no facet in the part (which is obviously the case if the cut + * hyperplanes have been chosen as the underlying hyperplanes of the + * facets (this is called an autopartition) and if the subdivision + * process has been continued until all facets have been processed. It is + * important to note that the polytope is not defined by a + * single part, but by several convex ones. This is the property that + * allows BSP-trees to represent non-convex polytopes despites all parts + * are convex. The {@link + * com.fr.third.org.apache.commons.math3.geometry.partitioning.Region Region} class is + * devoted to this representation, it is build on top of the {@link + * com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree BSPTree} class using + * boolean objects as the leaf nodes attributes to represent the + * inside/outside property of each leaf part, and also adds various + * methods dealing with boundaries (i.e. the separation between the + * inside and the outside parts). + *

    + * + *

    + * Rather than simply associating the internal nodes with an hyperplane, + * we consider sub-hyperplanes which correspond to the part of + * the hyperplane that is inside the convex part defined by all the + * parent nodes (this implies that the sub-hyperplane at root node is in + * fact a complete hyperplane, because there is no parent to bound + * it). Since the parts are convex, the sub-hyperplanes are convex, in + * 3D the convex parts are convex polyhedrons, and the sub-hyperplanes + * are convex polygons that cut these polyhedrons in two + * sub-polyhedrons. Using this definition, a BSP tree completely + * partitions the space. Each point either belongs to one of the + * sub-hyperplanes in an internal node or belongs to one of the leaf + * convex parts. + *

    + * + *

    + * In order to determine where a point is, it is sufficient to check its + * position with respect to the root cut hyperplane, to select the + * corresponding child tree and to repeat the procedure recursively, + * until either the point appears to be exactly on one of the hyperplanes + * in the middle of the tree or to be in one of the leaf parts. For + * this operation, it is sufficient to consider the complete hyperplanes, + * there is no need to check the points with the boundary of the + * sub-hyperplanes, because this check has in fact already been realized + * by the recursive descent in the tree. This is very easy to do and very + * efficient, especially if the tree is well balanced (the cost is + * O(log(n)) where n is the number of facets) + * or if the first tree levels close to the root discriminate large parts + * of the total space. + *

    + * + *

    + * One of the main sources for the development of this package was Bruce + * Naylor, John Amanatides and William Thibault paper Merging + * BSP Trees Yields Polyhedral Set Operations Proc. Siggraph '90, + * Computer Graphics 24(4), August 1990, pp 115-124, published by the + * Association for Computing Machinery (ACM). The same paper can also be + * found here. + *

    + * + *

    + * Note that the interfaces defined in this package are not intended to + * be implemented by Apache Commons Math users, they are only intended to be + * implemented within the library itself. New methods may be added even for + * minor versions, which breaks compatibility for external implementations. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java new file mode 100644 index 000000000..0ddc8f95a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java @@ -0,0 +1,634 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning.utilities; + +/** This class implements AVL trees. + * + *

    The purpose of this class is to sort elements while allowing + * duplicate elements (i.e. such that {@code a.equals(b)} is + * true). The {@code SortedSet} interface does not allow this, so + * a specific class is needed. Null elements are not allowed.

    + * + *

    Since the {@code equals} method is not sufficient to + * differentiate elements, the {@link #delete delete} method is + * implemented using the equality operator.

    + * + *

    In order to clearly mark the methods provided here do not have + * the same semantics as the ones specified in the + * {@code SortedSet} interface, different names are used + * ({@code add} has been replaced by {@link #insert insert} and + * {@code remove} has been replaced by {@link #delete + * delete}).

    + * + *

    This class is based on the C implementation Georg Kraml has put + * in the public domain. Unfortunately, his page seems not + * to exist any more.

    + * + * @param the type of the elements + * + * @since 3.0 + * @deprecated as of 3.4, this class is not used anymore and considered + * to be out of scope of Apache Commons Math + */ +@Deprecated +public class AVLTree> { + + /** Top level node. */ + private Node top; + + /** Build an empty tree. + */ + public AVLTree() { + top = null; + } + + /** Insert an element in the tree. + * @param element element to insert (silently ignored if null) + */ + public void insert(final T element) { + if (element != null) { + if (top == null) { + top = new Node(element, null); + } else { + top.insert(element); + } + } + } + + /** Delete an element from the tree. + *

    The element is deleted only if there is a node {@code n} + * containing exactly the element instance specified, i.e. for which + * {@code n.getElement() == element}. This is purposely + * different from the specification of the + * {@code java.util.Set} {@code remove} method (in fact, + * this is the reason why a specific class has been developed).

    + * @param element element to delete (silently ignored if null) + * @return true if the element was deleted from the tree + */ + public boolean delete(final T element) { + if (element != null) { + for (Node node = getNotSmaller(element); node != null; node = node.getNext()) { + // loop over all elements neither smaller nor larger + // than the specified one + if (node.element == element) { + node.delete(); + return true; + } else if (node.element.compareTo(element) > 0) { + // all the remaining elements are known to be larger, + // the element is not in the tree + return false; + } + } + } + return false; + } + + /** Check if the tree is empty. + * @return true if the tree is empty + */ + public boolean isEmpty() { + return top == null; + } + + + /** Get the number of elements of the tree. + * @return number of elements contained in the tree + */ + public int size() { + return (top == null) ? 0 : top.size(); + } + + /** Get the node whose element is the smallest one in the tree. + * @return the tree node containing the smallest element in the tree + * or null if the tree is empty + * @see #getLargest + * @see #getNotSmaller + * @see #getNotLarger + * @see Node#getPrevious + * @see Node#getNext + */ + public Node getSmallest() { + return (top == null) ? null : top.getSmallest(); + } + + /** Get the node whose element is the largest one in the tree. + * @return the tree node containing the largest element in the tree + * or null if the tree is empty + * @see #getSmallest + * @see #getNotSmaller + * @see #getNotLarger + * @see Node#getPrevious + * @see Node#getNext + */ + public Node getLargest() { + return (top == null) ? null : top.getLargest(); + } + + /** Get the node whose element is not smaller than the reference object. + * @param reference reference object (may not be in the tree) + * @return the tree node containing the smallest element not smaller + * than the reference object or null if either the tree is empty or + * all its elements are smaller than the reference object + * @see #getSmallest + * @see #getLargest + * @see #getNotLarger + * @see Node#getPrevious + * @see Node#getNext + */ + public Node getNotSmaller(final T reference) { + Node candidate = null; + for (Node node = top; node != null;) { + if (node.element.compareTo(reference) < 0) { + if (node.right == null) { + return candidate; + } + node = node.right; + } else { + candidate = node; + if (node.left == null) { + return candidate; + } + node = node.left; + } + } + return null; + } + + /** Get the node whose element is not larger than the reference object. + * @param reference reference object (may not be in the tree) + * @return the tree node containing the largest element not larger + * than the reference object (in which case the node is guaranteed + * not to be empty) or null if either the tree is empty or all its + * elements are larger than the reference object + * @see #getSmallest + * @see #getLargest + * @see #getNotSmaller + * @see Node#getPrevious + * @see Node#getNext + */ + public Node getNotLarger(final T reference) { + Node candidate = null; + for (Node node = top; node != null;) { + if (node.element.compareTo(reference) > 0) { + if (node.left == null) { + return candidate; + } + node = node.left; + } else { + candidate = node; + if (node.right == null) { + return candidate; + } + node = node.right; + } + } + return null; + } + + /** Enum for tree skew factor. */ + private enum Skew { + /** Code for left high trees. */ + LEFT_HIGH, + + /** Code for right high trees. */ + RIGHT_HIGH, + + /** Code for Skew.BALANCED trees. */ + BALANCED; + } + + /** This class implements AVL trees nodes. + *

    AVL tree nodes implement all the logical structure of the + * tree. Nodes are created by the {@link AVLTree AVLTree} class.

    + *

    The nodes are not independant from each other but must obey + * specific balancing constraints and the tree structure is + * rearranged as elements are inserted or deleted from the tree. The + * creation, modification and tree-related navigation methods have + * therefore restricted access. Only the order-related navigation, + * reading and delete methods are public.

    + * @see AVLTree + */ + public class Node { + + /** Element contained in the current node. */ + private T element; + + /** Left sub-tree. */ + private Node left; + + /** Right sub-tree. */ + private Node right; + + /** Parent tree. */ + private Node parent; + + /** Skew factor. */ + private Skew skew; + + /** Build a node for a specified element. + * @param element element + * @param parent parent node + */ + Node(final T element, final Node parent) { + this.element = element; + left = null; + right = null; + this.parent = parent; + skew = Skew.BALANCED; + } + + /** Get the contained element. + * @return element contained in the node + */ + public T getElement() { + return element; + } + + /** Get the number of elements of the tree rooted at this node. + * @return number of elements contained in the tree rooted at this node + */ + int size() { + return 1 + ((left == null) ? 0 : left.size()) + ((right == null) ? 0 : right.size()); + } + + /** Get the node whose element is the smallest one in the tree + * rooted at this node. + * @return the tree node containing the smallest element in the + * tree rooted at this node or null if the tree is empty + * @see #getLargest + */ + Node getSmallest() { + Node node = this; + while (node.left != null) { + node = node.left; + } + return node; + } + + /** Get the node whose element is the largest one in the tree + * rooted at this node. + * @return the tree node containing the largest element in the + * tree rooted at this node or null if the tree is empty + * @see #getSmallest + */ + Node getLargest() { + Node node = this; + while (node.right != null) { + node = node.right; + } + return node; + } + + /** Get the node containing the next smaller or equal element. + * @return node containing the next smaller or equal element or + * null if there is no smaller or equal element in the tree + * @see #getNext + */ + public Node getPrevious() { + + if (left != null) { + final Node node = left.getLargest(); + if (node != null) { + return node; + } + } + + for (Node node = this; node.parent != null; node = node.parent) { + if (node != node.parent.left) { + return node.parent; + } + } + + return null; + + } + + /** Get the node containing the next larger or equal element. + * @return node containing the next larger or equal element (in + * which case the node is guaranteed not to be empty) or null if + * there is no larger or equal element in the tree + * @see #getPrevious + */ + public Node getNext() { + + if (right != null) { + final Node node = right.getSmallest(); + if (node != null) { + return node; + } + } + + for (Node node = this; node.parent != null; node = node.parent) { + if (node != node.parent.right) { + return node.parent; + } + } + + return null; + + } + + /** Insert an element in a sub-tree. + * @param newElement element to insert + * @return true if the parent tree should be re-Skew.BALANCED + */ + boolean insert(final T newElement) { + if (newElement.compareTo(this.element) < 0) { + // the inserted element is smaller than the node + if (left == null) { + left = new Node(newElement, this); + return rebalanceLeftGrown(); + } + return left.insert(newElement) ? rebalanceLeftGrown() : false; + } + + // the inserted element is equal to or greater than the node + if (right == null) { + right = new Node(newElement, this); + return rebalanceRightGrown(); + } + return right.insert(newElement) ? rebalanceRightGrown() : false; + + } + + /** Delete the node from the tree. + */ + public void delete() { + if ((parent == null) && (left == null) && (right == null)) { + // this was the last node, the tree is now empty + element = null; + top = null; + } else { + + Node node; + Node child; + boolean leftShrunk; + if ((left == null) && (right == null)) { + node = this; + element = null; + leftShrunk = node == node.parent.left; + child = null; + } else { + node = (left != null) ? left.getLargest() : right.getSmallest(); + element = node.element; + leftShrunk = node == node.parent.left; + child = (node.left != null) ? node.left : node.right; + } + + node = node.parent; + if (leftShrunk) { + node.left = child; + } else { + node.right = child; + } + if (child != null) { + child.parent = node; + } + + while (leftShrunk ? node.rebalanceLeftShrunk() : node.rebalanceRightShrunk()) { + if (node.parent == null) { + return; + } + leftShrunk = node == node.parent.left; + node = node.parent; + } + + } + } + + /** Re-balance the instance as left sub-tree has grown. + * @return true if the parent tree should be reSkew.BALANCED too + */ + private boolean rebalanceLeftGrown() { + switch (skew) { + case LEFT_HIGH: + if (left.skew == Skew.LEFT_HIGH) { + rotateCW(); + skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + } else { + final Skew s = left.right.skew; + left.rotateCCW(); + rotateCW(); + switch(s) { + case LEFT_HIGH: + left.skew = Skew.BALANCED; + right.skew = Skew.RIGHT_HIGH; + break; + case RIGHT_HIGH: + left.skew = Skew.LEFT_HIGH; + right.skew = Skew.BALANCED; + break; + default: + left.skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + } + skew = Skew.BALANCED; + } + return false; + case RIGHT_HIGH: + skew = Skew.BALANCED; + return false; + default: + skew = Skew.LEFT_HIGH; + return true; + } + } + + /** Re-balance the instance as right sub-tree has grown. + * @return true if the parent tree should be reSkew.BALANCED too + */ + private boolean rebalanceRightGrown() { + switch (skew) { + case LEFT_HIGH: + skew = Skew.BALANCED; + return false; + case RIGHT_HIGH: + if (right.skew == Skew.RIGHT_HIGH) { + rotateCCW(); + skew = Skew.BALANCED; + left.skew = Skew.BALANCED; + } else { + final Skew s = right.left.skew; + right.rotateCW(); + rotateCCW(); + switch (s) { + case LEFT_HIGH: + left.skew = Skew.BALANCED; + right.skew = Skew.RIGHT_HIGH; + break; + case RIGHT_HIGH: + left.skew = Skew.LEFT_HIGH; + right.skew = Skew.BALANCED; + break; + default: + left.skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + } + skew = Skew.BALANCED; + } + return false; + default: + skew = Skew.RIGHT_HIGH; + return true; + } + } + + /** Re-balance the instance as left sub-tree has shrunk. + * @return true if the parent tree should be reSkew.BALANCED too + */ + private boolean rebalanceLeftShrunk() { + switch (skew) { + case LEFT_HIGH: + skew = Skew.BALANCED; + return true; + case RIGHT_HIGH: + if (right.skew == Skew.RIGHT_HIGH) { + rotateCCW(); + skew = Skew.BALANCED; + left.skew = Skew.BALANCED; + return true; + } else if (right.skew == Skew.BALANCED) { + rotateCCW(); + skew = Skew.LEFT_HIGH; + left.skew = Skew.RIGHT_HIGH; + return false; + } else { + final Skew s = right.left.skew; + right.rotateCW(); + rotateCCW(); + switch (s) { + case LEFT_HIGH: + left.skew = Skew.BALANCED; + right.skew = Skew.RIGHT_HIGH; + break; + case RIGHT_HIGH: + left.skew = Skew.LEFT_HIGH; + right.skew = Skew.BALANCED; + break; + default: + left.skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + } + skew = Skew.BALANCED; + return true; + } + default: + skew = Skew.RIGHT_HIGH; + return false; + } + } + + /** Re-balance the instance as right sub-tree has shrunk. + * @return true if the parent tree should be reSkew.BALANCED too + */ + private boolean rebalanceRightShrunk() { + switch (skew) { + case RIGHT_HIGH: + skew = Skew.BALANCED; + return true; + case LEFT_HIGH: + if (left.skew == Skew.LEFT_HIGH) { + rotateCW(); + skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + return true; + } else if (left.skew == Skew.BALANCED) { + rotateCW(); + skew = Skew.RIGHT_HIGH; + right.skew = Skew.LEFT_HIGH; + return false; + } else { + final Skew s = left.right.skew; + left.rotateCCW(); + rotateCW(); + switch (s) { + case LEFT_HIGH: + left.skew = Skew.BALANCED; + right.skew = Skew.RIGHT_HIGH; + break; + case RIGHT_HIGH: + left.skew = Skew.LEFT_HIGH; + right.skew = Skew.BALANCED; + break; + default: + left.skew = Skew.BALANCED; + right.skew = Skew.BALANCED; + } + skew = Skew.BALANCED; + return true; + } + default: + skew = Skew.LEFT_HIGH; + return false; + } + } + + /** Perform a clockwise rotation rooted at the instance. + *

    The skew factor are not updated by this method, they + * must be updated by the caller

    + */ + private void rotateCW() { + + final T tmpElt = element; + element = left.element; + left.element = tmpElt; + + final Node tmpNode = left; + left = tmpNode.left; + tmpNode.left = tmpNode.right; + tmpNode.right = right; + right = tmpNode; + + if (left != null) { + left.parent = this; + } + if (right.right != null) { + right.right.parent = right; + } + + } + + /** Perform a counter-clockwise rotation rooted at the instance. + *

    The skew factor are not updated by this method, they + * must be updated by the caller

    + */ + private void rotateCCW() { + + final T tmpElt = element; + element = right.element; + right.element = tmpElt; + + final Node tmpNode = right; + right = tmpNode.right; + tmpNode.right = tmpNode.left; + tmpNode.left = left; + left = tmpNode; + + if (right != null) { + right.parent = this; + } + if (left.left != null) { + left.left.parent = left; + } + + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java new file mode 100644 index 000000000..344c1cd30 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java @@ -0,0 +1,431 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning.utilities; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class implements an ordering operation for T-uples. + * + *

    Ordering is done by encoding all components of the T-uple into a + * single scalar value and using this value as the sorting + * key. Encoding is performed using the method invented by Georg + * Cantor in 1877 when he proved it was possible to establish a + * bijection between a line and a plane. The binary representations of + * the components of the T-uple are mixed together to form a single + * scalar. This means that the 2k bit of component 0 is + * followed by the 2k bit of component 1, then by the + * 2k bit of component 2 up to the 2k bit of + * component {@code t}, which is followed by the 2k-1 + * bit of component 0, followed by the 2k-1 bit of + * component 1 ... The binary representations are extended as needed + * to handle numbers with different scales and a suitable + * 2p offset is added to the components in order to avoid + * negative numbers (this offset is adjusted as needed during the + * comparison operations).

    + * + *

    The more interesting property of the encoding method for our + * purpose is that it allows to select all the points that are in a + * given range. This is depicted in dimension 2 by the following + * picture:

    + * + * + * + *

    This picture shows a set of 100000 random 2-D pairs having their + * first component between -50 and +150 and their second component + * between -350 and +50. We wanted to extract all pairs having their + * first component between +30 and +70 and their second component + * between -120 and -30. We built the lower left point at coordinates + * (30, -120) and the upper right point at coordinates (70, -30). All + * points smaller than the lower left point are drawn in red and all + * points larger than the upper right point are drawn in blue. The + * green points are between the two limits. This picture shows that + * all the desired points are selected, along with spurious points. In + * this case, we get 15790 points, 4420 of which really belonging to + * the desired rectangle. It is possible to extract very small + * subsets. As an example extracting from the same 100000 points set + * the points having their first component between +30 and +31 and + * their second component between -91 and -90, we get a subset of 11 + * points, 2 of which really belonging to the desired rectangle.

    + * + *

    the previous selection technique can be applied in all + * dimensions, still using two points to define the interval. The + * first point will have all its components set to their lower bounds + * while the second point will have all its components set to their + * upper bounds.

    + * + *

    T-uples with negative infinite or positive infinite components + * are sorted logically.

    + * + *

    Since the specification of the {@code Comparator} interface + * allows only {@code ClassCastException} errors, some arbitrary + * choices have been made to handle specific cases. The rationale for + * these choices is to keep regular and consistent T-uples + * together.

    + *
      + *
    • instances with different dimensions are sorted according to + * their dimension regardless of their components values
    • + *
    • instances with {@code Double.NaN} components are sorted + * after all other ones (even after instances with positive infinite + * components
    • + *
    • instances with both positive and negative infinite components + * are considered as if they had {@code Double.NaN} + * components
    • + *
    + * + * @since 3.0 + * @deprecated as of 3.4, this class is not used anymore and considered + * to be out of scope of Apache Commons Math + */ +@Deprecated +public class OrderedTuple implements Comparable { + + /** Sign bit mask. */ + private static final long SIGN_MASK = 0x8000000000000000L; + + /** Exponent bits mask. */ + private static final long EXPONENT_MASK = 0x7ff0000000000000L; + + /** Mantissa bits mask. */ + private static final long MANTISSA_MASK = 0x000fffffffffffffL; + + /** Implicit MSB for normalized numbers. */ + private static final long IMPLICIT_ONE = 0x0010000000000000L; + + /** Double components of the T-uple. */ + private double[] components; + + /** Offset scale. */ + private int offset; + + /** Least Significant Bit scale. */ + private int lsb; + + /** Ordering encoding of the double components. */ + private long[] encoding; + + /** Positive infinity marker. */ + private boolean posInf; + + /** Negative infinity marker. */ + private boolean negInf; + + /** Not A Number marker. */ + private boolean nan; + + /** Build an ordered T-uple from its components. + * @param components double components of the T-uple + */ + public OrderedTuple(final double ... components) { + this.components = components.clone(); + int msb = Integer.MIN_VALUE; + lsb = Integer.MAX_VALUE; + posInf = false; + negInf = false; + nan = false; + for (int i = 0; i < components.length; ++i) { + if (Double.isInfinite(components[i])) { + if (components[i] < 0) { + negInf = true; + } else { + posInf = true; + } + } else if (Double.isNaN(components[i])) { + nan = true; + } else { + final long b = Double.doubleToLongBits(components[i]); + final long m = mantissa(b); + if (m != 0) { + final int e = exponent(b); + msb = FastMath.max(msb, e + computeMSB(m)); + lsb = FastMath.min(lsb, e + computeLSB(m)); + } + } + } + + if (posInf && negInf) { + // instance cannot be sorted logically + posInf = false; + negInf = false; + nan = true; + } + + if (lsb <= msb) { + // encode the T-upple with the specified offset + encode(msb + 16); + } else { + encoding = new long[] { + 0x0L + }; + } + + } + + /** Encode the T-uple with a given offset. + * @param minOffset minimal scale of the offset to add to all + * components (must be greater than the MSBs of all components) + */ + private void encode(final int minOffset) { + + // choose an offset with some margins + offset = minOffset + 31; + offset -= offset % 32; + + if ((encoding != null) && (encoding.length == 1) && (encoding[0] == 0x0L)) { + // the components are all zeroes + return; + } + + // allocate an integer array to encode the components (we use only + // 63 bits per element because there is no unsigned long in Java) + final int neededBits = offset + 1 - lsb; + final int neededLongs = (neededBits + 62) / 63; + encoding = new long[components.length * neededLongs]; + + // mix the bits from all components + int eIndex = 0; + int shift = 62; + long word = 0x0L; + for (int k = offset; eIndex < encoding.length; --k) { + for (int vIndex = 0; vIndex < components.length; ++vIndex) { + if (getBit(vIndex, k) != 0) { + word |= 0x1L << shift; + } + if (shift-- == 0) { + encoding[eIndex++] = word; + word = 0x0L; + shift = 62; + } + } + } + + } + + /** Compares this ordered T-uple with the specified object. + + *

    The ordering method is detailed in the general description of + * the class. Its main property is to be consistent with distance: + * geometrically close T-uples stay close to each other when stored + * in a sorted collection using this comparison method.

    + + *

    T-uples with negative infinite, positive infinite are sorted + * logically.

    + + *

    Some arbitrary choices have been made to handle specific + * cases. The rationale for these choices is to keep + * normal and consistent T-uples together.

    + *
      + *
    • instances with different dimensions are sorted according to + * their dimension regardless of their components values
    • + *
    • instances with {@code Double.NaN} components are sorted + * after all other ones (evan after instances with positive infinite + * components
    • + *
    • instances with both positive and negative infinite components + * are considered as if they had {@code Double.NaN} + * components
    • + *
    + + * @param ot T-uple to compare instance with + * @return a negative integer if the instance is less than the + * object, zero if they are equal, or a positive integer if the + * instance is greater than the object + + */ + public int compareTo(final OrderedTuple ot) { + if (components.length == ot.components.length) { + if (nan) { + return +1; + } else if (ot.nan) { + return -1; + } else if (negInf || ot.posInf) { + return -1; + } else if (posInf || ot.negInf) { + return +1; + } else { + + if (offset < ot.offset) { + encode(ot.offset); + } else if (offset > ot.offset) { + ot.encode(offset); + } + + final int limit = FastMath.min(encoding.length, ot.encoding.length); + for (int i = 0; i < limit; ++i) { + if (encoding[i] < ot.encoding[i]) { + return -1; + } else if (encoding[i] > ot.encoding[i]) { + return +1; + } + } + + if (encoding.length < ot.encoding.length) { + return -1; + } else if (encoding.length > ot.encoding.length) { + return +1; + } else { + return 0; + } + + } + } + + return components.length - ot.components.length; + + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } else if (other instanceof OrderedTuple) { + return compareTo((OrderedTuple) other) == 0; + } else { + return false; + } + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + // the following constants are arbitrary small primes + final int multiplier = 37; + final int trueHash = 97; + final int falseHash = 71; + + // hash fields and combine them + // (we rely on the multiplier to have different combined weights + // for all int fields and all boolean fields) + int hash = Arrays.hashCode(components); + hash = hash * multiplier + offset; + hash = hash * multiplier + lsb; + hash = hash * multiplier + (posInf ? trueHash : falseHash); + hash = hash * multiplier + (negInf ? trueHash : falseHash); + hash = hash * multiplier + (nan ? trueHash : falseHash); + + return hash; + + } + + /** Get the components array. + * @return array containing the T-uple components + */ + public double[] getComponents() { + return components.clone(); + } + + /** Extract the sign from the bits of a double. + * @param bits binary representation of the double + * @return sign bit (zero if positive, non zero if negative) + */ + private static long sign(final long bits) { + return bits & SIGN_MASK; + } + + /** Extract the exponent from the bits of a double. + * @param bits binary representation of the double + * @return exponent + */ + private static int exponent(final long bits) { + return ((int) ((bits & EXPONENT_MASK) >> 52)) - 1075; + } + + /** Extract the mantissa from the bits of a double. + * @param bits binary representation of the double + * @return mantissa + */ + private static long mantissa(final long bits) { + return ((bits & EXPONENT_MASK) == 0) ? + ((bits & MANTISSA_MASK) << 1) : // subnormal number + (IMPLICIT_ONE | (bits & MANTISSA_MASK)); // normal number + } + + /** Compute the most significant bit of a long. + * @param l long from which the most significant bit is requested + * @return scale of the most significant bit of {@code l}, + * or 0 if {@code l} is zero + * @see #computeLSB + */ + private static int computeMSB(final long l) { + + long ll = l; + long mask = 0xffffffffL; + int scale = 32; + int msb = 0; + + while (scale != 0) { + if ((ll & mask) != ll) { + msb |= scale; + ll >>= scale; + } + scale >>= 1; + mask >>= scale; + } + + return msb; + + } + + /** Compute the least significant bit of a long. + * @param l long from which the least significant bit is requested + * @return scale of the least significant bit of {@code l}, + * or 63 if {@code l} is zero + * @see #computeMSB + */ + private static int computeLSB(final long l) { + + long ll = l; + long mask = 0xffffffff00000000L; + int scale = 32; + int lsb = 0; + + while (scale != 0) { + if ((ll & mask) == ll) { + lsb |= scale; + ll >>= scale; + } + scale >>= 1; + mask >>= scale; + } + + return lsb; + + } + + /** Get a bit from the mantissa of a double. + * @param i index of the component + * @param k scale of the requested bit + * @return the specified bit (either 0 or 1), after the offset has + * been added to the double + */ + private int getBit(final int i, final int k) { + final long bits = Double.doubleToLongBits(components[i]); + final int e = exponent(bits); + if ((k < e) || (k > offset)) { + return 0; + } else if (k == offset) { + return (sign(bits) == 0L) ? 1 : 0; + } else if (k > (e + 52)) { + return (sign(bits) == 0L) ? 0 : 1; + } else { + final long m = (sign(bits) == 0L) ? mantissa(bits) : -mantissa(bits); + return (int) ((m >> (k - e)) & 0x1L); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png new file mode 100644 index 000000000..4eca23302 Binary files /dev/null and b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png differ diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java new file mode 100644 index 000000000..bd7fc01e3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides multidimensional ordering features for partitioning. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.partitioning.utilities; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Arc.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Arc.java new file mode 100644 index 000000000..c903297d2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Arc.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + + +/** This class represents an arc on a circle. + * @see ArcsSet + * @since 3.3 + */ +public class Arc { + + /** The lower angular bound of the arc. */ + private final double lower; + + /** The upper angular bound of the arc. */ + private final double upper; + + /** Middle point of the arc. */ + private final double middle; + + /** Tolerance below which angles are considered identical. */ + private final double tolerance; + + /** Simple constructor. + *

    + * If either {@code lower} is equals to {@code upper} or + * the interval exceeds \( 2 \pi \), the arc is considered + * to be the full circle and its initial defining boundaries + * will be forgotten. {@code lower} is not allowed to be + * greater than {@code upper} (an exception is thrown in this case). + * {@code lower} will be canonicalized between 0 and \( 2 \pi \), and + * upper shifted accordingly, so the {@link #getInf()} and {@link #getSup()} + * may not return the value used at instance construction. + *

    + * @param lower lower angular bound of the arc + * @param upper upper angular bound of the arc + * @param tolerance tolerance below which angles are considered identical + * @exception NumberIsTooLargeException if lower is greater than upper + */ + public Arc(final double lower, final double upper, final double tolerance) + throws NumberIsTooLargeException { + this.tolerance = tolerance; + if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) { + // the arc must cover the whole circle + this.lower = 0; + this.upper = MathUtils.TWO_PI; + this.middle = FastMath.PI; + } else if (lower <= upper) { + this.lower = MathUtils.normalizeAngle(lower, FastMath.PI); + this.upper = this.lower + (upper - lower); + this.middle = 0.5 * (this.lower + this.upper); + } else { + throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, + lower, upper, true); + } + } + + /** Get the lower angular bound of the arc. + * @return lower angular bound of the arc, + * always between 0 and \( 2 \pi \) + */ + public double getInf() { + return lower; + } + + /** Get the upper angular bound of the arc. + * @return upper angular bound of the arc, + * always between {@link #getInf()} and {@link #getInf()} \( + 2 \pi \) + */ + public double getSup() { + return upper; + } + + /** Get the angular size of the arc. + * @return angular size of the arc + */ + public double getSize() { + return upper - lower; + } + + /** Get the barycenter of the arc. + * @return barycenter of the arc + */ + public double getBarycenter() { + return middle; + } + + /** Get the tolerance below which angles are considered identical. + * @return tolerance below which angles are considered identical + */ + public double getTolerance() { + return tolerance; + } + + /** Check a point with respect to the arc. + * @param point point to check + * @return a code representing the point status: either {@link + * Region.Location#INSIDE}, {@link Region.Location#OUTSIDE} or {@link Region.Location#BOUNDARY} + */ + public Region.Location checkPoint(final double point) { + final double normalizedPoint = MathUtils.normalizeAngle(point, middle); + if (normalizedPoint < lower - tolerance || normalizedPoint > upper + tolerance) { + return Region.Location.OUTSIDE; + } else if (normalizedPoint > lower + tolerance && normalizedPoint < upper - tolerance) { + return Region.Location.INSIDE; + } else { + return (getSize() >= MathUtils.TWO_PI - tolerance) ? Region.Location.INSIDE : Region.Location.BOUNDARY; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java new file mode 100644 index 000000000..7cd88f765 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java @@ -0,0 +1,956 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryProjection; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Side; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** This class represents a region of a circle: a set of arcs. + *

    + * Note that due to the wrapping around \(2 \pi\), barycenter is + * ill-defined here. It was defined only in order to fulfill + * the requirements of the {@link + * Region Region} + * interface, but its use is discouraged. + *

    + * @since 3.3 + */ +public class ArcsSet extends AbstractRegion implements Iterable { + + /** Build an arcs set representing the whole circle. + * @param tolerance tolerance below which close sub-arcs are merged together + */ + public ArcsSet(final double tolerance) { + super(tolerance); + } + + /** Build an arcs set corresponding to a single arc. + *

    + * If either {@code lower} is equals to {@code upper} or + * the interval exceeds \( 2 \pi \), the arc is considered + * to be the full circle and its initial defining boundaries + * will be forgotten. {@code lower} is not allowed to be greater + * than {@code upper} (an exception is thrown in this case). + *

    + * @param lower lower bound of the arc + * @param upper upper bound of the arc + * @param tolerance tolerance below which close sub-arcs are merged together + * @exception NumberIsTooLargeException if lower is greater than upper + */ + public ArcsSet(final double lower, final double upper, final double tolerance) + throws NumberIsTooLargeException { + super(buildTree(lower, upper, tolerance), tolerance); + } + + /** Build an arcs set from an inside/outside BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the arcs set + * @param tolerance tolerance below which close sub-arcs are merged together + * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not + * consistent across the \( 0, 2 \pi \) crossing + */ + public ArcsSet(final BSPTree tree, final double tolerance) + throws InconsistentStateAt2PiWrapping { + super(tree, tolerance); + check2PiConsistency(); + } + + /** Build an arcs set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoints polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements + * @param tolerance tolerance below which close sub-arcs are merged together + * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not + * consistent across the \( 0, 2 \pi \) crossing + */ + public ArcsSet(final Collection> boundary, final double tolerance) + throws InconsistentStateAt2PiWrapping { + super(boundary, tolerance); + check2PiConsistency(); + } + + /** Build an inside/outside tree representing a single arc. + * @param lower lower angular bound of the arc + * @param upper upper angular bound of the arc + * @param tolerance tolerance below which close sub-arcs are merged together + * @return the built tree + * @exception NumberIsTooLargeException if lower is greater than upper + */ + private static BSPTree buildTree(final double lower, final double upper, + final double tolerance) + throws NumberIsTooLargeException { + + if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) { + // the tree must cover the whole circle + return new BSPTree(Boolean.TRUE); + } else if (lower > upper) { + throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, + lower, upper, true); + } + + // this is a regular arc, covering only part of the circle + final double normalizedLower = MathUtils.normalizeAngle(lower, FastMath.PI); + final double normalizedUpper = normalizedLower + (upper - lower); + final SubHyperplane lowerCut = + new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane(); + + if (normalizedUpper <= MathUtils.TWO_PI) { + // simple arc starting after 0 and ending before 2 \pi + final SubHyperplane upperCut = + new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane(); + return new BSPTree(lowerCut, + new BSPTree(Boolean.FALSE), + new BSPTree(upperCut, + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null), + null); + } else { + // arc wrapping around 2 \pi + final SubHyperplane upperCut = + new LimitAngle(new S1Point(normalizedUpper - MathUtils.TWO_PI), true, tolerance).wholeHyperplane(); + return new BSPTree(lowerCut, + new BSPTree(upperCut, + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null), + new BSPTree(Boolean.TRUE), + null); + } + + } + + /** Check consistency. + * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not + * consistent across the \( 0, 2 \pi \) crossing + */ + private void check2PiConsistency() throws InconsistentStateAt2PiWrapping { + + // start search at the tree root + BSPTree root = getTree(false); + if (root.getCut() == null) { + return; + } + + // find the inside/outside state before the smallest internal node + final Boolean stateBefore = (Boolean) getFirstLeaf(root).getAttribute(); + + // find the inside/outside state after the largest internal node + final Boolean stateAfter = (Boolean) getLastLeaf(root).getAttribute(); + + if (stateBefore ^ stateAfter) { + throw new InconsistentStateAt2PiWrapping(); + } + + } + + /** Get the first leaf node of a tree. + * @param root tree root + * @return first leaf node (i.e. node corresponding to the region just after 0.0 radians) + */ + private BSPTree getFirstLeaf(final BSPTree root) { + + if (root.getCut() == null) { + return root; + } + + // find the smallest internal node + BSPTree smallest = null; + for (BSPTree n = root; n != null; n = previousInternalNode(n)) { + smallest = n; + } + + return leafBefore(smallest); + + } + + /** Get the last leaf node of a tree. + * @param root tree root + * @return last leaf node (i.e. node corresponding to the region just before \( 2 \pi \) radians) + */ + private BSPTree getLastLeaf(final BSPTree root) { + + if (root.getCut() == null) { + return root; + } + + // find the largest internal node + BSPTree largest = null; + for (BSPTree n = root; n != null; n = nextInternalNode(n)) { + largest = n; + } + + return leafAfter(largest); + + } + + /** Get the node corresponding to the first arc start. + * @return smallest internal node (i.e. first after 0.0 radians, in trigonometric direction), + * or null if there are no internal nodes (i.e. the set is either empty or covers the full circle) + */ + private BSPTree getFirstArcStart() { + + // start search at the tree root + BSPTree node = getTree(false); + if (node.getCut() == null) { + return null; + } + + // walk tree until we find the smallest internal node + node = getFirstLeaf(node).getParent(); + + // walk tree until we find an arc start + while (node != null && !isArcStart(node)) { + node = nextInternalNode(node); + } + + return node; + + } + + /** Check if an internal node corresponds to the start angle of an arc. + * @param node internal node to check + * @return true if the node corresponds to the start angle of an arc + */ + private boolean isArcStart(final BSPTree node) { + + if ((Boolean) leafBefore(node).getAttribute()) { + // it has an inside cell before it, it may end an arc but not start it + return false; + } + + if (!(Boolean) leafAfter(node).getAttribute()) { + // it has an outside cell after it, it is a dummy cut away from real arcs + return false; + } + + // the cell has an outside before and an inside after it + // it is the start of an arc + return true; + + } + + /** Check if an internal node corresponds to the end angle of an arc. + * @param node internal node to check + * @return true if the node corresponds to the end angle of an arc + */ + private boolean isArcEnd(final BSPTree node) { + + if (!(Boolean) leafBefore(node).getAttribute()) { + // it has an outside cell before it, it may start an arc but not end it + return false; + } + + if ((Boolean) leafAfter(node).getAttribute()) { + // it has an inside cell after it, it is a dummy cut in the middle of an arc + return false; + } + + // the cell has an inside before and an outside after it + // it is the end of an arc + return true; + + } + + /** Get the next internal node. + * @param node current internal node + * @return next internal node in trigonometric order, or null + * if this is the last internal node + */ + private BSPTree nextInternalNode(BSPTree node) { + + if (childAfter(node).getCut() != null) { + // the next node is in the sub-tree + return leafAfter(node).getParent(); + } + + // there is nothing left deeper in the tree, we backtrack + while (isAfterParent(node)) { + node = node.getParent(); + } + return node.getParent(); + + } + + /** Get the previous internal node. + * @param node current internal node + * @return previous internal node in trigonometric order, or null + * if this is the first internal node + */ + private BSPTree previousInternalNode(BSPTree node) { + + if (childBefore(node).getCut() != null) { + // the next node is in the sub-tree + return leafBefore(node).getParent(); + } + + // there is nothing left deeper in the tree, we backtrack + while (isBeforeParent(node)) { + node = node.getParent(); + } + return node.getParent(); + + } + + /** Find the leaf node just before an internal node. + * @param node internal node at which the sub-tree starts + * @return leaf node just before the internal node + */ + private BSPTree leafBefore(BSPTree node) { + + node = childBefore(node); + while (node.getCut() != null) { + node = childAfter(node); + } + + return node; + + } + + /** Find the leaf node just after an internal node. + * @param node internal node at which the sub-tree starts + * @return leaf node just after the internal node + */ + private BSPTree leafAfter(BSPTree node) { + + node = childAfter(node); + while (node.getCut() != null) { + node = childBefore(node); + } + + return node; + + } + + /** Check if a node is the child before its parent in trigonometric order. + * @param node child node considered + * @return true is the node has a parent end is before it in trigonometric order + */ + private boolean isBeforeParent(final BSPTree node) { + final BSPTree parent = node.getParent(); + if (parent == null) { + return false; + } else { + return node == childBefore(parent); + } + } + + /** Check if a node is the child after its parent in trigonometric order. + * @param node child node considered + * @return true is the node has a parent end is after it in trigonometric order + */ + private boolean isAfterParent(final BSPTree node) { + final BSPTree parent = node.getParent(); + if (parent == null) { + return false; + } else { + return node == childAfter(parent); + } + } + + /** Find the child node just before an internal node. + * @param node internal node at which the sub-tree starts + * @return child node just before the internal node + */ + private BSPTree childBefore(BSPTree node) { + if (isDirect(node)) { + // smaller angles are on minus side, larger angles are on plus side + return node.getMinus(); + } else { + // smaller angles are on plus side, larger angles are on minus side + return node.getPlus(); + } + } + + /** Find the child node just after an internal node. + * @param node internal node at which the sub-tree starts + * @return child node just after the internal node + */ + private BSPTree childAfter(BSPTree node) { + if (isDirect(node)) { + // smaller angles are on minus side, larger angles are on plus side + return node.getPlus(); + } else { + // smaller angles are on plus side, larger angles are on minus side + return node.getMinus(); + } + } + + /** Check if an internal node has a direct limit angle. + * @param node internal node to check + * @return true if the limit angle is direct + */ + private boolean isDirect(final BSPTree node) { + return ((LimitAngle) node.getCut().getHyperplane()).isDirect(); + } + + /** Get the limit angle of an internal node. + * @param node internal node to check + * @return limit angle + */ + private double getAngle(final BSPTree node) { + return ((LimitAngle) node.getCut().getHyperplane()).getLocation().getAlpha(); + } + + /** {@inheritDoc} */ + @Override + public ArcsSet buildNew(final BSPTree tree) { + return new ArcsSet(tree, getTolerance()); + } + + /** {@inheritDoc} */ + @Override + protected void computeGeometricalProperties() { + if (getTree(false).getCut() == null) { + setBarycenter(S1Point.NaN); + setSize(((Boolean) getTree(false).getAttribute()) ? MathUtils.TWO_PI : 0); + } else { + double size = 0.0; + double sum = 0.0; + for (final double[] a : this) { + final double length = a[1] - a[0]; + size += length; + sum += length * (a[0] + a[1]); + } + setSize(size); + if (Precision.equals(size, MathUtils.TWO_PI, 0)) { + setBarycenter(S1Point.NaN); + } else if (size >= Precision.SAFE_MIN) { + setBarycenter(new S1Point(sum / (2 * size))); + } else { + final LimitAngle limit = (LimitAngle) getTree(false).getCut().getHyperplane(); + setBarycenter(limit.getLocation()); + } + } + } + + /** {@inheritDoc} + * @since 3.3 + */ + @Override + public BoundaryProjection projectToBoundary(final Point point) { + + // get position of test point + final double alpha = ((S1Point) point).getAlpha(); + + boolean wrapFirst = false; + double first = Double.NaN; + double previous = Double.NaN; + for (final double[] a : this) { + + if (Double.isNaN(first)) { + // remember the first angle in case we need it later + first = a[0]; + } + + if (!wrapFirst) { + if (alpha < a[0]) { + // the test point lies between the previous and the current arcs + // offset will be positive + if (Double.isNaN(previous)) { + // we need to wrap around the circle + wrapFirst = true; + } else { + final double previousOffset = alpha - previous; + final double currentOffset = a[0] - alpha; + if (previousOffset < currentOffset) { + return new BoundaryProjection(point, new S1Point(previous), previousOffset); + } else { + return new BoundaryProjection(point, new S1Point(a[0]), currentOffset); + } + } + } else if (alpha <= a[1]) { + // the test point lies within the current arc + // offset will be negative + final double offset0 = a[0] - alpha; + final double offset1 = alpha - a[1]; + if (offset0 < offset1) { + return new BoundaryProjection(point, new S1Point(a[1]), offset1); + } else { + return new BoundaryProjection(point, new S1Point(a[0]), offset0); + } + } + } + previous = a[1]; + } + + if (Double.isNaN(previous)) { + + // there are no points at all in the arcs set + return new BoundaryProjection(point, null, MathUtils.TWO_PI); + + } else { + + // the test point if before first arc and after last arc, + // somewhere around the 0/2 \pi crossing + if (wrapFirst) { + // the test point is between 0 and first + final double previousOffset = alpha - (previous - MathUtils.TWO_PI); + final double currentOffset = first - alpha; + if (previousOffset < currentOffset) { + return new BoundaryProjection(point, new S1Point(previous), previousOffset); + } else { + return new BoundaryProjection(point, new S1Point(first), currentOffset); + } + } else { + // the test point is between last and 2\pi + final double previousOffset = alpha - previous; + final double currentOffset = first + MathUtils.TWO_PI - alpha; + if (previousOffset < currentOffset) { + return new BoundaryProjection(point, new S1Point(previous), previousOffset); + } else { + return new BoundaryProjection(point, new S1Point(first), currentOffset); + } + } + + } + + } + + /** Build an ordered list of arcs representing the instance. + *

    This method builds this arcs set as an ordered list of + * {@link Arc Arc} elements. An empty tree will build an empty list + * while a tree representing the whole circle will build a one + * element list with bounds set to \( 0 and 2 \pi \).

    + * @return a new ordered list containing {@link Arc Arc} elements + */ + public List asList() { + final List list = new ArrayList(); + for (final double[] a : this) { + list.add(new Arc(a[0], a[1], getTolerance())); + } + return list; + } + + /** {@inheritDoc} + *

    + * The iterator returns the limit angles pairs of sub-arcs in trigonometric order. + *

    + *

    + * The iterator does not support the optional {@code remove} operation. + *

    + */ + public Iterator iterator() { + return new SubArcsIterator(); + } + + /** Local iterator for sub-arcs. */ + private class SubArcsIterator implements Iterator { + + /** Start of the first arc. */ + private final BSPTree firstStart; + + /** Current node. */ + private BSPTree current; + + /** Sub-arc no yet returned. */ + private double[] pending; + + /** Simple constructor. + */ + SubArcsIterator() { + + firstStart = getFirstArcStart(); + current = firstStart; + + if (firstStart == null) { + // all the leaf tree nodes share the same inside/outside status + if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) { + // it is an inside node, it represents the full circle + pending = new double[] { + 0, MathUtils.TWO_PI + }; + } else { + pending = null; + } + } else { + selectPending(); + } + } + + /** Walk the tree to select the pending sub-arc. + */ + private void selectPending() { + + // look for the start of the arc + BSPTree start = current; + while (start != null && !isArcStart(start)) { + start = nextInternalNode(start); + } + + if (start == null) { + // we have exhausted the iterator + current = null; + pending = null; + return; + } + + // look for the end of the arc + BSPTree end = start; + while (end != null && !isArcEnd(end)) { + end = nextInternalNode(end); + } + + if (end != null) { + + // we have identified the arc + pending = new double[] { + getAngle(start), getAngle(end) + }; + + // prepare search for next arc + current = end; + + } else { + + // the final arc wraps around 2\pi, its end is before the first start + end = firstStart; + while (end != null && !isArcEnd(end)) { + end = previousInternalNode(end); + } + if (end == null) { + // this should never happen + throw new MathInternalError(); + } + + // we have identified the last arc + pending = new double[] { + getAngle(start), getAngle(end) + MathUtils.TWO_PI + }; + + // there won't be any other arcs + current = null; + + } + + } + + /** {@inheritDoc} */ + public boolean hasNext() { + return pending != null; + } + + /** {@inheritDoc} */ + public double[] next() { + if (pending == null) { + throw new NoSuchElementException(); + } + final double[] next = pending; + selectPending(); + return next; + } + + /** {@inheritDoc} */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + /** Compute the relative position of the instance with respect + * to an arc. + *

    + * The {@link Side#MINUS} side of the arc is the one covered by the arc. + *

    + * @param arc arc to check instance against + * @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH} + * or {@link Side#HYPER} + * @deprecated as of 3.6, replaced with {@link #split(Arc)}.{@link Split#getSide()} + */ + @Deprecated + public Side side(final Arc arc) { + return split(arc).getSide(); + } + + /** Split the instance in two parts by an arc. + * @param arc splitting arc + * @return an object containing both the part of the instance + * on the plus side of the arc and the part of the + * instance on the minus side of the arc + */ + public Split split(final Arc arc) { + + final List minus = new ArrayList(); + final List plus = new ArrayList(); + + final double reference = FastMath.PI + arc.getInf(); + final double arcLength = arc.getSup() - arc.getInf(); + + for (final double[] a : this) { + final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf(); + final double arcOffset = a[0] - syncedStart; + final double syncedEnd = a[1] - arcOffset; + if (syncedStart < arcLength) { + // the start point a[0] is in the minus part of the arc + minus.add(a[0]); + if (syncedEnd > arcLength) { + // the end point a[1] is past the end of the arc + // so we leave the minus part and enter the plus part + final double minusToPlus = arcLength + arcOffset; + minus.add(minusToPlus); + plus.add(minusToPlus); + if (syncedEnd > MathUtils.TWO_PI) { + // in fact the end point a[1] goes far enough that we + // leave the plus part of the arc and enter the minus part again + final double plusToMinus = MathUtils.TWO_PI + arcOffset; + plus.add(plusToMinus); + minus.add(plusToMinus); + minus.add(a[1]); + } else { + // the end point a[1] is in the plus part of the arc + plus.add(a[1]); + } + } else { + // the end point a[1] is in the minus part of the arc + minus.add(a[1]); + } + } else { + // the start point a[0] is in the plus part of the arc + plus.add(a[0]); + if (syncedEnd > MathUtils.TWO_PI) { + // the end point a[1] wraps around to the start of the arc + // so we leave the plus part and enter the minus part + final double plusToMinus = MathUtils.TWO_PI + arcOffset; + plus.add(plusToMinus); + minus.add(plusToMinus); + if (syncedEnd > MathUtils.TWO_PI + arcLength) { + // in fact the end point a[1] goes far enough that we + // leave the minus part of the arc and enter the plus part again + final double minusToPlus = MathUtils.TWO_PI + arcLength + arcOffset; + minus.add(minusToPlus); + plus.add(minusToPlus); + plus.add(a[1]); + } else { + // the end point a[1] is in the minus part of the arc + minus.add(a[1]); + } + } else { + // the end point a[1] is in the plus part of the arc + plus.add(a[1]); + } + } + } + + return new Split(createSplitPart(plus), createSplitPart(minus)); + + } + + /** Add an arc limit to a BSP tree under construction. + * @param tree BSP tree under construction + * @param alpha arc limit + * @param isStart if true, the limit is the start of an arc + */ + private void addArcLimit(final BSPTree tree, final double alpha, final boolean isStart) { + + final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance()); + final BSPTree node = tree.getCell(limit.getLocation(), getTolerance()); + if (node.getCut() != null) { + // this should never happen + throw new MathInternalError(); + } + + node.insertCut(limit); + node.setAttribute(null); + node.getPlus().setAttribute(Boolean.FALSE); + node.getMinus().setAttribute(Boolean.TRUE); + + } + + /** Create a split part. + *

    + * As per construction, the list of limit angles is known to have + * an even number of entries, with start angles at even indices and + * end angles at odd indices. + *

    + * @param limits limit angles of the split part + * @return split part (may be null) + */ + private ArcsSet createSplitPart(final List limits) { + if (limits.isEmpty()) { + return null; + } else { + + // collapse close limit angles + for (int i = 0; i < limits.size(); ++i) { + final int j = (i + 1) % limits.size(); + final double lA = limits.get(i); + final double lB = MathUtils.normalizeAngle(limits.get(j), lA); + if (FastMath.abs(lB - lA) <= getTolerance()) { + // the two limits are too close to each other, we remove both of them + if (j > 0) { + // regular case, the two entries are consecutive ones + limits.remove(j); + limits.remove(i); + i = i - 1; + } else { + // special case, i the the last entry and j is the first entry + // we have wrapped around list end + final double lEnd = limits.remove(limits.size() - 1); + final double lStart = limits.remove(0); + if (limits.isEmpty()) { + // the ends were the only limits, is it a full circle or an empty circle? + if (lEnd - lStart > FastMath.PI) { + // it was full circle + return new ArcsSet(new BSPTree(Boolean.TRUE), getTolerance()); + } else { + // it was an empty circle + return null; + } + } else { + // we have removed the first interval start, so our list + // currently starts with an interval end, which is wrong + // we need to move this interval end to the end of the list + limits.add(limits.remove(0) + MathUtils.TWO_PI); + } + } + } + } + + // build the tree by adding all angular sectors + BSPTree tree = new BSPTree(Boolean.FALSE); + for (int i = 0; i < limits.size() - 1; i += 2) { + addArcLimit(tree, limits.get(i), true); + addArcLimit(tree, limits.get(i + 1), false); + } + + if (tree.getCut() == null) { + // we did not insert anything + return null; + } + + return new ArcsSet(tree, getTolerance()); + + } + } + + /** Class holding the results of the {@link #split split} method. + */ + public static class Split { + + /** Part of the arcs set on the plus side of the splitting arc. */ + private final ArcsSet plus; + + /** Part of the arcs set on the minus side of the splitting arc. */ + private final ArcsSet minus; + + /** Build a Split from its parts. + * @param plus part of the arcs set on the plus side of the + * splitting arc + * @param minus part of the arcs set on the minus side of the + * splitting arc + */ + private Split(final ArcsSet plus, final ArcsSet minus) { + this.plus = plus; + this.minus = minus; + } + + /** Get the part of the arcs set on the plus side of the splitting arc. + * @return part of the arcs set on the plus side of the splitting arc + */ + public ArcsSet getPlus() { + return plus; + } + + /** Get the part of the arcs set on the minus side of the splitting arc. + * @return part of the arcs set on the minus side of the splitting arc + */ + public ArcsSet getMinus() { + return minus; + } + + /** Get the side of the split arc with respect to its splitter. + * @return {@link Side#PLUS} if only {@link #getPlus()} returns non-null, + * {@link Side#MINUS} if only {@link #getMinus()} returns non-null, + * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()} + * return non-null or {@link Side#HYPER} if both {@link #getPlus()} and + * {@link #getMinus()} return null + * @since 3.6 + */ + public Side getSide() { + if (plus != null) { + if (minus != null) { + return Side.BOTH; + } else { + return Side.PLUS; + } + } else if (minus != null) { + return Side.MINUS; + } else { + return Side.HYPER; + } + } + + } + + /** Specialized exception for inconsistent BSP tree state inconsistency. + *

    + * This exception is thrown at {@link ArcsSet} construction time when the + * {@link Location inside/outside} + * state is not consistent at the 0, \(2 \pi \) crossing. + *

    + */ + public static class InconsistentStateAt2PiWrapping extends MathIllegalArgumentException { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140107L; + + /** Simple constructor. + */ + public InconsistentStateAt2PiWrapping() { + super(LocalizedFormats.INCONSISTENT_STATE_AT_2_PI_WRAPPING); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.java new file mode 100644 index 000000000..3565564b5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.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 com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.Point; + +/** This class represents a 1D oriented hyperplane on the circle. + *

    An hyperplane on the 1-sphere is an angle with an orientation.

    + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.3 + */ +public class LimitAngle implements Hyperplane { + + /** Angle location. */ + private S1Point location; + + /** Orientation. */ + private boolean direct; + + /** Tolerance below which angles are considered identical. */ + private final double tolerance; + + /** Simple constructor. + * @param location location of the hyperplane + * @param direct if true, the plus side of the hyperplane is towards + * angles greater than {@code location} + * @param tolerance tolerance below which angles are considered identical + */ + public LimitAngle(final S1Point location, final boolean direct, final double tolerance) { + this.location = location; + this.direct = direct; + this.tolerance = tolerance; + } + + /** Copy the instance. + *

    Since instances are immutable, this method directly returns + * the instance.

    + * @return the instance itself + */ + public LimitAngle copySelf() { + return this; + } + + /** {@inheritDoc} */ + public double getOffset(final Point point) { + final double delta = ((S1Point) point).getAlpha() - location.getAlpha(); + return direct ? delta : -delta; + } + + /** Check if the hyperplane orientation is direct. + * @return true if the plus side of the hyperplane is towards + * angles greater than hyperplane location + */ + public boolean isDirect() { + return direct; + } + + /** Get the reverse of the instance. + *

    Get a limit angle with reversed orientation with respect to the + * instance. A new object is built, the instance is untouched.

    + * @return a new limit angle, with orientation opposite to the instance orientation + */ + public LimitAngle getReverse() { + return new LimitAngle(location, !direct, tolerance); + } + + /** Build a region covering the whole hyperplane. + *

    Since this class represent zero dimension spaces which does + * not have lower dimension sub-spaces, this method returns a dummy + * implementation of a {@link + * SubHyperplane SubHyperplane}. + * This implementation is only used to allow the {@link + * SubHyperplane + * SubHyperplane} class implementation to work properly, it should + * not be used otherwise.

    + * @return a dummy sub hyperplane + */ + public SubLimitAngle wholeHyperplane() { + return new SubLimitAngle(this, null); + } + + /** Build a region covering the whole space. + * @return a region containing the instance (really an {@link + * ArcsSet IntervalsSet} instance) + */ + public ArcsSet wholeSpace() { + return new ArcsSet(tolerance); + } + + /** {@inheritDoc} */ + public boolean sameOrientationAs(final Hyperplane other) { + return !(direct ^ ((LimitAngle) other).direct); + } + + /** Get the hyperplane location on the circle. + * @return the hyperplane location + */ + public S1Point getLocation() { + return location; + } + + /** {@inheritDoc} */ + public Point project(Point point) { + return location; + } + + /** {@inheritDoc} */ + public double getTolerance() { + return tolerance; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/S1Point.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/S1Point.java new file mode 100644 index 000000000..c77f0a4d0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/S1Point.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents a point on the 1-sphere. + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.3 + */ +public class S1Point implements Point { + + // CHECKSTYLE: stop ConstantName + /** A vector with all coordinates set to NaN. */ + public static final S1Point NaN = new S1Point(Double.NaN, Vector2D.NaN); + // CHECKSTYLE: resume ConstantName + + /** Serializable UID. */ + private static final long serialVersionUID = 20131218L; + + /** Azimuthal angle \( \alpha \). */ + private final double alpha; + + /** Corresponding 2D normalized vector. */ + private final Vector2D vector; + + /** Simple constructor. + * Build a vector from its coordinates + * @param alpha azimuthal angle \( \alpha \) + * @see #getAlpha() + */ + public S1Point(final double alpha) { + this(MathUtils.normalizeAngle(alpha, FastMath.PI), + new Vector2D(FastMath.cos(alpha), FastMath.sin(alpha))); + } + + /** Build a point from its internal components. + * @param alpha azimuthal angle \( \alpha \) + * @param vector corresponding vector + */ + private S1Point(final double alpha, final Vector2D vector) { + this.alpha = alpha; + this.vector = vector; + } + + /** Get the azimuthal angle \( \alpha \). + * @return azimuthal angle \( \alpha \) + * @see #S1Point(double) + */ + public double getAlpha() { + return alpha; + } + + /** Get the corresponding normalized vector in the 2D euclidean space. + * @return normalized vector + */ + public Vector2D getVector() { + return vector; + } + + /** {@inheritDoc} */ + public Space getSpace() { + return Sphere1D.getInstance(); + } + + /** {@inheritDoc} */ + public boolean isNaN() { + return Double.isNaN(alpha); + } + + /** {@inheritDoc} */ + public double distance(final Point point) { + return distance(this, (S1Point) point); + } + + /** Compute the distance (angular separation) between two points. + * @param p1 first vector + * @param p2 second vector + * @return the angular separation between p1 and p2 + */ + public static double distance(S1Point p1, S1Point p2) { + return Vector2D.angle(p1.vector, p2.vector); + } + + /** + * Test for the equality of two points on the 2-sphere. + *

    + * If all coordinates of two points are exactly the same, and none are + * Double.NaN, the two points are considered to be equal. + *

    + *

    + * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) coordinates of the + * 2D vector are equal to Double.NaN, the 2D vector is equal to + * {@link #NaN}. + *

    + * + * @param other Object to test for equality to this + * @return true if two points on the 2-sphere objects are equal, false if + * object is null, not an instance of S2Point, or + * not equal to this S2Point instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof S1Point) { + final S1Point rhs = (S1Point) other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return alpha == rhs.alpha; + } + + return false; + + } + + /** + * Get a hashCode for the 2D vector. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 542; + } + return 1759 * MathUtils.hash(alpha); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java new file mode 100644 index 000000000..eea978a30 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** + * This class implements a one-dimensional sphere (i.e. a circle). + *

    + * We use here the topologists definition of the 1-sphere (see + * Sphere on + * MathWorld), i.e. the 1-sphere is the one-dimensional closed curve + * defined in 2D as x2+y2=1. + *

    + * @since 3.3 + */ +public class Sphere1D implements Serializable, Space { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20131218L; + + /** Private constructor for the singleton. + */ + private Sphere1D() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static Sphere1D getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public int getDimension() { + return 1; + } + + /** {@inheritDoc} + *

    + * As the 1-dimension sphere does not have proper sub-spaces, + * this method always throws a {@link NoSubSpaceException} + *

    + * @return nothing + * @throws NoSubSpaceException in all cases + */ + public Space getSubSpace() throws NoSubSpaceException { + throw new NoSubSpaceException(); + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final Sphere1D INSTANCE = new Sphere1D(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + + /** Specialized exception for inexistent sub-space. + *

    + * This exception is thrown when attempting to get the sub-space of a one-dimensional space + *

    + */ + public static class NoSubSpaceException extends MathUnsupportedOperationException { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140225L; + + /** Simple constructor. + */ + public NoSubSpaceException() { + super(LocalizedFormats.NOT_SUPPORTED_IN_DIMENSION_N, 1); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java new file mode 100644 index 000000000..7fa09bf95 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; + +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; + +/** This class represents sub-hyperplane for {@link LimitAngle}. + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.3 + */ +public class SubLimitAngle extends AbstractSubHyperplane { + + /** Simple constructor. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + public SubLimitAngle(final Hyperplane hyperplane, + final Region remainingRegion) { + super(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public double getSize() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public boolean isEmpty() { + return false; + } + + /** {@inheritDoc} */ + @Override + protected AbstractSubHyperplane buildNew(final Hyperplane hyperplane, + final Region remainingRegion) { + return new SubLimitAngle(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public SplitSubHyperplane split(final Hyperplane hyperplane) { + final double global = hyperplane.getOffset(((LimitAngle) getHyperplane()).getLocation()); + return (global < -1.0e-10) ? + new SplitSubHyperplane(null, this) : + new SplitSubHyperplane(this, null); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/package-info.java new file mode 100644 index 000000000..00d108017 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/oned/package-info.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides basic geometry components on the 1-sphere. + *

    + *

    + * We use here the topologists definition of the 1-sphere (see + * Sphere on + * MathWorld), i.e. the 1-sphere is the one-dimensional closed curve + * defined in 2D as x2+y2=1. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.oned; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Circle.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Circle.java new file mode 100644 index 000000000..c2ec75510 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Circle.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Embedding; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Transform; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Arc; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.ArcsSet; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.S1Point; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Sphere1D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class represents an oriented great circle on the 2-sphere. + + *

    An oriented circle can be defined by a center point. The circle + * is the the set of points that are in the normal plan the center.

    + + *

    Since it is oriented the two spherical caps at its two sides are + * unambiguously identified as a left cap and a right cap. This can be + * used to identify the interior and the exterior in a simple way by + * local properties only when part of a line is used to define part of + * a spherical polygon boundary.

    + + * @since 3.3 + */ +public class Circle implements Hyperplane, Embedding { + + /** Pole or circle center. */ + private Vector3D pole; + + /** First axis in the equator plane, origin of the phase angles. */ + private Vector3D x; + + /** Second axis in the equator plane, in quadrature with respect to x. */ + private Vector3D y; + + /** Tolerance below which close sub-arcs are merged together. */ + private final double tolerance; + + /** Build a great circle from its pole. + *

    The circle is oriented in the trigonometric direction around pole.

    + * @param pole circle pole + * @param tolerance tolerance below which close sub-arcs are merged together + */ + public Circle(final Vector3D pole, final double tolerance) { + reset(pole); + this.tolerance = tolerance; + } + + /** Build a great circle from two non-aligned points. + *

    The circle is oriented from first to second point using the path smaller than \( \pi \).

    + * @param first first point contained in the great circle + * @param second second point contained in the great circle + * @param tolerance tolerance below which close sub-arcs are merged together + */ + public Circle(final S2Point first, final S2Point second, final double tolerance) { + reset(first.getVector().crossProduct(second.getVector())); + this.tolerance = tolerance; + } + + /** Build a circle from its internal components. + *

    The circle is oriented in the trigonometric direction around center.

    + * @param pole circle pole + * @param x first axis in the equator plane + * @param y second axis in the equator plane + * @param tolerance tolerance below which close sub-arcs are merged together + */ + private Circle(final Vector3D pole, final Vector3D x, final Vector3D y, + final double tolerance) { + this.pole = pole; + this.x = x; + this.y = y; + this.tolerance = tolerance; + } + + /** Copy constructor. + *

    The created instance is completely independent from the + * original instance, it is a deep copy.

    + * @param circle circle to copy + */ + public Circle(final Circle circle) { + this(circle.pole, circle.x, circle.y, circle.tolerance); + } + + /** {@inheritDoc} */ + public Circle copySelf() { + return new Circle(this); + } + + /** Reset the instance as if built from a pole. + *

    The circle is oriented in the trigonometric direction around pole.

    + * @param newPole circle pole + */ + public void reset(final Vector3D newPole) { + this.pole = newPole.normalize(); + this.x = newPole.orthogonal(); + this.y = Vector3D.crossProduct(newPole, x).normalize(); + } + + /** Revert the instance. + */ + public void revertSelf() { + // x remains the same + y = y.negate(); + pole = pole.negate(); + } + + /** Get the reverse of the instance. + *

    Get a circle with reversed orientation with respect to the + * instance. A new object is built, the instance is untouched.

    + * @return a new circle, with orientation opposite to the instance orientation + */ + public Circle getReverse() { + return new Circle(pole.negate(), x, y.negate(), tolerance); + } + + /** {@inheritDoc} */ + public Point project(Point point) { + return toSpace(toSubSpace(point)); + } + + /** {@inheritDoc} */ + public double getTolerance() { + return tolerance; + } + + /** {@inheritDoc} + * @see #getPhase(Vector3D) + */ + public S1Point toSubSpace(final Point point) { + return new S1Point(getPhase(((S2Point) point).getVector())); + } + + /** Get the phase angle of a direction. + *

    + * The direction may not belong to the circle as the + * phase is computed for the meridian plane between the circle + * pole and the direction. + *

    + * @param direction direction for which phase is requested + * @return phase angle of the direction around the circle + * @see #toSubSpace(Point) + */ + public double getPhase(final Vector3D direction) { + return FastMath.PI + FastMath.atan2(-direction.dotProduct(y), -direction.dotProduct(x)); + } + + /** {@inheritDoc} + * @see #getPointAt(double) + */ + public S2Point toSpace(final Point point) { + return new S2Point(getPointAt(((S1Point) point).getAlpha())); + } + + /** Get a circle point from its phase around the circle. + * @param alpha phase around the circle + * @return circle point on the sphere + * @see #toSpace(Point) + * @see #getXAxis() + * @see #getYAxis() + */ + public Vector3D getPointAt(final double alpha) { + return new Vector3D(FastMath.cos(alpha), x, FastMath.sin(alpha), y); + } + + /** Get the X axis of the circle. + *

    + * This method returns the same value as {@link #getPointAt(double) + * getPointAt(0.0)} but it does not do any computation and always + * return the same instance. + *

    + * @return an arbitrary x axis on the circle + * @see #getPointAt(double) + * @see #getYAxis() + * @see #getPole() + */ + public Vector3D getXAxis() { + return x; + } + + /** Get the Y axis of the circle. + *

    + * This method returns the same value as {@link #getPointAt(double) + * getPointAt(0.5 * FastMath.PI)} but it does not do any computation and always + * return the same instance. + *

    + * @return an arbitrary y axis point on the circle + * @see #getPointAt(double) + * @see #getXAxis() + * @see #getPole() + */ + public Vector3D getYAxis() { + return y; + } + + /** Get the pole of the circle. + *

    + * As the circle is a great circle, the pole does not + * belong to it. + *

    + * @return pole of the circle + * @see #getXAxis() + * @see #getYAxis() + */ + public Vector3D getPole() { + return pole; + } + + /** Get the arc of the instance that lies inside the other circle. + * @param other other circle + * @return arc of the instance that lies inside the other circle + */ + public Arc getInsideArc(final Circle other) { + final double alpha = getPhase(other.pole); + final double halfPi = 0.5 * FastMath.PI; + return new Arc(alpha - halfPi, alpha + halfPi, tolerance); + } + + /** {@inheritDoc} */ + public SubCircle wholeHyperplane() { + return new SubCircle(this, new ArcsSet(tolerance)); + } + + /** Build a region covering the whole space. + * @return a region containing the instance (really a {@link + * SphericalPolygonsSet SphericalPolygonsSet} instance) + */ + public SphericalPolygonsSet wholeSpace() { + return new SphericalPolygonsSet(tolerance); + } + + /** {@inheritDoc} + * @see #getOffset(Vector3D) + */ + public double getOffset(final Point point) { + return getOffset(((S2Point) point).getVector()); + } + + /** Get the offset (oriented distance) of a direction. + *

    The offset is defined as the angular distance between the + * circle center and the direction minus the circle radius. It + * is therefore 0 on the circle, positive for directions outside of + * the cone delimited by the circle, and negative inside the cone.

    + * @param direction direction to check + * @return offset of the direction + * @see #getOffset(Point) + */ + public double getOffset(final Vector3D direction) { + return Vector3D.angle(pole, direction) - 0.5 * FastMath.PI; + } + + /** {@inheritDoc} */ + public boolean sameOrientationAs(final Hyperplane other) { + final Circle otherC = (Circle) other; + return Vector3D.dotProduct(pole, otherC.pole) >= 0.0; + } + + /** Get a {@link Transform + * Transform} embedding a 3D rotation. + * @param rotation rotation to use + * @return a new transform that can be applied to either {@link + * Point Point}, {@link Circle Line} or {@link + * SubHyperplane + * SubHyperplane} instances + */ + public static Transform getTransform(final Rotation rotation) { + return new CircleTransform(rotation); + } + + /** Class embedding a 3D rotation. */ + private static class CircleTransform implements Transform { + + /** Underlying rotation. */ + private final Rotation rotation; + + /** Build a transform from a {@code Rotation}. + * @param rotation rotation to use + */ + CircleTransform(final Rotation rotation) { + this.rotation = rotation; + } + + /** {@inheritDoc} */ + public S2Point apply(final Point point) { + return new S2Point(rotation.applyTo(((S2Point) point).getVector())); + } + + /** {@inheritDoc} */ + public Circle apply(final Hyperplane hyperplane) { + final Circle circle = (Circle) hyperplane; + return new Circle(rotation.applyTo(circle.pole), + rotation.applyTo(circle.x), + rotation.applyTo(circle.y), + circle.tolerance); + } + + /** {@inheritDoc} */ + public SubHyperplane apply(final SubHyperplane sub, + final Hyperplane original, + final Hyperplane transformed) { + // as the circle is rotated, the limit angles are rotated too + return sub; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Edge.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Edge.java new file mode 100644 index 000000000..ffb8b1502 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Edge.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Arc; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** Spherical polygons boundary edge. + * @see SphericalPolygonsSet#getBoundaryLoops() + * @see Vertex + * @since 3.3 + */ +public class Edge { + + /** Start vertex. */ + private final Vertex start; + + /** End vertex. */ + private Vertex end; + + /** Length of the arc. */ + private final double length; + + /** Circle supporting the edge. */ + private final Circle circle; + + /** Build an edge not contained in any node yet. + * @param start start vertex + * @param end end vertex + * @param length length of the arc (it can be greater than \( \pi \)) + * @param circle circle supporting the edge + */ + Edge(final Vertex start, final Vertex end, final double length, final Circle circle) { + + this.start = start; + this.end = end; + this.length = length; + this.circle = circle; + + // connect the vertices back to the edge + start.setOutgoing(this); + end.setIncoming(this); + + } + + /** Get start vertex. + * @return start vertex + */ + public Vertex getStart() { + return start; + } + + /** Get end vertex. + * @return end vertex + */ + public Vertex getEnd() { + return end; + } + + /** Get the length of the arc. + * @return length of the arc (can be greater than \( \pi \)) + */ + public double getLength() { + return length; + } + + /** Get the circle supporting this edge. + * @return circle supporting this edge + */ + public Circle getCircle() { + return circle; + } + + /** Get an intermediate point. + *

    + * The angle along the edge should normally be between 0 and {@link #getLength()} + * in order to remain within edge limits. However, there are no checks on the + * value of the angle, so user can rebuild the full circle on which an edge is + * defined if they want. + *

    + * @param alpha angle along the edge, counted from {@link #getStart()} + * @return an intermediate point + */ + public Vector3D getPointAt(final double alpha) { + return circle.getPointAt(alpha + circle.getPhase(start.getLocation().getVector())); + } + + /** Connect the instance with a following edge. + * @param next edge following the instance + */ + void setNextEdge(final Edge next) { + end = next.getStart(); + end.setIncoming(this); + end.bindWith(getCircle()); + } + + /** Split the edge. + *

    + * Once split, this edge is not referenced anymore by the vertices, + * it is replaced by the two or three sub-edges and intermediate splitting + * vertices are introduced to connect these sub-edges together. + *

    + * @param splitCircle circle splitting the edge in several parts + * @param outsideList list where to put parts that are outside of the split circle + * @param insideList list where to put parts that are inside the split circle + */ + void split(final Circle splitCircle, + final List outsideList, final List insideList) { + + // get the inside arc, synchronizing its phase with the edge itself + final double edgeStart = circle.getPhase(start.getLocation().getVector()); + final Arc arc = circle.getInsideArc(splitCircle); + final double arcRelativeStart = MathUtils.normalizeAngle(arc.getInf(), edgeStart + FastMath.PI) - edgeStart; + final double arcRelativeEnd = arcRelativeStart + arc.getSize(); + final double unwrappedEnd = arcRelativeEnd - MathUtils.TWO_PI; + + // build the sub-edges + final double tolerance = circle.getTolerance(); + Vertex previousVertex = start; + if (unwrappedEnd >= length - tolerance) { + + // the edge is entirely contained inside the circle + // we don't split anything + insideList.add(this); + + } else { + + // there are at least some parts of the edge that should be outside + // (even is they are later be filtered out as being too small) + double alreadyManagedLength = 0; + if (unwrappedEnd >= 0) { + // the start of the edge is inside the circle + previousVertex = addSubEdge(previousVertex, + new Vertex(new S2Point(circle.getPointAt(edgeStart + unwrappedEnd))), + unwrappedEnd, insideList, splitCircle); + alreadyManagedLength = unwrappedEnd; + } + + if (arcRelativeStart >= length - tolerance) { + // the edge ends while still outside of the circle + if (unwrappedEnd >= 0) { + previousVertex = addSubEdge(previousVertex, end, + length - alreadyManagedLength, outsideList, splitCircle); + } else { + // the edge is entirely outside of the circle + // we don't split anything + outsideList.add(this); + } + } else { + // the edge is long enough to enter inside the circle + previousVertex = addSubEdge(previousVertex, + new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))), + arcRelativeStart - alreadyManagedLength, outsideList, splitCircle); + alreadyManagedLength = arcRelativeStart; + + if (arcRelativeEnd >= length - tolerance) { + // the edge ends while still inside of the circle + previousVertex = addSubEdge(previousVertex, end, + length - alreadyManagedLength, insideList, splitCircle); + } else { + // the edge is long enough to exit outside of the circle + previousVertex = addSubEdge(previousVertex, + new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))), + arcRelativeStart - alreadyManagedLength, insideList, splitCircle); + alreadyManagedLength = arcRelativeStart; + previousVertex = addSubEdge(previousVertex, end, + length - alreadyManagedLength, outsideList, splitCircle); + } + } + + } + + } + + /** Add a sub-edge to a list if long enough. + *

    + * If the length of the sub-edge to add is smaller than the {@link Circle#getTolerance()} + * tolerance of the support circle, it will be ignored. + *

    + * @param subStart start of the sub-edge + * @param subEnd end of the sub-edge + * @param subLength length of the sub-edge + * @param splitCircle circle splitting the edge in several parts + * @param list list where to put the sub-edge + * @return end vertex of the edge ({@code subEnd} if the edge was long enough and really + * added, {@code subStart} if the edge was too small and therefore ignored) + */ + private Vertex addSubEdge(final Vertex subStart, final Vertex subEnd, final double subLength, + final List list, final Circle splitCircle) { + + if (subLength <= circle.getTolerance()) { + // the edge is too short, we ignore it + return subStart; + } + + // really add the edge + subEnd.bindWith(splitCircle); + final Edge edge = new Edge(subStart, subEnd, subLength, circle); + list.add(edge); + return subEnd; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java new file mode 100644 index 000000000..f42ba3104 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Arc; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.ArcsSet; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.S1Point; + +/** Visitor building edges. + * @since 3.3 + */ +class EdgesBuilder implements BSPTreeVisitor { + + /** Root of the tree. */ + private final BSPTree root; + + /** Tolerance below which points are consider to be identical. */ + private final double tolerance; + + /** Built edges and their associated nodes. */ + private final Map> edgeToNode; + + /** Reversed map. */ + private final Map, List> nodeToEdgesList; + + /** Simple constructor. + * @param root tree root + * @param tolerance below which points are consider to be identical + */ + EdgesBuilder(final BSPTree root, final double tolerance) { + this.root = root; + this.tolerance = tolerance; + this.edgeToNode = new IdentityHashMap>(); + this.nodeToEdgesList = new IdentityHashMap, List>(); + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + nodeToEdgesList.put(node, new ArrayList()); + @SuppressWarnings("unchecked") + final BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); + if (attribute.getPlusOutside() != null) { + addContribution((SubCircle) attribute.getPlusOutside(), false, node); + } + if (attribute.getPlusInside() != null) { + addContribution((SubCircle) attribute.getPlusInside(), true, node); + } + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + } + + /** Add the contribution of a boundary edge. + * @param sub boundary facet + * @param reversed if true, the facet has the inside on its plus side + * @param node node to which the edge belongs + */ + private void addContribution(final SubCircle sub, final boolean reversed, + final BSPTree node) { + final Circle circle = (Circle) sub.getHyperplane(); + final List arcs = ((ArcsSet) sub.getRemainingRegion()).asList(); + for (final Arc a : arcs) { + final Vertex start = new Vertex((S2Point) circle.toSpace(new S1Point(a.getInf()))); + final Vertex end = new Vertex((S2Point) circle.toSpace(new S1Point(a.getSup()))); + start.bindWith(circle); + end.bindWith(circle); + final Edge edge; + if (reversed) { + edge = new Edge(end, start, a.getSize(), circle.getReverse()); + } else { + edge = new Edge(start, end, a.getSize(), circle); + } + edgeToNode.put(edge, node); + nodeToEdgesList.get(node).add(edge); + } + } + + /** Get the edge that should naturally follow another one. + * @param previous edge to be continued + * @return other edge, starting where the previous one ends (they + * have not been connected yet) + * @exception MathIllegalStateException if there is not a single other edge + */ + private Edge getFollowingEdge(final Edge previous) + throws MathIllegalStateException { + + // get the candidate nodes + final S2Point point = previous.getEnd().getLocation(); + final List> candidates = root.getCloseCuts(point, tolerance); + + // the following edge we are looking for must start from one of the candidates nodes + double closest = tolerance; + Edge following = null; + for (final BSPTree node : candidates) { + for (final Edge edge : nodeToEdgesList.get(node)) { + if (edge != previous && edge.getStart().getIncoming() == null) { + final Vector3D edgeStart = edge.getStart().getLocation().getVector(); + final double gap = Vector3D.angle(point.getVector(), edgeStart); + if (gap <= closest) { + closest = gap; + following = edge; + } + } + } + } + + if (following == null) { + final Vector3D previousStart = previous.getStart().getLocation().getVector(); + if (Vector3D.angle(point.getVector(), previousStart) <= tolerance) { + // the edge connects back to itself + return previous; + } + + // this should never happen + throw new MathIllegalStateException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN); + + } + + return following; + + } + + /** Get the boundary edges. + * @return boundary edges + * @exception MathIllegalStateException if there is not a single other edge + */ + public List getEdges() throws MathIllegalStateException { + + // connect the edges + for (final Edge previous : edgeToNode.keySet()) { + previous.setNextEdge(getFollowingEdge(previous)); + } + + return new ArrayList(edgeToNode.keySet()); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java new file mode 100644 index 000000000..b6adca228 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** Visitor computing geometrical properties. + * @since 3.3 + */ +class PropertiesComputer implements BSPTreeVisitor { + + /** Tolerance below which points are consider to be identical. */ + private final double tolerance; + + /** Summed area. */ + private double summedArea; + + /** Summed barycenter. */ + private Vector3D summedBarycenter; + + /** List of points strictly inside convex cells. */ + private final List convexCellsInsidePoints; + + /** Simple constructor. + * @param tolerance below which points are consider to be identical + */ + PropertiesComputer(final double tolerance) { + this.tolerance = tolerance; + this.summedArea = 0; + this.summedBarycenter = Vector3D.ZERO; + this.convexCellsInsidePoints = new ArrayList(); + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree node) { + // nothing to do here + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree node) { + if ((Boolean) node.getAttribute()) { + + // transform this inside leaf cell into a simple convex polygon + final SphericalPolygonsSet convex = + new SphericalPolygonsSet(node.pruneAroundConvexCell(Boolean.TRUE, + Boolean.FALSE, + null), + tolerance); + + // extract the start of the single loop boundary of the convex cell + final List boundary = convex.getBoundaryLoops(); + if (boundary.size() != 1) { + // this should never happen + throw new MathInternalError(); + } + + // compute the geometrical properties of the convex cell + final double area = convexCellArea(boundary.get(0)); + final Vector3D barycenter = convexCellBarycenter(boundary.get(0)); + convexCellsInsidePoints.add(barycenter); + + // add the cell contribution to the global properties + summedArea += area; + summedBarycenter = new Vector3D(1, summedBarycenter, area, barycenter); + + } + } + + /** Compute convex cell area. + * @param start start vertex of the convex cell boundary + * @return area + */ + private double convexCellArea(final Vertex start) { + + int n = 0; + double sum = 0; + + // loop around the cell + for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) { + + // find path interior angle at vertex + final Vector3D previousPole = e.getCircle().getPole(); + final Vector3D nextPole = e.getEnd().getOutgoing().getCircle().getPole(); + final Vector3D point = e.getEnd().getLocation().getVector(); + double alpha = FastMath.atan2(Vector3D.dotProduct(nextPole, Vector3D.crossProduct(point, previousPole)), + -Vector3D.dotProduct(nextPole, previousPole)); + if (alpha < 0) { + alpha += MathUtils.TWO_PI; + } + sum += alpha; + n++; + } + + // compute area using extended Girard theorem + // see Spherical Trigonometry: For the Use of Colleges and Schools by I. Todhunter + // article 99 in chapter VIII Area Of a Spherical Triangle. Spherical Excess. + // book available from project Gutenberg at http://www.gutenberg.org/ebooks/19770 + return sum - (n - 2) * FastMath.PI; + + } + + /** Compute convex cell barycenter. + * @param start start vertex of the convex cell boundary + * @return barycenter + */ + private Vector3D convexCellBarycenter(final Vertex start) { + + int n = 0; + Vector3D sumB = Vector3D.ZERO; + + // loop around the cell + for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) { + sumB = new Vector3D(1, sumB, e.getLength(), e.getCircle().getPole()); + n++; + } + + return sumB.normalize(); + + } + + /** Get the area. + * @return area + */ + public double getArea() { + return summedArea; + } + + /** Get the barycenter. + * @return barycenter + */ + public S2Point getBarycenter() { + if (summedBarycenter.getNormSq() == 0) { + return S2Point.NaN; + } else { + return new S2Point(summedBarycenter); + } + } + + /** Get the points strictly inside convex cells. + * @return points strictly inside convex cells + */ + public List getConvexCellsInsidePoints() { + return convexCellsInsidePoints; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/S2Point.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/S2Point.java new file mode 100644 index 000000000..d15029ea5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/S2Point.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Space; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents a point on the 2-sphere. + *

    + * We use the mathematical convention to use the azimuthal angle \( \theta \) + * in the x-y plane as the first coordinate, and the polar angle \( \varphi \) + * as the second coordinate (see Spherical + * Coordinates in MathWorld). + *

    + *

    Instances of this class are guaranteed to be immutable.

    + * @since 3.3 + */ +public class S2Point implements Point { + + /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */ + public static final S2Point PLUS_I = new S2Point(0, 0.5 * FastMath.PI, Vector3D.PLUS_I); + + /** +J (coordinates: \( \theta = \pi/2, \varphi = \pi/2 \))). */ + public static final S2Point PLUS_J = new S2Point(0.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.PLUS_J); + + /** +K (coordinates: \( \theta = any angle, \varphi = 0 \)). */ + public static final S2Point PLUS_K = new S2Point(0, 0, Vector3D.PLUS_K); + + /** -I (coordinates: \( \theta = \pi, \varphi = \pi/2 \)). */ + public static final S2Point MINUS_I = new S2Point(FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_I); + + /** -J (coordinates: \( \theta = 3\pi/2, \varphi = \pi/2 \)). */ + public static final S2Point MINUS_J = new S2Point(1.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_J); + + /** -K (coordinates: \( \theta = any angle, \varphi = \pi \)). */ + public static final S2Point MINUS_K = new S2Point(0, FastMath.PI, Vector3D.MINUS_K); + + // CHECKSTYLE: stop ConstantName + /** A vector with all coordinates set to NaN. */ + public static final S2Point NaN = new S2Point(Double.NaN, Double.NaN, Vector3D.NaN); + // CHECKSTYLE: resume ConstantName + + /** Serializable UID. */ + private static final long serialVersionUID = 20131218L; + + /** Azimuthal angle \( \theta \) in the x-y plane. */ + private final double theta; + + /** Polar angle \( \varphi \). */ + private final double phi; + + /** Corresponding 3D normalized vector. */ + private final Vector3D vector; + + /** Simple constructor. + * Build a vector from its spherical coordinates + * @param theta azimuthal angle \( \theta \) in the x-y plane + * @param phi polar angle \( \varphi \) + * @see #getTheta() + * @see #getPhi() + * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range + */ + public S2Point(final double theta, final double phi) + throws OutOfRangeException { + this(theta, phi, vector(theta, phi)); + } + + /** Simple constructor. + * Build a vector from its underlying 3D vector + * @param vector 3D vector + * @exception MathArithmeticException if vector norm is zero + */ + public S2Point(final Vector3D vector) throws MathArithmeticException { + this(FastMath.atan2(vector.getY(), vector.getX()), Vector3D.angle(Vector3D.PLUS_K, vector), + vector.normalize()); + } + + /** Build a point from its internal components. + * @param theta azimuthal angle \( \theta \) in the x-y plane + * @param phi polar angle \( \varphi \) + * @param vector corresponding vector + */ + private S2Point(final double theta, final double phi, final Vector3D vector) { + this.theta = theta; + this.phi = phi; + this.vector = vector; + } + + /** Build the normalized vector corresponding to spherical coordinates. + * @param theta azimuthal angle \( \theta \) in the x-y plane + * @param phi polar angle \( \varphi \) + * @return normalized vector + * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range + */ + private static Vector3D vector(final double theta, final double phi) + throws OutOfRangeException { + + if (phi < 0 || phi > FastMath.PI) { + throw new OutOfRangeException(phi, 0, FastMath.PI); + } + + final double cosTheta = FastMath.cos(theta); + final double sinTheta = FastMath.sin(theta); + final double cosPhi = FastMath.cos(phi); + final double sinPhi = FastMath.sin(phi); + + return new Vector3D(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi); + + } + + /** Get the azimuthal angle \( \theta \) in the x-y plane. + * @return azimuthal angle \( \theta \) in the x-y plane + * @see #S2Point(double, double) + */ + public double getTheta() { + return theta; + } + + /** Get the polar angle \( \varphi \). + * @return polar angle \( \varphi \) + * @see #S2Point(double, double) + */ + public double getPhi() { + return phi; + } + + /** Get the corresponding normalized vector in the 3D euclidean space. + * @return normalized vector + */ + public Vector3D getVector() { + return vector; + } + + /** {@inheritDoc} */ + public Space getSpace() { + return Sphere2D.getInstance(); + } + + /** {@inheritDoc} */ + public boolean isNaN() { + return Double.isNaN(theta) || Double.isNaN(phi); + } + + /** Get the opposite of the instance. + * @return a new vector which is opposite to the instance + */ + public S2Point negate() { + return new S2Point(-theta, FastMath.PI - phi, vector.negate()); + } + + /** {@inheritDoc} */ + public double distance(final Point point) { + return distance(this, (S2Point) point); + } + + /** Compute the distance (angular separation) between two points. + * @param p1 first vector + * @param p2 second vector + * @return the angular separation between p1 and p2 + */ + public static double distance(S2Point p1, S2Point p2) { + return Vector3D.angle(p1.vector, p2.vector); + } + + /** + * Test for the equality of two points on the 2-sphere. + *

    + * If all coordinates of two points are exactly the same, and none are + * Double.NaN, the two points are considered to be equal. + *

    + *

    + * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) coordinates of the + * 2D vector are equal to Double.NaN, the 2D vector is equal to + * {@link #NaN}. + *

    + * + * @param other Object to test for equality to this + * @return true if two points on the 2-sphere objects are equal, false if + * object is null, not an instance of S2Point, or + * not equal to this S2Point instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof S2Point) { + final S2Point rhs = (S2Point) other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return (theta == rhs.theta) && (phi == rhs.phi); + } + return false; + } + + /** + * Get a hashCode for the 2D vector. + *

    + * All NaN values have the same hash code.

    + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 542; + } + return 134 * (37 * MathUtils.hash(theta) + MathUtils.hash(phi)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java new file mode 100644 index 000000000..28af25e29 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Sphere1D; +import com.fr.third.org.apache.commons.math3.geometry.Space; + +/** + * This class implements a two-dimensional sphere (i.e. the regular sphere). + *

    + * We use here the topologists definition of the 2-sphere (see + * Sphere on + * MathWorld), i.e. the 2-sphere is the two-dimensional surface + * defined in 3D as x2+y2+z2=1. + *

    + * @since 3.3 + */ +public class Sphere2D implements Serializable, Space { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20131218L; + + /** Private constructor for the singleton. + */ + private Sphere2D() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static Sphere2D getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public int getDimension() { + return 2; + } + + /** {@inheritDoc} */ + public Sphere1D getSubSpace() { + return Sphere1D.getInstance(); + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final Sphere2D INSTANCE = new Sphere2D(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java new file mode 100644 index 000000000..1bcd852aa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java @@ -0,0 +1,568 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.geometry.Point; +import com.fr.third.org.apache.commons.math3.geometry.Vector; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.enclosing.EnclosingBall; +import com.fr.third.org.apache.commons.math3.geometry.enclosing.WelzlEncloser; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Euclidean3D; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.RotationConvention; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.SphereGenerator; +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BSPTree; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.BoundaryProjection; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.RegionFactory; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Sphere1D; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** This class represents a region on the 2-sphere: a set of spherical polygons. + * @since 3.3 + */ +public class SphericalPolygonsSet extends AbstractRegion { + + /** Boundary defined as an array of closed loops start vertices. */ + private List loops; + + /** Build a polygons set representing the whole real 2-sphere. + * @param tolerance below which points are consider to be identical + */ + public SphericalPolygonsSet(final double tolerance) { + super(tolerance); + } + + /** Build a polygons set representing a hemisphere. + * @param pole pole of the hemisphere (the pole is in the inside half) + * @param tolerance below which points are consider to be identical + */ + public SphericalPolygonsSet(final Vector3D pole, final double tolerance) { + super(new BSPTree(new Circle(pole, tolerance).wholeHyperplane(), + new BSPTree(Boolean.FALSE), + new BSPTree(Boolean.TRUE), + null), + tolerance); + } + + /** Build a polygons set representing a regular polygon. + * @param center center of the polygon (the center is in the inside half) + * @param meridian point defining the reference meridian for first polygon vertex + * @param outsideRadius distance of the vertices to the center + * @param n number of sides of the polygon + * @param tolerance below which points are consider to be identical + */ + public SphericalPolygonsSet(final Vector3D center, final Vector3D meridian, + final double outsideRadius, final int n, + final double tolerance) { + this(tolerance, createRegularPolygonVertices(center, meridian, outsideRadius, n)); + } + + /** Build a polygons set from a BSP tree. + *

    The leaf nodes of the BSP tree must have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}

    + * @param tree inside/outside BSP tree representing the region + * @param tolerance below which points are consider to be identical + */ + public SphericalPolygonsSet(final BSPTree tree, final double tolerance) { + super(tree, tolerance); + } + + /** Build a polygons set from a Boundary REPresentation (B-rep). + *

    The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.

    + *

    The boundary elements can be in any order, and can form + * several non-connected sets (like for example polygons with holes + * or a set of disjoint polygons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link + * Region#checkPoint(Point) + * checkPoint} method will not be meaningful anymore.

    + *

    If the boundary is empty, the region will represent the whole + * space.

    + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @param tolerance below which points are consider to be identical + */ + public SphericalPolygonsSet(final Collection> boundary, final double tolerance) { + super(boundary, tolerance); + } + + /** Build a polygon from a simple list of vertices. + *

    The boundary is provided as a list of points considering to + * represent the vertices of a simple loop. The interior part of the + * region is on the left side of this path and the exterior is on its + * right side.

    + *

    This constructor does not handle polygons with a boundary + * forming several disconnected paths (such as polygons with holes).

    + *

    For cases where this simple constructor applies, it is expected to + * be numerically more robust than the {@link #SphericalPolygonsSet(Collection, + * double) general constructor} using {@link SubHyperplane subhyperplanes}.

    + *

    If the list is empty, the region will represent the whole + * space.

    + *

    + * Polygons with thin pikes or dents are inherently difficult to handle because + * they involve circles with almost opposite directions at some vertices. Polygons + * whose vertices come from some physical measurement with noise are also + * difficult because an edge that should be straight may be broken in lots of + * different pieces with almost equal directions. In both cases, computing the + * circles intersections is not numerically robust due to the almost 0 or almost + * π angle. Such cases need to carefully adjust the {@code hyperplaneThickness} + * parameter. A too small value would often lead to completely wrong polygons + * with large area wrongly identified as inside or outside. Large values are + * often much safer. As a rule of thumb, a value slightly below the size of the + * most accurate detail needed is a good value for the {@code hyperplaneThickness} + * parameter. + *

    + * @param hyperplaneThickness tolerance below which points are considered to + * belong to the hyperplane (which is therefore more a slab) + * @param vertices vertices of the simple loop boundary + */ + public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ... vertices) { + super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness); + } + + /** Build the vertices representing a regular polygon. + * @param center center of the polygon (the center is in the inside half) + * @param meridian point defining the reference meridian for first polygon vertex + * @param outsideRadius distance of the vertices to the center + * @param n number of sides of the polygon + * @return vertices array + */ + private static S2Point[] createRegularPolygonVertices(final Vector3D center, final Vector3D meridian, + final double outsideRadius, final int n) { + final S2Point[] array = new S2Point[n]; + final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian), + outsideRadius, RotationConvention.VECTOR_OPERATOR); + array[0] = new S2Point(r0.applyTo(center)); + + final Rotation r = new Rotation(center, MathUtils.TWO_PI / n, RotationConvention.VECTOR_OPERATOR); + for (int i = 1; i < n; ++i) { + array[i] = new S2Point(r.applyTo(array[i - 1].getVector())); + } + + return array; + } + + /** Build the BSP tree of a polygons set from a simple list of vertices. + *

    The boundary is provided as a list of points considering to + * represent the vertices of a simple loop. The interior part of the + * region is on the left side of this path and the exterior is on its + * right side.

    + *

    This constructor does not handle polygons with a boundary + * forming several disconnected paths (such as polygons with holes).

    + *

    This constructor handles only polygons with edges strictly shorter + * than \( \pi \). If longer edges are needed, they need to be broken up + * in smaller sub-edges so this constraint holds.

    + *

    For cases where this simple constructor applies, it is expected to + * be numerically more robust than the {@link #PolygonsSet(Collection) general + * constructor} using {@link SubHyperplane subhyperplanes}.

    + * @param hyperplaneThickness tolerance below which points are consider to + * belong to the hyperplane (which is therefore more a slab) + * @param vertices vertices of the simple loop boundary + * @return the BSP tree of the input vertices + */ + private static BSPTree verticesToTree(final double hyperplaneThickness, + final S2Point ... vertices) { + + final int n = vertices.length; + if (n == 0) { + // the tree represents the whole space + return new BSPTree(Boolean.TRUE); + } + + // build the vertices + final Vertex[] vArray = new Vertex[n]; + for (int i = 0; i < n; ++i) { + vArray[i] = new Vertex(vertices[i]); + } + + // build the edges + List edges = new ArrayList(n); + Vertex end = vArray[n - 1]; + for (int i = 0; i < n; ++i) { + + // get the endpoints of the edge + final Vertex start = end; + end = vArray[i]; + + // get the circle supporting the edge, taking care not to recreate it + // if it was already created earlier due to another edge being aligned + // with the current one + Circle circle = start.sharedCircleWith(end); + if (circle == null) { + circle = new Circle(start.getLocation(), end.getLocation(), hyperplaneThickness); + } + + // create the edge and store it + edges.add(new Edge(start, end, + Vector3D.angle(start.getLocation().getVector(), + end.getLocation().getVector()), + circle)); + + // check if another vertex also happens to be on this circle + for (final Vertex vertex : vArray) { + if (vertex != start && vertex != end && + FastMath.abs(circle.getOffset(vertex.getLocation())) <= hyperplaneThickness) { + vertex.bindWith(circle); + } + } + + } + + // build the tree top-down + final BSPTree tree = new BSPTree(); + insertEdges(hyperplaneThickness, tree, edges); + + return tree; + + } + + /** Recursively build a tree by inserting cut sub-hyperplanes. + * @param hyperplaneThickness tolerance below which points are considered to + * belong to the hyperplane (which is therefore more a slab) + * @param node current tree node (it is a leaf node at the beginning + * of the call) + * @param edges list of edges to insert in the cell defined by this node + * (excluding edges not belonging to the cell defined by this node) + */ + private static void insertEdges(final double hyperplaneThickness, + final BSPTree node, + final List edges) { + + // find an edge with an hyperplane that can be inserted in the node + int index = 0; + Edge inserted = null; + while (inserted == null && index < edges.size()) { + inserted = edges.get(index++); + if (!node.insertCut(inserted.getCircle())) { + inserted = null; + } + } + + if (inserted == null) { + // no suitable edge was found, the node remains a leaf node + // we need to set its inside/outside boolean indicator + final BSPTree parent = node.getParent(); + if (parent == null || node == parent.getMinus()) { + node.setAttribute(Boolean.TRUE); + } else { + node.setAttribute(Boolean.FALSE); + } + return; + } + + // we have split the node by inserting an edge as a cut sub-hyperplane + // distribute the remaining edges in the two sub-trees + final List outsideList = new ArrayList(); + final List insideList = new ArrayList(); + for (final Edge edge : edges) { + if (edge != inserted) { + edge.split(inserted.getCircle(), outsideList, insideList); + } + } + + // recurse through lower levels + if (!outsideList.isEmpty()) { + insertEdges(hyperplaneThickness, node.getPlus(), outsideList); + } else { + node.getPlus().setAttribute(Boolean.FALSE); + } + if (!insideList.isEmpty()) { + insertEdges(hyperplaneThickness, node.getMinus(), insideList); + } else { + node.getMinus().setAttribute(Boolean.TRUE); + } + + } + + /** {@inheritDoc} */ + @Override + public SphericalPolygonsSet buildNew(final BSPTree tree) { + return new SphericalPolygonsSet(tree, getTolerance()); + } + + /** {@inheritDoc} + * @exception MathIllegalStateException if the tolerance setting does not allow to build + * a clean non-ambiguous boundary + */ + @Override + protected void computeGeometricalProperties() throws MathIllegalStateException { + + final BSPTree tree = getTree(true); + + if (tree.getCut() == null) { + + // the instance has a single cell without any boundaries + + if (tree.getCut() == null && (Boolean) tree.getAttribute()) { + // the instance covers the whole space + setSize(4 * FastMath.PI); + setBarycenter(new S2Point(0, 0)); + } else { + setSize(0); + setBarycenter(S2Point.NaN); + } + + } else { + + // the instance has a boundary + final PropertiesComputer pc = new PropertiesComputer(getTolerance()); + tree.visit(pc); + setSize(pc.getArea()); + setBarycenter(pc.getBarycenter()); + + } + + } + + /** Get the boundary loops of the polygon. + *

    The polygon boundary can be represented as a list of closed loops, + * each loop being given by exactly one of its vertices. From each loop + * start vertex, one can follow the loop by finding the outgoing edge, + * then the end vertex, then the next outgoing edge ... until the start + * vertex of the loop (exactly the same instance) is found again once + * the full loop has been visited.

    + *

    If the polygon has no boundary at all, a zero length loop + * array will be returned.

    + *

    If the polygon is a simple one-piece polygon, then the returned + * array will contain a single vertex. + *

    + *

    All edges in the various loops have the inside of the region on + * their left side (i.e. toward their pole) and the outside on their + * right side (i.e. away from their pole) when moving in the underlying + * circle direction. This means that the closed loops obey the direct + * trigonometric orientation.

    + * @return boundary of the polygon, organized as an unmodifiable list of loops start vertices. + * @exception MathIllegalStateException if the tolerance setting does not allow to build + * a clean non-ambiguous boundary + * @see Vertex + * @see Edge + */ + public List getBoundaryLoops() throws MathIllegalStateException { + + if (loops == null) { + if (getTree(false).getCut() == null) { + loops = Collections.emptyList(); + } else { + + // sort the arcs according to their start point + final BSPTree root = getTree(true); + final EdgesBuilder visitor = new EdgesBuilder(root, getTolerance()); + root.visit(visitor); + final List edges = visitor.getEdges(); + + + // convert the list of all edges into a list of start vertices + loops = new ArrayList(); + while (!edges.isEmpty()) { + + // this is an edge belonging to a new loop, store it + Edge edge = edges.get(0); + final Vertex startVertex = edge.getStart(); + loops.add(startVertex); + + // remove all remaining edges in the same loop + do { + + // remove one edge + for (final Iterator iterator = edges.iterator(); iterator.hasNext();) { + if (iterator.next() == edge) { + iterator.remove(); + break; + } + } + + // go to next edge following the boundary loop + edge = edge.getEnd().getOutgoing(); + + } while (edge.getStart() != startVertex); + + } + + } + } + + return Collections.unmodifiableList(loops); + + } + + /** Get a spherical cap enclosing the polygon. + *

    + * This method is intended as a first test to quickly identify points + * that are guaranteed to be outside of the region, hence performing a full + * {@link #checkPoint(Vector) checkPoint} + * only if the point status remains undecided after the quick check. It is + * is therefore mostly useful to speed up computation for small polygons with + * complex shapes (say a country boundary on Earth), as the spherical cap will + * be small and hence will reliably identify a large part of the sphere as outside, + * whereas the full check can be more computing intensive. A typical use case is + * therefore: + *

    + *
    +     *   // compute region, plus an enclosing spherical cap
    +     *   SphericalPolygonsSet complexShape = ...;
    +     *   EnclosingBall cap = complexShape.getEnclosingCap();
    +     *
    +     *   // check lots of points
    +     *   for (Vector3D p : points) {
    +     *
    +     *     final Location l;
    +     *     if (cap.contains(p)) {
    +     *       // we cannot be sure where the point is
    +     *       // we need to perform the full computation
    +     *       l = complexShape.checkPoint(v);
    +     *     } else {
    +     *       // no need to do further computation,
    +     *       // we already know the point is outside
    +     *       l = Location.OUTSIDE;
    +     *     }
    +     *
    +     *     // use l ...
    +     *
    +     *   }
    +     * 
    + *

    + * In the special cases of empty or whole sphere polygons, special + * spherical caps are returned, with angular radius set to negative + * or positive infinity so the {@link + * EnclosingBall#contains(Point) ball.contains(point)} + * method return always false or true. + *

    + *

    + * This method is not guaranteed to return the smallest enclosing cap. + *

    + * @return a spherical cap enclosing the polygon + */ + public EnclosingBall getEnclosingCap() { + + // handle special cases first + if (isEmpty()) { + return new EnclosingBall(S2Point.PLUS_K, Double.NEGATIVE_INFINITY); + } + if (isFull()) { + return new EnclosingBall(S2Point.PLUS_K, Double.POSITIVE_INFINITY); + } + + // as the polygons is neither empty nor full, it has some boundaries and cut hyperplanes + final BSPTree root = getTree(false); + if (isEmpty(root.getMinus()) && isFull(root.getPlus())) { + // the polygon covers an hemisphere, and its boundary is one 2π long edge + final Circle circle = (Circle) root.getCut().getHyperplane(); + return new EnclosingBall(new S2Point(circle.getPole()).negate(), + 0.5 * FastMath.PI); + } + if (isFull(root.getMinus()) && isEmpty(root.getPlus())) { + // the polygon covers an hemisphere, and its boundary is one 2π long edge + final Circle circle = (Circle) root.getCut().getHyperplane(); + return new EnclosingBall(new S2Point(circle.getPole()), + 0.5 * FastMath.PI); + } + + // gather some inside points, to be used by the encloser + final List points = getInsidePoints(); + + // extract points from the boundary loops, to be used by the encloser as well + final List boundary = getBoundaryLoops(); + for (final Vertex loopStart : boundary) { + int count = 0; + for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) { + ++count; + points.add(v.getLocation().getVector()); + } + } + + // find the smallest enclosing 3D sphere + final SphereGenerator generator = new SphereGenerator(); + final WelzlEncloser encloser = + new WelzlEncloser(getTolerance(), generator); + EnclosingBall enclosing3D = encloser.enclose(points); + final Vector3D[] support3D = enclosing3D.getSupport(); + + // convert to 3D sphere to spherical cap + final double r = enclosing3D.getRadius(); + final double h = enclosing3D.getCenter().getNorm(); + if (h < getTolerance()) { + // the 3D sphere is centered on the unit sphere and covers it + // fall back to a crude approximation, based only on outside convex cells + EnclosingBall enclosingS2 = + new EnclosingBall(S2Point.PLUS_K, Double.POSITIVE_INFINITY); + for (Vector3D outsidePoint : getOutsidePoints()) { + final S2Point outsideS2 = new S2Point(outsidePoint); + final BoundaryProjection projection = projectToBoundary(outsideS2); + if (FastMath.PI - projection.getOffset() < enclosingS2.getRadius()) { + enclosingS2 = new EnclosingBall(outsideS2.negate(), + FastMath.PI - projection.getOffset(), + (S2Point) projection.getProjected()); + } + } + return enclosingS2; + } + final S2Point[] support = new S2Point[support3D.length]; + for (int i = 0; i < support3D.length; ++i) { + support[i] = new S2Point(support3D[i]); + } + + final EnclosingBall enclosingS2 = + new EnclosingBall(new S2Point(enclosing3D.getCenter()), + FastMath.acos((1 + h * h - r * r) / (2 * h)), + support); + + return enclosingS2; + + } + + /** Gather some inside points. + * @return list of points known to be strictly in all inside convex cells + */ + private List getInsidePoints() { + final PropertiesComputer pc = new PropertiesComputer(getTolerance()); + getTree(true).visit(pc); + return pc.getConvexCellsInsidePoints(); + } + + /** Gather some outside points. + * @return list of points known to be strictly in all outside convex cells + */ + private List getOutsidePoints() { + final SphericalPolygonsSet complement = + (SphericalPolygonsSet) new RegionFactory().getComplement(this); + final PropertiesComputer pc = new PropertiesComputer(getTolerance()); + complement.getTree(true).visit(pc); + return pc.getConvexCellsInsidePoints(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java new file mode 100644 index 000000000..4cfb57cbf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import com.fr.third.org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Hyperplane; +import com.fr.third.org.apache.commons.math3.geometry.partitioning.Region; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Arc; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.ArcsSet; +import com.fr.third.org.apache.commons.math3.geometry.spherical.oned.Sphere1D; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** This class represents a sub-hyperplane for {@link Circle}. + * @since 3.3 + */ +public class SubCircle extends AbstractSubHyperplane { + + /** Simple constructor. + * @param hyperplane underlying hyperplane + * @param remainingRegion remaining region of the hyperplane + */ + public SubCircle(final Hyperplane hyperplane, + final Region remainingRegion) { + super(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + protected AbstractSubHyperplane buildNew(final Hyperplane hyperplane, + final Region remainingRegion) { + return new SubCircle(hyperplane, remainingRegion); + } + + /** {@inheritDoc} */ + @Override + public SplitSubHyperplane split(final Hyperplane hyperplane) { + + final Circle thisCircle = (Circle) getHyperplane(); + final Circle otherCircle = (Circle) hyperplane; + final double angle = Vector3D.angle(thisCircle.getPole(), otherCircle.getPole()); + + if (angle < thisCircle.getTolerance() || angle > FastMath.PI - thisCircle.getTolerance()) { + // the two circles are aligned or opposite + return new SplitSubHyperplane(null, null); + } else { + // the two circles intersect each other + final Arc arc = thisCircle.getInsideArc(otherCircle); + final ArcsSet.Split split = ((ArcsSet) getRemainingRegion()).split(arc); + final ArcsSet plus = split.getPlus(); + final ArcsSet minus = split.getMinus(); + return new SplitSubHyperplane(plus == null ? null : new SubCircle(thisCircle.copySelf(), plus), + minus == null ? null : new SubCircle(thisCircle.copySelf(), minus)); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Vertex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Vertex.java new file mode 100644 index 000000000..e45e96e68 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/Vertex.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; + +import java.util.ArrayList; +import java.util.List; + +/** Spherical polygons boundary vertex. + * @see SphericalPolygonsSet#getBoundaryLoops() + * @see Edge + * @since 3.3 + */ +public class Vertex { + + /** Vertex location. */ + private final S2Point location; + + /** Incoming edge. */ + private Edge incoming; + + /** Outgoing edge. */ + private Edge outgoing; + + /** Circles bound with this vertex. */ + private final List circles; + + /** Build a non-processed vertex not owned by any node yet. + * @param location vertex location + */ + Vertex(final S2Point location) { + this.location = location; + this.incoming = null; + this.outgoing = null; + this.circles = new ArrayList(); + } + + /** Get Vertex location. + * @return vertex location + */ + public S2Point getLocation() { + return location; + } + + /** Bind a circle considered to contain this vertex. + * @param circle circle to bind with this vertex + */ + void bindWith(final Circle circle) { + circles.add(circle); + } + + /** Get the common circle bound with both the instance and another vertex, if any. + *

    + * When two vertices are both bound to the same circle, this means they are + * already handled by node associated with this circle, so there is no need + * to create a cut hyperplane for them. + *

    + * @param vertex other vertex to check instance against + * @return circle bound with both the instance and another vertex, or null if the + * two vertices do not share a circle yet + */ + Circle sharedCircleWith(final Vertex vertex) { + for (final Circle circle1 : circles) { + for (final Circle circle2 : vertex.circles) { + if (circle1 == circle2) { + return circle1; + } + } + } + return null; + } + + /** Set incoming edge. + *

    + * The circle supporting the incoming edge is automatically bound + * with the instance. + *

    + * @param incoming incoming edge + */ + void setIncoming(final Edge incoming) { + this.incoming = incoming; + bindWith(incoming.getCircle()); + } + + /** Get incoming edge. + * @return incoming edge + */ + public Edge getIncoming() { + return incoming; + } + + /** Set outgoing edge. + *

    + * The circle supporting the outgoing edge is automatically bound + * with the instance. + *

    + * @param outgoing outgoing edge + */ + void setOutgoing(final Edge outgoing) { + this.outgoing = outgoing; + bindWith(outgoing.getCircle()); + } + + /** Get outgoing edge. + * @return outgoing edge + */ + public Edge getOutgoing() { + return outgoing; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/package-info.java new file mode 100644 index 000000000..eac420ec3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/geometry/spherical/twod/package-info.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides basic geometry components on the 2-sphere. + *

    + *

    + * We use here the topologists definition of the 2-sphere (see + * Sphere on + * MathWorld), i.e. the 2-sphere is the two-dimensional surface + * defined in 3D as x2+y2+z2=1. + *

    + * + */ +package com.fr.third.org.apache.commons.math3.geometry.spherical.twod; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractFieldMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractFieldMatrix.java new file mode 100644 index 000000000..fe7938440 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractFieldMatrix.java @@ -0,0 +1,1156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.util.ArrayList; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Basic implementation of {@link FieldMatrix} methods regardless of the underlying storage. + *

    All the methods implemented here use {@link #getEntry(int, int)} to access + * matrix elements. Derived class can provide faster implementations.

    + * + * @param Type of the field elements. + * + * @since 2.0 + */ +public abstract class AbstractFieldMatrix> + implements FieldMatrix { + /** Field to which the elements belong. */ + private final Field 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 field) { + this.field = field; + } + + /** + * Create a new FieldMatrix with the supplied row and column dimensions. + * + * @param field Field to which the elements belong. + * @param rowDimension Number of rows in the new matrix. + * @param columnDimension Number of columns in the new matrix. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + */ + protected AbstractFieldMatrix(final Field field, + final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + if (rowDimension <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, + rowDimension); + } + if (columnDimension <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, + columnDimension); + } + this.field = field; + } + + /** + * Get the elements type from an array. + * + * @param Type of the field elements. + * @param d Data array. + * @return the field to which the array elements belong. + * @throws NullArgumentException if the array is {@code null}. + * @throws NoDataException if the array is empty. + */ + protected static > Field extractField(final T[][] d) + throws NoDataException, NullArgumentException { + if (d == null) { + throw new NullArgumentException(); + } + if (d.length == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + if (d[0].length == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + return d[0][0].getField(); + } + + /** + * Get the elements type from an array. + * + * @param Type of the field elements. + * @param d Data array. + * @return the field to which the array elements belong. + * @throws NoDataException if array is empty. + */ + protected static > Field extractField(final T[] d) + throws NoDataException { + if (d.length == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + return d[0].getField(); + } + + /** Build an array of elements. + *

    + * Complete arrays are filled with field.getZero() + *

    + * @param 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 new Field[rows][] works) + * @return a new array + * @deprecated as of 3.2, replaced by {@link MathArrays#buildArray(Field, int, int)} + */ + @Deprecated + protected static > T[][] buildArray(final Field field, + final int rows, + final int columns) { + return MathArrays.buildArray(field, rows, columns); + } + + /** Build an array of elements. + *

    + * Arrays are filled with field.getZero() + *

    + * @param the type of the field elements + * @param field field to which array elements belong + * @param length of the array + * @return a new array + * @deprecated as of 3.2, replaced by {@link MathArrays#buildArray(Field, int)} + */ + @Deprecated + protected static > T[] buildArray(final Field field, + final int length) { + return MathArrays.buildArray(field, length); + } + + /** {@inheritDoc} */ + public Field getField() { + return field; + } + + /** {@inheritDoc} */ + public abstract FieldMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException; + + /** {@inheritDoc} */ + public abstract FieldMatrix copy(); + + /** {@inheritDoc} */ + public FieldMatrix add(FieldMatrix m) + throws MatrixDimensionMismatchException { + // safety check + checkAdditionCompatible(m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final FieldMatrix 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 subtract(final FieldMatrix m) + throws MatrixDimensionMismatchException { + // safety check + checkSubtractionCompatible(m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final FieldMatrix 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 scalarAdd(final T d) { + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final FieldMatrix 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 scalarMultiply(final T d) { + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final FieldMatrix 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 multiply(final FieldMatrix m) + throws DimensionMismatchException { + // safety check + checkMultiplicationCompatible(m); + + final int nRows = getRowDimension(); + final int nCols = m.getColumnDimension(); + final int nSum = getColumnDimension(); + final FieldMatrix 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 preMultiply(final FieldMatrix m) + throws DimensionMismatchException { + return m.multiply(this); + } + + /** {@inheritDoc} */ + public FieldMatrix power(final int p) throws NonSquareMatrixException, + NotPositiveException { + if (p < 0) { + throw new NotPositiveException(p); + } + + if (!isSquare()) { + throw new NonSquareMatrixException(getRowDimension(), getColumnDimension()); + } + + if (p == 0) { + return MatrixUtils.createFieldIdentityMatrix(this.getField(), this.getRowDimension()); + } + + if (p == 1) { + return this.copy(); + } + + final int power = p - 1; + + /* + * Only log_2(p) operations is used by doing as follows: + * 5^214 = 5^128 * 5^64 * 5^16 * 5^4 * 5^2 + * + * In general, the same approach is used for A^p. + */ + + final char[] binaryRepresentation = Integer.toBinaryString(power) + .toCharArray(); + final ArrayList nonZeroPositions = new ArrayList(); + + for (int i = 0; i < binaryRepresentation.length; ++i) { + if (binaryRepresentation[i] == '1') { + final int pos = binaryRepresentation.length - i - 1; + nonZeroPositions.add(pos); + } + } + + ArrayList> results = new ArrayList>( + binaryRepresentation.length); + + results.add(0, this.copy()); + + for (int i = 1; i < binaryRepresentation.length; ++i) { + final FieldMatrix s = results.get(i - 1); + final FieldMatrix r = s.multiply(s); + results.add(i, r); + } + + FieldMatrix result = this.copy(); + + for (Integer i : nonZeroPositions) { + result = result.multiply(results.get(i)); + } + + return result; + } + + /** {@inheritDoc} */ + public T[][] getData() { + final T[][] data = MathArrays.buildArray(field, getRowDimension(), getColumnDimension()); + + for (int i = 0; i < data.length; ++i) { + final T[] dataI = data[i]; + for (int j = 0; j < dataI.length; ++j) { + dataI[j] = getEntry(i, j); + } + } + + return data; + } + + /** {@inheritDoc} */ + public FieldMatrix getSubMatrix(final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + + final FieldMatrix 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 getSubMatrix(final int[] selectedRows, + final int[] selectedColumns) + throws NoDataException, NullArgumentException, OutOfRangeException { + + // safety checks + checkSubMatrixIndex(selectedRows, selectedColumns); + + // copy entries + final FieldMatrix subMatrix = + createMatrix(selectedRows.length, selectedColumns.length); + subMatrix.walkInOptimizedOrder(new DefaultFieldMatrixChangingVisitor(field.getZero()) { + + /** {@inheritDoc} */ + @Override + public T visit(final int row, final int column, final T value) { + return getEntry(selectedRows[row], selectedColumns[column]); + } + + }); + + return subMatrix; + + } + + /** {@inheritDoc} */ + public void copySubMatrix(final int startRow, final int endRow, + final int startColumn, final int endColumn, + final T[][] destination) + throws MatrixDimensionMismatchException, NumberIsTooSmallException, + OutOfRangeException{ + // safety checks + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + final int rowsCount = endRow + 1 - startRow; + final int columnsCount = endColumn + 1 - startColumn; + if ((destination.length < rowsCount) || (destination[0].length < columnsCount)) { + throw new MatrixDimensionMismatchException(destination.length, + destination[0].length, + rowsCount, + columnsCount); + } + + // copy entries + walkInOptimizedOrder(new DefaultFieldMatrixPreservingVisitor(field.getZero()) { + + /** Initial row index. */ + private int startRow; + + /** Initial column index. */ + private int startColumn; + + /** {@inheritDoc} */ + @Override + public void start(final int rows, final int columns, + final int startRow, final int endRow, + final int startColumn, final int endColumn) { + this.startRow = startRow; + this.startColumn = startColumn; + } + + /** {@inheritDoc} */ + @Override + public void visit(final int row, final int column, final T value) { + destination[row - startRow][column - startColumn] = value; + } + + }, startRow, endRow, startColumn, endColumn); + + } + + /** {@inheritDoc} */ + public void copySubMatrix(int[] selectedRows, int[] selectedColumns, T[][] destination) + throws MatrixDimensionMismatchException, NoDataException, + NullArgumentException, OutOfRangeException { + // safety checks + checkSubMatrixIndex(selectedRows, selectedColumns); + if ((destination.length < selectedRows.length) || + (destination[0].length < selectedColumns.length)) { + throw new MatrixDimensionMismatchException(destination.length, + destination[0].length, + selectedRows.length, + selectedColumns.length); + } + + // copy entries + for (int i = 0; i < selectedRows.length; i++) { + final T[] destinationI = destination[i]; + for (int j = 0; j < selectedColumns.length; j++) { + destinationI[j] = getEntry(selectedRows[i], selectedColumns[j]); + } + } + + } + + /** {@inheritDoc} */ + public void setSubMatrix(final T[][] subMatrix, final int row, + final int column) + throws DimensionMismatchException, OutOfRangeException, + NoDataException, NullArgumentException { + if (subMatrix == null) { + throw new NullArgumentException(); + } + final int nRows = subMatrix.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + + final int nCols = subMatrix[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + + for (int r = 1; r < nRows; ++r) { + if (subMatrix[r].length != nCols) { + throw new DimensionMismatchException(nCols, subMatrix[r].length); + } + } + + checkRowIndex(row); + checkColumnIndex(column); + checkRowIndex(nRows + row - 1); + checkColumnIndex(nCols + column - 1); + + for (int i = 0; i < nRows; ++i) { + for (int j = 0; j < nCols; ++j) { + setEntry(row + i, column + j, subMatrix[i][j]); + } + } + } + + /** {@inheritDoc} */ + public FieldMatrix getRowMatrix(final int row) throws OutOfRangeException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + final FieldMatrix 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 matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + if ((matrix.getRowDimension() != 1) || + (matrix.getColumnDimension() != nCols)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, matrix.getEntry(0, i)); + } + + } + + /** {@inheritDoc} */ + public FieldMatrix getColumnMatrix(final int column) + throws OutOfRangeException { + + checkColumnIndex(column); + final int nRows = getRowDimension(); + final FieldMatrix 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 matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkColumnIndex(column); + final int nRows = getRowDimension(); + if ((matrix.getRowDimension() != nRows) || + (matrix.getColumnDimension() != 1)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, matrix.getEntry(i, 0)); + } + + } + + /** {@inheritDoc} */ + public FieldVector getRowVector(final int row) + throws OutOfRangeException { + return new ArrayFieldVector(field, getRow(row), false); + } + + /** {@inheritDoc} */ + public void setRowVector(final int row, final FieldVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + if (vector.getDimension() != nCols) { + throw new MatrixDimensionMismatchException(1, vector.getDimension(), + 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, vector.getEntry(i)); + } + + } + + /** {@inheritDoc} */ + public FieldVector getColumnVector(final int column) + throws OutOfRangeException { + return new ArrayFieldVector(field, getColumn(column), false); + } + + /** {@inheritDoc} */ + public void setColumnVector(final int column, final FieldVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + + checkColumnIndex(column); + final int nRows = getRowDimension(); + if (vector.getDimension() != nRows) { + throw new MatrixDimensionMismatchException(vector.getDimension(), 1, + nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, vector.getEntry(i)); + } + + } + + /** {@inheritDoc} */ + public T[] getRow(final int row) throws OutOfRangeException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + final T[] out = MathArrays.buildArray(field, nCols); + for (int i = 0; i < nCols; ++i) { + out[i] = getEntry(row, i); + } + + return out; + + } + + /** {@inheritDoc} */ + public void setRow(final int row, final T[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + if (array.length != nCols) { + throw new MatrixDimensionMismatchException(1, array.length, 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, array[i]); + } + + } + + /** {@inheritDoc} */ + public T[] getColumn(final int column) throws OutOfRangeException { + checkColumnIndex(column); + final int nRows = getRowDimension(); + final T[] out = MathArrays.buildArray(field, nRows); + for (int i = 0; i < nRows; ++i) { + out[i] = getEntry(i, column); + } + + return out; + + } + + /** {@inheritDoc} */ + public void setColumn(final int column, final T[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkColumnIndex(column); + final int nRows = getRowDimension(); + if (array.length != nRows) { + throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, array[i]); + } + } + + /** {@inheritDoc} */ + public abstract T getEntry(int row, int column) throws OutOfRangeException; + + /** {@inheritDoc} */ + public abstract void setEntry(int row, int column, T value) throws OutOfRangeException; + + /** {@inheritDoc} */ + public abstract void addToEntry(int row, int column, T increment) throws OutOfRangeException; + + /** {@inheritDoc} */ + public abstract void multiplyEntry(int row, int column, T factor) throws OutOfRangeException; + + /** {@inheritDoc} */ + public FieldMatrix transpose() { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + final FieldMatrix out = createMatrix(nCols, nRows); + walkInOptimizedOrder(new DefaultFieldMatrixPreservingVisitor(field.getZero()) { + /** {@inheritDoc} */ + @Override + public void visit(final int row, final int column, final T value) { + out.setEntry(column, row, value); + } + }); + + return out; + } + + /** {@inheritDoc} */ + public boolean isSquare() { + return getColumnDimension() == getRowDimension(); + } + + /** {@inheritDoc} */ + public abstract int getRowDimension(); + + /** {@inheritDoc} */ + public abstract int getColumnDimension(); + + /** {@inheritDoc} */ + public T getTrace() throws NonSquareMatrixException { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (nRows != nCols) { + throw new NonSquareMatrixException(nRows, nCols); + } + T trace = field.getZero(); + for (int i = 0; i < nRows; ++i) { + trace = trace.add(getEntry(i, i)); + } + return trace; + } + + /** {@inheritDoc} */ + public T[] operate(final T[] v) throws DimensionMismatchException { + + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nCols) { + throw new DimensionMismatchException(v.length, nCols); + } + + final T[] out = MathArrays.buildArray(field, nRows); + for (int row = 0; row < nRows; ++row) { + T sum = field.getZero(); + for (int i = 0; i < nCols; ++i) { + sum = sum.add(getEntry(row, i).multiply(v[i])); + } + out[row] = sum; + } + + return out; + } + + /** {@inheritDoc} */ + public FieldVector operate(final FieldVector v) + throws DimensionMismatchException { + try { + return new ArrayFieldVector(field, operate(((ArrayFieldVector) v).getDataRef()), false); + } catch (ClassCastException cce) { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.getDimension() != nCols) { + throw new DimensionMismatchException(v.getDimension(), nCols); + } + + final T[] out = MathArrays.buildArray(field, nRows); + for (int row = 0; row < nRows; ++row) { + T sum = field.getZero(); + for (int i = 0; i < nCols; ++i) { + sum = sum.add(getEntry(row, i).multiply(v.getEntry(i))); + } + out[row] = sum; + } + + return new ArrayFieldVector(field, out, false); + } + } + + /** {@inheritDoc} */ + public T[] preMultiply(final T[] v) throws DimensionMismatchException { + + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nRows) { + throw new DimensionMismatchException(v.length, nRows); + } + + final T[] out = MathArrays.buildArray(field, nCols); + for (int col = 0; col < nCols; ++col) { + T sum = field.getZero(); + for (int i = 0; i < nRows; ++i) { + sum = sum.add(getEntry(i, col).multiply(v[i])); + } + out[col] = sum; + } + + return out; + } + + /** {@inheritDoc} */ + public FieldVector preMultiply(final FieldVector v) + throws DimensionMismatchException { + try { + return new ArrayFieldVector(field, preMultiply(((ArrayFieldVector) v).getDataRef()), false); + } catch (ClassCastException cce) { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.getDimension() != nRows) { + throw new DimensionMismatchException(v.getDimension(), nRows); + } + + final T[] out = MathArrays.buildArray(field, nCols); + for (int col = 0; col < nCols; ++col) { + T sum = field.getZero(); + for (int i = 0; i < nRows; ++i) { + sum = sum.add(getEntry(i, col).multiply(v.getEntry(i))); + } + out[col] = sum; + } + + return new ArrayFieldVector(field, out, false); + } + } + + /** {@inheritDoc} */ + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int row = 0; row < rows; ++row) { + for (int column = 0; column < columns; ++column) { + final T oldValue = getEntry(row, column); + final T newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int row = 0; row < rows; ++row) { + for (int column = 0; column < columns; ++column) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int row = startRow; row <= endRow; ++row) { + for (int column = startColumn; column <= endColumn; ++column) { + final T oldValue = getEntry(row, column); + final T newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int row = startRow; row <= endRow; ++row) { + for (int column = startColumn; column <= endColumn; ++column) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInColumnOrder(final FieldMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int column = 0; column < columns; ++column) { + for (int row = 0; row < rows; ++row) { + final T oldValue = getEntry(row, column); + final T newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInColumnOrder(final FieldMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int column = 0; column < columns; ++column) { + for (int row = 0; row < rows; ++row) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInColumnOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int column = startColumn; column <= endColumn; ++column) { + for (int row = startRow; row <= endRow; ++row) { + final T oldValue = getEntry(row, column); + final T newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInColumnOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException{ + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int column = startColumn; column <= endColumn; ++column) { + for (int row = startRow; row <= endRow; ++row) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public T walkInOptimizedOrder(final FieldMatrixChangingVisitor visitor) { + return walkInRowOrder(visitor); + } + + /** {@inheritDoc} */ + public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor visitor) { + return walkInRowOrder(visitor); + } + + /** {@inheritDoc} */ + public T walkInOptimizedOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn); + } + + /** {@inheritDoc} */ + public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn); + } + + /** + * Get a string representation for this matrix. + * @return a string representation for this matrix + */ + @Override + public String toString() { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + final StringBuffer res = new StringBuffer(); + String fullClassName = getClass().getName(); + String shortClassName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1); + res.append(shortClassName).append("{"); + + for (int i = 0; i < nRows; ++i) { + if (i > 0) { + res.append(","); + } + res.append("{"); + for (int j = 0; j < nCols; ++j) { + if (j > 0) { + res.append(","); + } + res.append(getEntry(i, j)); + } + res.append("}"); + } + + res.append("}"); + return res.toString(); + } + + /** + * Returns true iff object is a + * FieldMatrix instance with the same dimensions as this + * and all corresponding matrix entries are equal. + * + * @param object the object to test equality against. + * @return true if object equals this + */ + @Override + public boolean equals(final Object object) { + if (object == this ) { + return true; + } + if (object instanceof FieldMatrix == false) { + return false; + } + FieldMatrix m = (FieldMatrix) object; + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (m.getColumnDimension() != nCols || m.getRowDimension() != nRows) { + return false; + } + for (int row = 0; row < nRows; ++row) { + for (int col = 0; col < nCols; ++col) { + if (!getEntry(row, col).equals(m.getEntry(row, col))) { + return false; + } + } + } + return true; + } + + /** + * Computes a hashcode for the matrix. + * + * @return hashcode for matrix + */ + @Override + public int hashCode() { + int ret = 322562; + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + ret = ret * 31 + nRows; + ret = ret * 31 + nCols; + for (int row = 0; row < nRows; ++row) { + for (int col = 0; col < nCols; ++col) { + ret = ret * 31 + (11 * (row+1) + 17 * (col+1)) * getEntry(row, col).hashCode(); + } + } + return ret; + } + + /** + * Check if a row index is valid. + * + * @param row Row index to check. + * @throws OutOfRangeException if {@code index} is not valid. + */ + protected void checkRowIndex(final int row) throws OutOfRangeException { + if (row < 0 || row >= getRowDimension()) { + throw new OutOfRangeException(LocalizedFormats.ROW_INDEX, + row, 0, getRowDimension() - 1); + } + } + + /** + * Check if a column index is valid. + * + * @param column Column index to check. + * @throws OutOfRangeException if {@code index} is not valid. + */ + protected void checkColumnIndex(final int column) + throws OutOfRangeException { + if (column < 0 || column >= getColumnDimension()) { + throw new OutOfRangeException(LocalizedFormats.COLUMN_INDEX, + column, 0, getColumnDimension() - 1); + } + } + + /** + * Check if submatrix ranges indices are valid. + * Rows and columns are indicated counting from 0 to n-1. + * + * @param startRow Initial row index. + * @param endRow Final row index. + * @param startColumn Initial column index. + * @param endColumn Final column index. + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + */ + protected void checkSubMatrixIndex(final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkRowIndex(startRow); + checkRowIndex(endRow); + if (endRow < startRow) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, + endRow, startRow, true); + } + + checkColumnIndex(startColumn); + checkColumnIndex(endColumn); + if (endColumn < startColumn) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN, + endColumn, startColumn, true); + } + } + + /** + * Check if submatrix ranges indices are valid. + * Rows and columns are indicated counting from 0 to n-1. + * + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @throws NullArgumentException if the arrays are {@code null}. + * @throws NoDataException if the arrays have zero length. + * @throws OutOfRangeException if row or column selections are not valid. + */ + protected void checkSubMatrixIndex(final int[] selectedRows, final int[] selectedColumns) + throws NoDataException, NullArgumentException, OutOfRangeException { + if (selectedRows == null || + selectedColumns == null) { + throw new NullArgumentException(); + } + if (selectedRows.length == 0 || + selectedColumns.length == 0) { + throw new NoDataException(); + } + + for (final int row : selectedRows) { + checkRowIndex(row); + } + for (final int column : selectedColumns) { + checkColumnIndex(column); + } + } + + /** + * Check if a matrix is addition compatible with the instance. + * + * @param m Matrix to check. + * @throws MatrixDimensionMismatchException if the matrix is not + * addition-compatible with instance. + */ + protected void checkAdditionCompatible(final FieldMatrix m) + throws MatrixDimensionMismatchException { + if ((getRowDimension() != m.getRowDimension()) || + (getColumnDimension() != m.getColumnDimension())) { + throw new MatrixDimensionMismatchException(m.getRowDimension(), m.getColumnDimension(), + getRowDimension(), getColumnDimension()); + } + } + + /** + * Check if a matrix is subtraction compatible with the instance. + * + * @param m Matrix to check. + * @throws MatrixDimensionMismatchException if the matrix is not + * subtraction-compatible with instance. + */ + protected void checkSubtractionCompatible(final FieldMatrix m) + throws MatrixDimensionMismatchException { + if ((getRowDimension() != m.getRowDimension()) || + (getColumnDimension() != m.getColumnDimension())) { + throw new MatrixDimensionMismatchException(m.getRowDimension(), m.getColumnDimension(), + getRowDimension(), getColumnDimension()); + } + } + + /** + * Check if a matrix is multiplication compatible with the instance. + * + * @param m Matrix to check. + * @throws DimensionMismatchException if the matrix is not + * multiplication-compatible with instance. + */ + protected void checkMultiplicationCompatible(final FieldMatrix m) + throws DimensionMismatchException { + if (getColumnDimension() != m.getRowDimension()) { + throw new DimensionMismatchException(m.getRowDimension(), getColumnDimension()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractRealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractRealMatrix.java new file mode 100644 index 000000000..140a7e13d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AbstractRealMatrix.java @@ -0,0 +1,992 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.util.ArrayList; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Basic implementation of RealMatrix methods regardless of the underlying storage. + *

    All the methods implemented here use {@link #getEntry(int, int)} to access + * matrix elements. Derived class can provide faster implementations.

    + * + * @since 2.0 + */ +public abstract class AbstractRealMatrix + extends RealLinearOperator + implements RealMatrix { + + /** Default format. */ + private static final RealMatrixFormat DEFAULT_FORMAT = RealMatrixFormat.getInstance(Locale.US); + static { + // set the minimum fraction digits to 1 to keep compatibility + DEFAULT_FORMAT.getFormat().setMinimumFractionDigits(1); + } + + /** + * Creates a matrix with no data + */ + protected AbstractRealMatrix() {} + + /** + * Create a new RealMatrix with the supplied row and column dimensions. + * + * @param rowDimension the number of rows in the new matrix + * @param columnDimension the number of columns in the new matrix + * @throws NotStrictlyPositiveException if row or column dimension is not positive + */ + protected AbstractRealMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + if (rowDimension < 1) { + throw new NotStrictlyPositiveException(rowDimension); + } + if (columnDimension < 1) { + throw new NotStrictlyPositiveException(columnDimension); + } + } + + /** {@inheritDoc} */ + public RealMatrix add(RealMatrix m) + throws MatrixDimensionMismatchException { + MatrixUtils.checkAdditionCompatible(this, m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final RealMatrix out = createMatrix(rowCount, columnCount); + for (int row = 0; row < rowCount; ++row) { + for (int col = 0; col < columnCount; ++col) { + out.setEntry(row, col, getEntry(row, col) + m.getEntry(row, col)); + } + } + + return out; + } + + /** {@inheritDoc} */ + public RealMatrix subtract(final RealMatrix m) + throws MatrixDimensionMismatchException { + MatrixUtils.checkSubtractionCompatible(this, m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final RealMatrix out = createMatrix(rowCount, columnCount); + for (int row = 0; row < rowCount; ++row) { + for (int col = 0; col < columnCount; ++col) { + out.setEntry(row, col, getEntry(row, col) - m.getEntry(row, col)); + } + } + + return out; + } + + /** {@inheritDoc} */ + public RealMatrix scalarAdd(final double d) { + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final RealMatrix out = createMatrix(rowCount, columnCount); + for (int row = 0; row < rowCount; ++row) { + for (int col = 0; col < columnCount; ++col) { + out.setEntry(row, col, getEntry(row, col) + d); + } + } + + return out; + } + + /** {@inheritDoc} */ + public RealMatrix scalarMultiply(final double d) { + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final RealMatrix out = createMatrix(rowCount, columnCount); + for (int row = 0; row < rowCount; ++row) { + for (int col = 0; col < columnCount; ++col) { + out.setEntry(row, col, getEntry(row, col) * d); + } + } + + return out; + } + + /** {@inheritDoc} */ + public RealMatrix multiply(final RealMatrix m) + throws DimensionMismatchException { + MatrixUtils.checkMultiplicationCompatible(this, m); + + final int nRows = getRowDimension(); + final int nCols = m.getColumnDimension(); + final int nSum = getColumnDimension(); + final RealMatrix out = createMatrix(nRows, nCols); + for (int row = 0; row < nRows; ++row) { + for (int col = 0; col < nCols; ++col) { + double sum = 0; + for (int i = 0; i < nSum; ++i) { + sum += getEntry(row, i) * m.getEntry(i, col); + } + out.setEntry(row, col, sum); + } + } + + return out; + } + + /** {@inheritDoc} */ + public RealMatrix preMultiply(final RealMatrix m) + throws DimensionMismatchException { + return m.multiply(this); + } + + /** {@inheritDoc} */ + public RealMatrix power(final int p) + throws NotPositiveException, NonSquareMatrixException { + if (p < 0) { + throw new NotPositiveException(LocalizedFormats.NOT_POSITIVE_EXPONENT, p); + } + + if (!isSquare()) { + throw new NonSquareMatrixException(getRowDimension(), getColumnDimension()); + } + + if (p == 0) { + return MatrixUtils.createRealIdentityMatrix(this.getRowDimension()); + } + + if (p == 1) { + return this.copy(); + } + + final int power = p - 1; + + /* + * Only log_2(p) operations is used by doing as follows: + * 5^214 = 5^128 * 5^64 * 5^16 * 5^4 * 5^2 + * + * In general, the same approach is used for A^p. + */ + + final char[] binaryRepresentation = Integer.toBinaryString(power).toCharArray(); + final ArrayList nonZeroPositions = new ArrayList(); + int maxI = -1; + + for (int i = 0; i < binaryRepresentation.length; ++i) { + if (binaryRepresentation[i] == '1') { + final int pos = binaryRepresentation.length - i - 1; + nonZeroPositions.add(pos); + + // The positions are taken in turn, so maxI is only changed once + if (maxI == -1) { + maxI = pos; + } + } + } + + RealMatrix[] results = new RealMatrix[maxI + 1]; + results[0] = this.copy(); + + for (int i = 1; i <= maxI; ++i) { + results[i] = results[i-1].multiply(results[i-1]); + } + + RealMatrix result = this.copy(); + + for (Integer i : nonZeroPositions) { + result = result.multiply(results[i]); + } + + return result; + } + + /** {@inheritDoc} */ + public double[][] getData() { + final double[][] data = new double[getRowDimension()][getColumnDimension()]; + + for (int i = 0; i < data.length; ++i) { + final double[] dataI = data[i]; + for (int j = 0; j < dataI.length; ++j) { + dataI[j] = getEntry(i, j); + } + } + + return data; + } + + /** {@inheritDoc} */ + public double getNorm() { + return walkInColumnOrder(new RealMatrixPreservingVisitor() { + + /** Last row index. */ + private double endRow; + + /** Sum of absolute values on one column. */ + private double columnSum; + + /** Maximal sum across all columns. */ + private double maxColSum; + + /** {@inheritDoc} */ + public void start(final int rows, final int columns, + final int startRow, final int endRow, + final int startColumn, final int endColumn) { + this.endRow = endRow; + columnSum = 0; + maxColSum = 0; + } + + /** {@inheritDoc} */ + public void visit(final int row, final int column, final double value) { + columnSum += FastMath.abs(value); + if (row == endRow) { + maxColSum = FastMath.max(maxColSum, columnSum); + columnSum = 0; + } + } + + /** {@inheritDoc} */ + public double end() { + return maxColSum; + } + }); + } + + /** {@inheritDoc} */ + public double getFrobeniusNorm() { + return walkInOptimizedOrder(new RealMatrixPreservingVisitor() { + + /** Sum of squared entries. */ + private double sum; + + /** {@inheritDoc} */ + public void start(final int rows, final int columns, + final int startRow, final int endRow, + final int startColumn, final int endColumn) { + sum = 0; + } + + /** {@inheritDoc} */ + public void visit(final int row, final int column, final double value) { + sum += value * value; + } + + /** {@inheritDoc} */ + public double end() { + return FastMath.sqrt(sum); + } + }); + } + + /** {@inheritDoc} */ + public RealMatrix getSubMatrix(final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + + final RealMatrix subMatrix = + createMatrix(endRow - startRow + 1, endColumn - startColumn + 1); + for (int i = startRow; i <= endRow; ++i) { + for (int j = startColumn; j <= endColumn; ++j) { + subMatrix.setEntry(i - startRow, j - startColumn, getEntry(i, j)); + } + } + + return subMatrix; + } + + /** {@inheritDoc} */ + public RealMatrix getSubMatrix(final int[] selectedRows, + final int[] selectedColumns) + throws NullArgumentException, NoDataException, OutOfRangeException { + MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns); + + final RealMatrix subMatrix = + createMatrix(selectedRows.length, selectedColumns.length); + subMatrix.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() { + + /** {@inheritDoc} */ + @Override + public double visit(final int row, final int column, final double value) { + return getEntry(selectedRows[row], selectedColumns[column]); + } + + }); + + return subMatrix; + } + + /** {@inheritDoc} */ + public void copySubMatrix(final int startRow, final int endRow, + final int startColumn, final int endColumn, + final double[][] destination) + throws OutOfRangeException, NumberIsTooSmallException, + MatrixDimensionMismatchException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + final int rowsCount = endRow + 1 - startRow; + final int columnsCount = endColumn + 1 - startColumn; + if ((destination.length < rowsCount) || (destination[0].length < columnsCount)) { + throw new MatrixDimensionMismatchException(destination.length, destination[0].length, + rowsCount, columnsCount); + } + + for (int i = 1; i < rowsCount; i++) { + if (destination[i].length < columnsCount) { + throw new MatrixDimensionMismatchException(destination.length, destination[i].length, + rowsCount, columnsCount); + } + } + + walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() { + + /** Initial row index. */ + private int startRow; + + /** Initial column index. */ + private int startColumn; + + /** {@inheritDoc} */ + @Override + public void start(final int rows, final int columns, + final int startRow, final int endRow, + final int startColumn, final int endColumn) { + this.startRow = startRow; + this.startColumn = startColumn; + } + + /** {@inheritDoc} */ + @Override + public void visit(final int row, final int column, final double value) { + destination[row - startRow][column - startColumn] = value; + } + + }, startRow, endRow, startColumn, endColumn); + } + + /** {@inheritDoc} */ + public void copySubMatrix(int[] selectedRows, int[] selectedColumns, + double[][] destination) + throws OutOfRangeException, NullArgumentException, NoDataException, + MatrixDimensionMismatchException { + MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns); + final int nCols = selectedColumns.length; + if ((destination.length < selectedRows.length) || + (destination[0].length < nCols)) { + throw new MatrixDimensionMismatchException(destination.length, destination[0].length, + selectedRows.length, selectedColumns.length); + } + + for (int i = 0; i < selectedRows.length; i++) { + final double[] destinationI = destination[i]; + if (destinationI.length < nCols) { + throw new MatrixDimensionMismatchException(destination.length, destinationI.length, + selectedRows.length, selectedColumns.length); + } + for (int j = 0; j < selectedColumns.length; j++) { + destinationI[j] = getEntry(selectedRows[i], selectedColumns[j]); + } + } + } + + /** {@inheritDoc} */ + public void setSubMatrix(final double[][] subMatrix, final int row, final int column) + throws NoDataException, OutOfRangeException, + DimensionMismatchException, NullArgumentException { + MathUtils.checkNotNull(subMatrix); + final int nRows = subMatrix.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + + final int nCols = subMatrix[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + + for (int r = 1; r < nRows; ++r) { + if (subMatrix[r].length != nCols) { + throw new DimensionMismatchException(nCols, subMatrix[r].length); + } + } + + MatrixUtils.checkRowIndex(this, row); + MatrixUtils.checkColumnIndex(this, column); + MatrixUtils.checkRowIndex(this, nRows + row - 1); + MatrixUtils.checkColumnIndex(this, nCols + column - 1); + + for (int i = 0; i < nRows; ++i) { + for (int j = 0; j < nCols; ++j) { + setEntry(row + i, column + j, subMatrix[i][j]); + } + } + } + + /** {@inheritDoc} */ + public RealMatrix getRowMatrix(final int row) throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + final RealMatrix out = createMatrix(1, nCols); + for (int i = 0; i < nCols; ++i) { + out.setEntry(0, i, getEntry(row, i)); + } + + return out; + } + + /** {@inheritDoc} */ + public void setRowMatrix(final int row, final RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + if ((matrix.getRowDimension() != 1) || + (matrix.getColumnDimension() != nCols)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, matrix.getEntry(0, i)); + } + } + + /** {@inheritDoc} */ + public RealMatrix getColumnMatrix(final int column) + throws OutOfRangeException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + final RealMatrix out = createMatrix(nRows, 1); + for (int i = 0; i < nRows; ++i) { + out.setEntry(i, 0, getEntry(i, column)); + } + + return out; + } + + /** {@inheritDoc} */ + public void setColumnMatrix(final int column, final RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + if ((matrix.getRowDimension() != nRows) || + (matrix.getColumnDimension() != 1)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, matrix.getEntry(i, 0)); + } + } + + /** {@inheritDoc} */ + public RealVector getRowVector(final int row) + throws OutOfRangeException { + return new ArrayRealVector(getRow(row), false); + } + + /** {@inheritDoc} */ + public void setRowVector(final int row, final RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + if (vector.getDimension() != nCols) { + throw new MatrixDimensionMismatchException(1, vector.getDimension(), + 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, vector.getEntry(i)); + } + } + + /** {@inheritDoc} */ + public RealVector getColumnVector(final int column) + throws OutOfRangeException { + return new ArrayRealVector(getColumn(column), false); + } + + /** {@inheritDoc} */ + public void setColumnVector(final int column, final RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + if (vector.getDimension() != nRows) { + throw new MatrixDimensionMismatchException(vector.getDimension(), 1, + nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, vector.getEntry(i)); + } + } + + /** {@inheritDoc} */ + public double[] getRow(final int row) throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + final double[] out = new double[nCols]; + for (int i = 0; i < nCols; ++i) { + out[i] = getEntry(row, i); + } + + return out; + } + + /** {@inheritDoc} */ + public void setRow(final int row, final double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + if (array.length != nCols) { + throw new MatrixDimensionMismatchException(1, array.length, 1, nCols); + } + for (int i = 0; i < nCols; ++i) { + setEntry(row, i, array[i]); + } + } + + /** {@inheritDoc} */ + public double[] getColumn(final int column) throws OutOfRangeException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + final double[] out = new double[nRows]; + for (int i = 0; i < nRows; ++i) { + out[i] = getEntry(i, column); + } + + return out; + } + + /** {@inheritDoc} */ + public void setColumn(final int column, final double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + if (array.length != nRows) { + throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1); + } + for (int i = 0; i < nRows; ++i) { + setEntry(i, column, array[i]); + } + } + + /** {@inheritDoc} */ + public void addToEntry(int row, int column, double increment) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + setEntry(row, column, getEntry(row, column) + increment); + } + + /** {@inheritDoc} */ + public void multiplyEntry(int row, int column, double factor) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + setEntry(row, column, getEntry(row, column) * factor); + } + + /** {@inheritDoc} */ + public RealMatrix transpose() { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + final RealMatrix out = createMatrix(nCols, nRows); + walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() { + + /** {@inheritDoc} */ + @Override + public void visit(final int row, final int column, final double value) { + out.setEntry(column, row, value); + } + + }); + + return out; + } + + /** {@inheritDoc} */ + public boolean isSquare() { + return getColumnDimension() == getRowDimension(); + } + + /** + * Returns the number of rows of this matrix. + * + * @return the number of rows. + */ + @Override + public abstract int getRowDimension(); + + /** + * Returns the number of columns of this matrix. + * + * @return the number of columns. + */ + @Override + public abstract int getColumnDimension(); + + /** {@inheritDoc} */ + public double getTrace() throws NonSquareMatrixException { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (nRows != nCols) { + throw new NonSquareMatrixException(nRows, nCols); + } + double trace = 0; + for (int i = 0; i < nRows; ++i) { + trace += getEntry(i, i); + } + return trace; + } + + /** {@inheritDoc} */ + public double[] operate(final double[] v) + throws DimensionMismatchException { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nCols) { + throw new DimensionMismatchException(v.length, nCols); + } + + final double[] out = new double[nRows]; + for (int row = 0; row < nRows; ++row) { + double sum = 0; + for (int i = 0; i < nCols; ++i) { + sum += getEntry(row, i) * v[i]; + } + out[row] = sum; + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public RealVector operate(final RealVector v) + throws DimensionMismatchException { + try { + return new ArrayRealVector(operate(((ArrayRealVector) v).getDataRef()), false); + } catch (ClassCastException cce) { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.getDimension() != nCols) { + throw new DimensionMismatchException(v.getDimension(), nCols); + } + + final double[] out = new double[nRows]; + for (int row = 0; row < nRows; ++row) { + double sum = 0; + for (int i = 0; i < nCols; ++i) { + sum += getEntry(row, i) * v.getEntry(i); + } + out[row] = sum; + } + + return new ArrayRealVector(out, false); + } + } + + /** {@inheritDoc} */ + public double[] preMultiply(final double[] v) throws DimensionMismatchException { + + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nRows) { + throw new DimensionMismatchException(v.length, nRows); + } + + final double[] out = new double[nCols]; + for (int col = 0; col < nCols; ++col) { + double sum = 0; + for (int i = 0; i < nRows; ++i) { + sum += getEntry(i, col) * v[i]; + } + out[col] = sum; + } + + return out; + } + + /** {@inheritDoc} */ + public RealVector preMultiply(final RealVector v) throws DimensionMismatchException { + try { + return new ArrayRealVector(preMultiply(((ArrayRealVector) v).getDataRef()), false); + } catch (ClassCastException cce) { + + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.getDimension() != nRows) { + throw new DimensionMismatchException(v.getDimension(), nRows); + } + + final double[] out = new double[nCols]; + for (int col = 0; col < nCols; ++col) { + double sum = 0; + for (int i = 0; i < nRows; ++i) { + sum += getEntry(i, col) * v.getEntry(i); + } + out[col] = sum; + } + + return new ArrayRealVector(out, false); + } + } + + /** {@inheritDoc} */ + public double walkInRowOrder(final RealMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int row = 0; row < rows; ++row) { + for (int column = 0; column < columns; ++column) { + final double oldValue = getEntry(row, column); + final double newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int row = 0; row < rows; ++row) { + for (int column = 0; column < columns; ++column) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInRowOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int row = startRow; row <= endRow; ++row) { + for (int column = startColumn; column <= endColumn; ++column) { + final double oldValue = getEntry(row, column); + final double newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int row = startRow; row <= endRow; ++row) { + for (int column = startColumn; column <= endColumn; ++column) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInColumnOrder(final RealMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int column = 0; column < columns; ++column) { + for (int row = 0; row < rows; ++row) { + final double oldValue = getEntry(row, column); + final double newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int column = 0; column < columns; ++column) { + for (int row = 0; row < rows; ++row) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInColumnOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int column = startColumn; column <= endColumn; ++column) { + for (int row = startRow; row <= endRow; ++row) { + final double oldValue = getEntry(row, column); + final double newValue = visitor.visit(row, column, oldValue); + setEntry(row, column, newValue); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int column = startColumn; column <= endColumn; ++column) { + for (int row = startRow; row <= endRow; ++row) { + visitor.visit(row, column, getEntry(row, column)); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor) { + return walkInRowOrder(visitor); + } + + /** {@inheritDoc} */ + public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor) { + return walkInRowOrder(visitor); + } + + /** {@inheritDoc} */ + public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn); + } + + /** {@inheritDoc} */ + public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn); + } + + /** + * Get a string representation for this matrix. + * @return a string representation for this matrix + */ + @Override + public String toString() { + final StringBuilder res = new StringBuilder(); + String fullClassName = getClass().getName(); + String shortClassName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1); + res.append(shortClassName); + res.append(DEFAULT_FORMAT.format(this)); + return res.toString(); + } + + /** + * Returns true iff object is a + * RealMatrix instance with the same dimensions as this + * and all corresponding matrix entries are equal. + * + * @param object the object to test equality against. + * @return true if object equals this + */ + @Override + public boolean equals(final Object object) { + if (object == this ) { + return true; + } + if (object instanceof RealMatrix == false) { + return false; + } + RealMatrix m = (RealMatrix) object; + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (m.getColumnDimension() != nCols || m.getRowDimension() != nRows) { + return false; + } + for (int row = 0; row < nRows; ++row) { + for (int col = 0; col < nCols; ++col) { + if (getEntry(row, col) != m.getEntry(row, col)) { + return false; + } + } + } + return true; + } + + /** + * Computes a hashcode for the matrix. + * + * @return hashcode for matrix + */ + @Override + public int hashCode() { + int ret = 7; + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + ret = ret * 31 + nRows; + ret = ret * 31 + nCols; + for (int row = 0; row < nRows; ++row) { + for (int col = 0; col < nCols; ++col) { + ret = ret * 31 + (11 * (row+1) + 17 * (col+1)) * + MathUtils.hash(getEntry(row, col)); + } + } + return ret; + } + + + /* + * Empty implementations of these methods are provided in order to allow for + * the use of the @Override tag with Java 1.5. + */ + + /** {@inheritDoc} */ + public abstract RealMatrix createMatrix(int rowDimension, int columnDimension) + throws NotStrictlyPositiveException; + + /** {@inheritDoc} */ + public abstract RealMatrix copy(); + + /** {@inheritDoc} */ + public abstract double getEntry(int row, int column) + throws OutOfRangeException; + + /** {@inheritDoc} */ + public abstract void setEntry(int row, int column, double value) + throws OutOfRangeException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AnyMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AnyMatrix.java new file mode 100644 index 000000000..8fb5b2f6d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/AnyMatrix.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + + +/** + * Interface defining very basic matrix operations. + * @since 2.0 + */ +public interface AnyMatrix { + + /** + * Is this a square matrix? + * @return true if the matrix is square (rowDimension = columnDimension) + */ + boolean isSquare(); + + /** + * Returns the number of rows in the matrix. + * + * @return rowDimension + */ + int getRowDimension(); + + /** + * Returns the number of columns in the matrix. + * + * @return columnDimension + */ + int getColumnDimension(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java new file mode 100644 index 000000000..181b2a8b3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java @@ -0,0 +1,612 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of FieldMatrix using a {@link FieldElement}[][] array to store entries. + *

    + * As specified in the {@link FieldMatrix} interface, matrix element indexing + * is 0-based -- e.g., getEntry(0, 0) + * returns the element in the first row, first column of the matrix. + *

    + * + * @param the type of the field elements + */ +public class Array2DRowFieldMatrix> + extends AbstractFieldMatrix + implements Serializable { + /** Serializable version identifier */ + private static final long serialVersionUID = 7260756672015356458L; + /** Entries of the matrix */ + private T[][] data; + + /** + * Creates a matrix with no data + * @param field field to which the elements belong + */ + public Array2DRowFieldMatrix(final Field field) { + super(field); + } + + /** + * Create a new {@code FieldMatrix} with the supplied row and column dimensions. + * + * @param field Field to which the elements belong. + * @param rowDimension Number of rows in the new matrix. + * @param columnDimension Number of columns in the new matrix. + * @throws NotStrictlyPositiveException if row or column dimension is not positive. + */ + public Array2DRowFieldMatrix(final Field field, final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + super(field, rowDimension, columnDimension); + data = MathArrays.buildArray(field, rowDimension, columnDimension); + } + + /** + * Create a new {@code FieldMatrix} using the input array as the underlying + * data array. + *

    The input array is copied, not referenced. This constructor has + * the same effect as calling {@link #Array2DRowFieldMatrix(FieldElement[][], boolean)} + * with the second argument set to {@code true}.

    + * + * @param d Data for the new matrix. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NoDataException if there are not at least one row and one column. + * @see #Array2DRowFieldMatrix(FieldElement[][], boolean) + */ + public Array2DRowFieldMatrix(final T[][] d) + throws DimensionMismatchException, NullArgumentException, + NoDataException { + this(extractField(d), d); + } + + /** + * Create a new {@code FieldMatrix} using the input array as the underlying + * data array. + *

    The input array is copied, not referenced. This constructor has + * the same effect as calling {@link #Array2DRowFieldMatrix(FieldElement[][], boolean)} + * with the second argument set to {@code true}.

    + * + * @param field Field to which the elements belong. + * @param d Data for the new matrix. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NoDataException if there are not at least one row and one column. + * @see #Array2DRowFieldMatrix(FieldElement[][], boolean) + */ + public Array2DRowFieldMatrix(final Field field, final T[][] d) + throws DimensionMismatchException, NullArgumentException, + NoDataException { + super(field); + copyIn(d); + } + + /** + * Create a new {@code FieldMatrix} using the input array as the underlying + * data array. + *

    If an array is built specially in order to be embedded in a + * {@code FieldMatrix} and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied.

    + * + * @param d Data for the new matrix. + * @param copyArray Whether to copy or reference the input array. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NoDataException if there are not at least one row and one column. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #Array2DRowFieldMatrix(FieldElement[][]) + */ + public Array2DRowFieldMatrix(final T[][] d, final boolean copyArray) + throws DimensionMismatchException, NoDataException, + NullArgumentException { + this(extractField(d), d, copyArray); + } + + /** + * Create a new {@code FieldMatrix} using the input array as the underlying + * data array. + *

    If an array is built specially in order to be embedded in a + * {@code FieldMatrix} and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied.

    + * + * @param field Field to which the elements belong. + * @param d Data for the new matrix. + * @param copyArray Whether to copy or reference the input array. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NoDataException if there are not at least one row and one column. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #Array2DRowFieldMatrix(FieldElement[][]) + */ + public Array2DRowFieldMatrix(final Field field, final T[][] d, final boolean copyArray) + throws DimensionMismatchException, NoDataException, NullArgumentException { + super(field); + if (copyArray) { + copyIn(d); + } else { + MathUtils.checkNotNull(d); + final int nRows = d.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + final int nCols = d[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + for (int r = 1; r < nRows; r++) { + if (d[r].length != nCols) { + throw new DimensionMismatchException(nCols, d[r].length); + } + } + data = d; + } + } + + /** + * Create a new (column) {@code FieldMatrix} using {@code v} as the + * data for the unique column of the created matrix. + * The input array is copied. + * + * @param v Column vector holding data for new matrix. + * @throws NoDataException if v is empty + */ + public Array2DRowFieldMatrix(final T[] v) throws NoDataException { + this(extractField(v), v); + } + + /** + * Create a new (column) {@code FieldMatrix} using {@code v} as the + * data for the unique column of the created matrix. + * The input array is copied. + * + * @param field Field to which the elements belong. + * @param v Column vector holding data for new matrix. + */ + public Array2DRowFieldMatrix(final Field field, final T[] v) { + super(field); + final int nRows = v.length; + data = MathArrays.buildArray(getField(), nRows, 1); + for (int row = 0; row < nRows; row++) { + data[row][0] = v[row]; + } + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + return new Array2DRowFieldMatrix(getField(), rowDimension, columnDimension); + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix copy() { + return new Array2DRowFieldMatrix(getField(), copyOut(), false); + } + + /** + * Add {@code m} to this matrix. + * + * @param m Matrix to be added. + * @return {@code this} + m. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as this matrix. + */ + public Array2DRowFieldMatrix add(final Array2DRowFieldMatrix m) + throws MatrixDimensionMismatchException { + // safety check + checkAdditionCompatible(m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final T[][] outData = MathArrays.buildArray(getField(), rowCount, columnCount); + for (int row = 0; row < rowCount; row++) { + final T[] dataRow = data[row]; + final T[] mRow = m.data[row]; + final T[] outDataRow = outData[row]; + for (int col = 0; col < columnCount; col++) { + outDataRow[col] = dataRow[col].add(mRow[col]); + } + } + + return new Array2DRowFieldMatrix(getField(), outData, false); + } + + /** + * Subtract {@code m} from this matrix. + * + * @param m Matrix to be subtracted. + * @return {@code this} + m. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as this matrix. + */ + public Array2DRowFieldMatrix subtract(final Array2DRowFieldMatrix m) + throws MatrixDimensionMismatchException { + // safety check + checkSubtractionCompatible(m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final T[][] outData = MathArrays.buildArray(getField(), rowCount, columnCount); + for (int row = 0; row < rowCount; row++) { + final T[] dataRow = data[row]; + final T[] mRow = m.data[row]; + final T[] outDataRow = outData[row]; + for (int col = 0; col < columnCount; col++) { + outDataRow[col] = dataRow[col].subtract(mRow[col]); + } + } + + return new Array2DRowFieldMatrix(getField(), outData, false); + + } + + /** + * Postmultiplying this matrix by {@code m}. + * + * @param m Matrix to postmultiply by. + * @return {@code this} * m. + * @throws DimensionMismatchException if the number of columns of this + * matrix is not equal to the number of rows of {@code m}. + */ + public Array2DRowFieldMatrix multiply(final Array2DRowFieldMatrix m) + throws DimensionMismatchException { + // safety check + checkMultiplicationCompatible(m); + + final int nRows = this.getRowDimension(); + final int nCols = m.getColumnDimension(); + final int nSum = this.getColumnDimension(); + final T[][] outData = MathArrays.buildArray(getField(), nRows, nCols); + for (int row = 0; row < nRows; row++) { + final T[] dataRow = data[row]; + final T[] outDataRow = outData[row]; + for (int col = 0; col < nCols; col++) { + T sum = getField().getZero(); + for (int i = 0; i < nSum; i++) { + sum = sum.add(dataRow[i].multiply(m.data[i][col])); + } + outDataRow[col] = sum; + } + } + + return new Array2DRowFieldMatrix(getField(), outData, false); + + } + + /** {@inheritDoc} */ + @Override + public T[][] getData() { + return copyOut(); + } + + /** + * Get a reference to the underlying data array. + * This methods returns internal data, not fresh copy of it. + * + * @return the 2-dimensional array of entries. + */ + public T[][] getDataRef() { + return data; + } + + /** {@inheritDoc} */ + @Override + public void setSubMatrix(final T[][] subMatrix, final int row, + final int column) + throws OutOfRangeException, NullArgumentException, NoDataException, + DimensionMismatchException { + if (data == null) { + if (row > 0) { + throw new MathIllegalStateException(LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row); + } + if (column > 0) { + throw new MathIllegalStateException(LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column); + } + final int nRows = subMatrix.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + + final int nCols = subMatrix[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + data = MathArrays.buildArray(getField(), subMatrix.length, nCols); + for (int i = 0; i < data.length; ++i) { + if (subMatrix[i].length != nCols) { + throw new DimensionMismatchException(nCols, subMatrix[i].length); + } + System.arraycopy(subMatrix[i], 0, data[i + row], column, nCols); + } + } else { + super.setSubMatrix(subMatrix, row, column); + } + + } + + /** {@inheritDoc} */ + @Override + public T getEntry(final int row, final int column) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + return data[row][column]; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(final int row, final int column, final T value) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + data[row][column] = value; + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(final int row, final int column, final T increment) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + data[row][column] = data[row][column].add(increment); + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(final int row, final int column, final T factor) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + data[row][column] = data[row][column].multiply(factor); + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return (data == null) ? 0 : data.length; + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return ((data == null) || (data[0] == null)) ? 0 : data[0].length; + } + + /** {@inheritDoc} */ + @Override + public T[] operate(final T[] v) throws DimensionMismatchException { + final int nRows = this.getRowDimension(); + final int nCols = this.getColumnDimension(); + if (v.length != nCols) { + throw new DimensionMismatchException(v.length, nCols); + } + final T[] out = MathArrays.buildArray(getField(), nRows); + for (int row = 0; row < nRows; row++) { + final T[] dataRow = data[row]; + T sum = getField().getZero(); + for (int i = 0; i < nCols; i++) { + sum = sum.add(dataRow[i].multiply(v[i])); + } + out[row] = sum; + } + return out; + } + + /** {@inheritDoc} */ + @Override + public T[] preMultiply(final T[] v) throws DimensionMismatchException { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nRows) { + throw new DimensionMismatchException(v.length, nRows); + } + + final T[] out = MathArrays.buildArray(getField(), nCols); + for (int col = 0; col < nCols; ++col) { + T sum = getField().getZero(); + for (int i = 0; i < nRows; ++i) { + sum = sum.add(data[i][col].multiply(v[i])); + } + out[col] = sum; + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int i = 0; i < rows; ++i) { + final T[] rowI = data[i]; + for (int j = 0; j < columns; ++j) { + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int i = 0; i < rows; ++i) { + final T[] rowI = data[i]; + for (int j = 0; j < columns; ++j) { + visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int i = startRow; i <= endRow; ++i) { + final T[] rowI = data[i]; + for (int j = startColumn; j <= endColumn; ++j) { + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int i = startRow; i <= endRow; ++i) { + final T[] rowI = data[i]; + for (int j = startColumn; j <= endColumn; ++j) { + visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInColumnOrder(final FieldMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int j = 0; j < columns; ++j) { + for (int i = 0; i < rows; ++i) { + final T[] rowI = data[i]; + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInColumnOrder(final FieldMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int j = 0; j < columns; ++j) { + for (int i = 0; i < rows; ++i) { + visitor.visit(i, j, data[i][j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInColumnOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int j = startColumn; j <= endColumn; ++j) { + for (int i = startRow; i <= endRow; ++i) { + final T[] rowI = data[i]; + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInColumnOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int j = startColumn; j <= endColumn; ++j) { + for (int i = startRow; i <= endRow; ++i) { + visitor.visit(i, j, data[i][j]); + } + } + return visitor.end(); + } + + /** + * Get a fresh copy of the underlying data array. + * + * @return a copy of the underlying data array. + */ + private T[][] copyOut() { + final int nRows = this.getRowDimension(); + final T[][] out = MathArrays.buildArray(getField(), nRows, getColumnDimension()); + // can't copy 2-d array in one shot, otherwise get row references + for (int i = 0; i < nRows; i++) { + System.arraycopy(data[i], 0, out[i], 0, data[i].length); + } + return out; + } + + /** + * Replace data with a fresh copy of the input array. + * + * @param in Data to copy. + * @throws NoDataException if the input array is empty. + * @throws DimensionMismatchException if the input array is not rectangular. + * @throws NullArgumentException if the input array is {@code null}. + */ + private void copyIn(final T[][] in) + throws NullArgumentException, NoDataException, + DimensionMismatchException { + setSubMatrix(in, 0, 0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowRealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowRealMatrix.java new file mode 100644 index 000000000..dfc913ce6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/Array2DRowRealMatrix.java @@ -0,0 +1,548 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of {@link RealMatrix} using a {@code double[][]} array to + * store entries. + * + */ +public class Array2DRowRealMatrix extends AbstractRealMatrix implements Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = -1067294169172445528L; + + /** Entries of the matrix. */ + private double data[][]; + + /** + * Creates a matrix with no data + */ + public Array2DRowRealMatrix() {} + + /** + * Create a new RealMatrix with the supplied row and column dimensions. + * + * @param rowDimension Number of rows in the new matrix. + * @param columnDimension Number of columns in the new matrix. + * @throws NotStrictlyPositiveException if the row or column dimension is + * not positive. + */ + public Array2DRowRealMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + super(rowDimension, columnDimension); + data = new double[rowDimension][columnDimension]; + } + + /** + * Create a new {@code RealMatrix} using the input array as the underlying + * data array. + *

    The input array is copied, not referenced. This constructor has + * the same effect as calling {@link #Array2DRowRealMatrix(double[][], boolean)} + * with the second argument set to {@code true}.

    + * + * @param d Data for the new matrix. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NoDataException if {@code d} row or column dimension is zero. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #Array2DRowRealMatrix(double[][], boolean) + */ + public Array2DRowRealMatrix(final double[][] d) + throws DimensionMismatchException, NoDataException, NullArgumentException { + copyIn(d); + } + + /** + * Create a new RealMatrix using the input array as the underlying + * data array. + * If an array is built specially in order to be embedded in a + * RealMatrix and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied. + * + * @param d Data for new matrix. + * @param copyArray if {@code true}, the input array will be copied, + * otherwise it will be referenced. + * @throws DimensionMismatchException if {@code d} is not rectangular. + * @throws NoDataException if {@code d} row or column dimension is zero. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #Array2DRowRealMatrix(double[][]) + */ + public Array2DRowRealMatrix(final double[][] d, final boolean copyArray) + throws DimensionMismatchException, NoDataException, + NullArgumentException { + if (copyArray) { + copyIn(d); + } else { + if (d == null) { + throw new NullArgumentException(); + } + final int nRows = d.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + final int nCols = d[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + for (int r = 1; r < nRows; r++) { + if (d[r].length != nCols) { + throw new DimensionMismatchException(d[r].length, nCols); + } + } + data = d; + } + } + + /** + * Create a new (column) RealMatrix using {@code v} as the + * data for the unique column of the created matrix. + * The input array is copied. + * + * @param v Column vector holding data for new matrix. + */ + public Array2DRowRealMatrix(final double[] v) { + final int nRows = v.length; + data = new double[nRows][1]; + for (int row = 0; row < nRows; row++) { + data[row][0] = v[row]; + } + } + + /** {@inheritDoc} */ + @Override + public RealMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + return new Array2DRowRealMatrix(rowDimension, columnDimension); + } + + /** {@inheritDoc} */ + @Override + public RealMatrix copy() { + return new Array2DRowRealMatrix(copyOut(), false); + } + + /** + * Compute the sum of {@code this} and {@code m}. + * + * @param m Matrix to be added. + * @return {@code this + m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public Array2DRowRealMatrix add(final Array2DRowRealMatrix m) + throws MatrixDimensionMismatchException { + // Safety check. + MatrixUtils.checkAdditionCompatible(this, m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final double[][] outData = new double[rowCount][columnCount]; + for (int row = 0; row < rowCount; row++) { + final double[] dataRow = data[row]; + final double[] mRow = m.data[row]; + final double[] outDataRow = outData[row]; + for (int col = 0; col < columnCount; col++) { + outDataRow[col] = dataRow[col] + mRow[col]; + } + } + + return new Array2DRowRealMatrix(outData, false); + } + + /** + * Returns {@code this} minus {@code m}. + * + * @param m Matrix to be subtracted. + * @return {@code this - m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public Array2DRowRealMatrix subtract(final Array2DRowRealMatrix m) + throws MatrixDimensionMismatchException { + MatrixUtils.checkSubtractionCompatible(this, m); + + final int rowCount = getRowDimension(); + final int columnCount = getColumnDimension(); + final double[][] outData = new double[rowCount][columnCount]; + for (int row = 0; row < rowCount; row++) { + final double[] dataRow = data[row]; + final double[] mRow = m.data[row]; + final double[] outDataRow = outData[row]; + for (int col = 0; col < columnCount; col++) { + outDataRow[col] = dataRow[col] - mRow[col]; + } + } + + return new Array2DRowRealMatrix(outData, false); + } + + /** + * Returns the result of postmultiplying {@code this} by {@code m}. + * + * @param m matrix to postmultiply by + * @return {@code this * m} + * @throws DimensionMismatchException if + * {@code columnDimension(this) != rowDimension(m)} + */ + public Array2DRowRealMatrix multiply(final Array2DRowRealMatrix m) + throws DimensionMismatchException { + MatrixUtils.checkMultiplicationCompatible(this, m); + + final int nRows = this.getRowDimension(); + final int nCols = m.getColumnDimension(); + final int nSum = this.getColumnDimension(); + + final double[][] outData = new double[nRows][nCols]; + // Will hold a column of "m". + final double[] mCol = new double[nSum]; + final double[][] mData = m.data; + + // Multiply. + for (int col = 0; col < nCols; col++) { + // Copy all elements of column "col" of "m" so that + // will be in contiguous memory. + for (int mRow = 0; mRow < nSum; mRow++) { + mCol[mRow] = mData[mRow][col]; + } + + for (int row = 0; row < nRows; row++) { + final double[] dataRow = data[row]; + double sum = 0; + for (int i = 0; i < nSum; i++) { + sum += dataRow[i] * mCol[i]; + } + outData[row][col] = sum; + } + } + + return new Array2DRowRealMatrix(outData, false); + } + + /** {@inheritDoc} */ + @Override + public double[][] getData() { + return copyOut(); + } + + /** + * Get a reference to the underlying data array. + * + * @return 2-dimensional array of entries. + */ + public double[][] getDataRef() { + return data; + } + + /** {@inheritDoc} */ + @Override + public void setSubMatrix(final double[][] subMatrix, final int row, + final int column) + throws NoDataException, OutOfRangeException, + DimensionMismatchException, NullArgumentException { + if (data == null) { + if (row > 0) { + throw new MathIllegalStateException(LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row); + } + if (column > 0) { + throw new MathIllegalStateException(LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column); + } + MathUtils.checkNotNull(subMatrix); + final int nRows = subMatrix.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + + final int nCols = subMatrix[0].length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + data = new double[subMatrix.length][nCols]; + for (int i = 0; i < data.length; ++i) { + if (subMatrix[i].length != nCols) { + throw new DimensionMismatchException(subMatrix[i].length, nCols); + } + System.arraycopy(subMatrix[i], 0, data[i + row], column, nCols); + } + } else { + super.setSubMatrix(subMatrix, row, column); + } + + } + + /** {@inheritDoc} */ + @Override + public double getEntry(final int row, final int column) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + return data[row][column]; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(final int row, final int column, final double value) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + data[row][column] = value; + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(final int row, final int column, + final double increment) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + data[row][column] += increment; + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(final int row, final int column, + final double factor) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + data[row][column] *= factor; + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return (data == null) ? 0 : data.length; + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return ((data == null) || (data[0] == null)) ? 0 : data[0].length; + } + + /** {@inheritDoc} */ + @Override + public double[] operate(final double[] v) + throws DimensionMismatchException { + final int nRows = this.getRowDimension(); + final int nCols = this.getColumnDimension(); + if (v.length != nCols) { + throw new DimensionMismatchException(v.length, nCols); + } + final double[] out = new double[nRows]; + for (int row = 0; row < nRows; row++) { + final double[] dataRow = data[row]; + double sum = 0; + for (int i = 0; i < nCols; i++) { + sum += dataRow[i] * v[i]; + } + out[row] = sum; + } + return out; + } + + /** {@inheritDoc} */ + @Override + public double[] preMultiply(final double[] v) + throws DimensionMismatchException { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + if (v.length != nRows) { + throw new DimensionMismatchException(v.length, nRows); + } + + final double[] out = new double[nCols]; + for (int col = 0; col < nCols; ++col) { + double sum = 0; + for (int i = 0; i < nRows; ++i) { + sum += data[i][col] * v[i]; + } + out[col] = sum; + } + + return out; + + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int i = 0; i < rows; ++i) { + final double[] rowI = data[i]; + for (int j = 0; j < columns; ++j) { + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int i = 0; i < rows; ++i) { + final double[] rowI = data[i]; + for (int j = 0; j < columns; ++j) { + visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int i = startRow; i <= endRow; ++i) { + final double[] rowI = data[i]; + for (int j = startColumn; j <= endColumn; ++j) { + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int i = startRow; i <= endRow; ++i) { + final double[] rowI = data[i]; + for (int j = startColumn; j <= endColumn; ++j) { + visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInColumnOrder(final RealMatrixChangingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int j = 0; j < columns; ++j) { + for (int i = 0; i < rows; ++i) { + final double[] rowI = data[i]; + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor) { + final int rows = getRowDimension(); + final int columns = getColumnDimension(); + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int j = 0; j < columns; ++j) { + for (int i = 0; i < rows; ++i) { + visitor.visit(i, j, data[i][j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInColumnOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int j = startColumn; j <= endColumn; ++j) { + for (int i = startRow; i <= endRow; ++i) { + final double[] rowI = data[i]; + rowI[j] = visitor.visit(i, j, rowI[j]); + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(getRowDimension(), getColumnDimension(), + startRow, endRow, startColumn, endColumn); + for (int j = startColumn; j <= endColumn; ++j) { + for (int i = startRow; i <= endRow; ++i) { + visitor.visit(i, j, data[i][j]); + } + } + return visitor.end(); + } + + /** + * Get a fresh copy of the underlying data array. + * + * @return a copy of the underlying data array. + */ + private double[][] copyOut() { + final int nRows = this.getRowDimension(); + final double[][] out = new double[nRows][this.getColumnDimension()]; + // can't copy 2-d array in one shot, otherwise get row references + for (int i = 0; i < nRows; i++) { + System.arraycopy(data[i], 0, out[i], 0, data[i].length); + } + return out; + } + + /** + * Replace data with a fresh copy of the input array. + * + * @param in Data to copy. + * @throws NoDataException if the input array is empty. + * @throws DimensionMismatchException if the input array is not rectangular. + * @throws NullArgumentException if the input array is {@code null}. + */ + private void copyIn(final double[][] in) + throws DimensionMismatchException, NoDataException, NullArgumentException { + setSubMatrix(in, 0, 0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayFieldVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayFieldVector.java new file mode 100644 index 000000000..f49b76c26 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayFieldVector.java @@ -0,0 +1,1147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This class implements the {@link FieldVector} interface with a {@link FieldElement} array. + * @param the type of the field elements + * @since 2.0 + */ +public class ArrayFieldVector> implements FieldVector, Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = 7648186910365927050L; + + /** Entries of the vector. */ + private T[] data; + + /** Field to which the elements belong. */ + private final Field field; + + /** + * Build a 0-length vector. + * Zero-length vectors may be used to initialize construction of vectors + * by data gathering. We start with zero-length and use either the {@link + * #ArrayFieldVector(ArrayFieldVector, ArrayFieldVector)} constructor + * or one of the {@code append} methods ({@link #add(FieldVector)} or + * {@link #append(ArrayFieldVector)}) to gather data into this vector. + * + * @param field field to which the elements belong + */ + public ArrayFieldVector(final Field field) { + this(field, 0); + } + + /** + * Construct a vector of zeroes. + * + * @param field Field to which the elements belong. + * @param size Size of the vector. + */ + public ArrayFieldVector(Field field, int size) { + this.field = field; + this.data = MathArrays.buildArray(field, size); + } + + /** + * Construct a vector with preset values. + * + * @param size Size of the vector. + * @param preset All entries will be set with this value. + */ + public ArrayFieldVector(int size, T preset) { + this(preset.getField(), size); + Arrays.fill(data, preset); + } + + /** + * Construct a vector from an array, copying the input array. + * This constructor needs a non-empty {@code d} array to retrieve + * the field from its first element. This implies it cannot build + * 0 length vectors. To build vectors from any size, one should + * use the {@link #ArrayFieldVector(Field, FieldElement[])} constructor. + * + * @param d Array. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws ZeroException if {@code d} is empty. + * @see #ArrayFieldVector(Field, FieldElement[]) + */ + public ArrayFieldVector(T[] d) + throws NullArgumentException, ZeroException { + MathUtils.checkNotNull(d); + try { + field = d[0].getField(); + data = d.clone(); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT); + } + } + + /** + * Construct a vector from an array, copying the input array. + * + * @param field Field to which the elements belong. + * @param d Array. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #ArrayFieldVector(FieldElement[]) + */ + public ArrayFieldVector(Field field, T[] d) + throws NullArgumentException { + MathUtils.checkNotNull(d); + this.field = field; + data = d.clone(); + } + + /** + * Create a new ArrayFieldVector using the input array as the underlying + * data array. + * If an array is built specially in order to be embedded in a + * ArrayFieldVector and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied. + * This constructor needs a non-empty {@code d} array to retrieve + * the field from its first element. This implies it cannot build + * 0 length vectors. To build vectors from any size, one should + * use the {@link #ArrayFieldVector(Field, FieldElement[], boolean)} + * constructor. + * + * @param d Data for the new vector. + * @param copyArray If {@code true}, the input array will be copied, + * otherwise it will be referenced. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws ZeroException if {@code d} is empty. + * @see #ArrayFieldVector(FieldElement[]) + * @see #ArrayFieldVector(Field, FieldElement[], boolean) + */ + public ArrayFieldVector(T[] d, boolean copyArray) + throws NullArgumentException, ZeroException { + MathUtils.checkNotNull(d); + if (d.length == 0) { + throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT); + } + field = d[0].getField(); + data = copyArray ? d.clone() : d; + } + + /** + * Create a new ArrayFieldVector using the input array as the underlying + * data array. + * If an array is built specially in order to be embedded in a + * ArrayFieldVector and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied. + * + * @param field Field to which the elements belong. + * @param d Data for the new vector. + * @param copyArray If {@code true}, the input array will be copied, + * otherwise it will be referenced. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #ArrayFieldVector(FieldElement[], boolean) + */ + public ArrayFieldVector(Field field, T[] d, boolean copyArray) + throws NullArgumentException { + MathUtils.checkNotNull(d); + this.field = field; + data = copyArray ? d.clone() : d; + } + + /** + * Construct a vector from part of a array. + * + * @param d Array. + * @param pos Position of the first entry. + * @param size Number of entries to copy. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NumberIsTooLargeException if the size of {@code d} is less + * than {@code pos + size}. + */ + public ArrayFieldVector(T[] d, int pos, int size) + throws NullArgumentException, NumberIsTooLargeException { + MathUtils.checkNotNull(d); + if (d.length < pos + size) { + throw new NumberIsTooLargeException(pos + size, d.length, true); + } + field = d[0].getField(); + data = MathArrays.buildArray(field, size); + System.arraycopy(d, pos, data, 0, size); + } + + /** + * Construct a vector from part of a array. + * + * @param field Field to which the elements belong. + * @param d Array. + * @param pos Position of the first entry. + * @param size Number of entries to copy. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NumberIsTooLargeException if the size of {@code d} is less + * than {@code pos + size}. + */ + public ArrayFieldVector(Field field, T[] d, int pos, int size) + throws NullArgumentException, NumberIsTooLargeException { + MathUtils.checkNotNull(d); + if (d.length < pos + size) { + throw new NumberIsTooLargeException(pos + size, d.length, true); + } + this.field = field; + data = MathArrays.buildArray(field, size); + System.arraycopy(d, pos, data, 0, size); + } + + /** + * Construct a vector from another vector, using a deep copy. + * + * @param v Vector to copy. + * @throws NullArgumentException if {@code v} is {@code null}. + */ + public ArrayFieldVector(FieldVector v) + throws NullArgumentException { + MathUtils.checkNotNull(v); + field = v.getField(); + data = MathArrays.buildArray(field, v.getDimension()); + for (int i = 0; i < data.length; ++i) { + data[i] = v.getEntry(i); + } + } + + /** + * Construct a vector from another vector, using a deep copy. + * + * @param v Vector to copy. + * @throws NullArgumentException if {@code v} is {@code null}. + */ + public ArrayFieldVector(ArrayFieldVector v) + throws NullArgumentException { + MathUtils.checkNotNull(v); + field = v.getField(); + data = v.data.clone(); + } + + /** + * Construct a vector from another vector. + * + * @param v Vector to copy. + * @param deep If {@code true} perform a deep copy, otherwise perform + * a shallow copy + * @throws NullArgumentException if {@code v} is {@code null}. + */ + public ArrayFieldVector(ArrayFieldVector v, boolean deep) + throws NullArgumentException { + MathUtils.checkNotNull(v); + field = v.getField(); + data = deep ? v.data.clone() : v.data; + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldVector, FieldVector)} + */ + @Deprecated + public ArrayFieldVector(ArrayFieldVector v1, ArrayFieldVector v2) + throws NullArgumentException { + this((FieldVector) v1, (FieldVector) v2); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @since 3.2 + */ + public ArrayFieldVector(FieldVector v1, FieldVector v2) + throws NullArgumentException { + MathUtils.checkNotNull(v1); + MathUtils.checkNotNull(v2); + field = v1.getField(); + final T[] v1Data = + (v1 instanceof ArrayFieldVector) ? ((ArrayFieldVector) v1).data : v1.toArray(); + final T[] v2Data = + (v2 instanceof ArrayFieldVector) ? ((ArrayFieldVector) v2).data : v2.toArray(); + data = MathArrays.buildArray(field, v1Data.length + v2Data.length); + System.arraycopy(v1Data, 0, data, 0, v1Data.length); + System.arraycopy(v2Data, 0, data, v1Data.length, v2Data.length); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldVector, FieldElement[])} + */ + @Deprecated + public ArrayFieldVector(ArrayFieldVector v1, T[] v2) + throws NullArgumentException { + this((FieldVector) v1, v2); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @since 3.2 + */ + public ArrayFieldVector(FieldVector v1, T[] v2) + throws NullArgumentException { + MathUtils.checkNotNull(v1); + MathUtils.checkNotNull(v2); + field = v1.getField(); + final T[] v1Data = + (v1 instanceof ArrayFieldVector) ? ((ArrayFieldVector) v1).data : v1.toArray(); + data = MathArrays.buildArray(field, v1Data.length + v2.length); + System.arraycopy(v1Data, 0, data, 0, v1Data.length); + System.arraycopy(v2, 0, data, v1Data.length, v2.length); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldElement[], FieldVector)} + */ + @Deprecated + public ArrayFieldVector(T[] v1, ArrayFieldVector v2) + throws NullArgumentException { + this(v1, (FieldVector) v2); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @since 3.2 + */ + public ArrayFieldVector(T[] v1, FieldVector v2) + throws NullArgumentException { + MathUtils.checkNotNull(v1); + MathUtils.checkNotNull(v2); + field = v2.getField(); + final T[] v2Data = + (v2 instanceof ArrayFieldVector) ? ((ArrayFieldVector) v2).data : v2.toArray(); + data = MathArrays.buildArray(field, v1.length + v2Data.length); + System.arraycopy(v1, 0, data, 0, v1.length); + System.arraycopy(v2Data, 0, data, v1.length, v2Data.length); + } + + /** + * Construct a vector by appending one vector to another vector. + * This constructor needs at least one non-empty array to retrieve + * the field from its first element. This implies it cannot build + * 0 length vectors. To build vectors from any size, one should + * use the {@link #ArrayFieldVector(Field, FieldElement[], FieldElement[])} + * constructor. + * + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @throws ZeroException if both arrays are empty. + * @see #ArrayFieldVector(Field, FieldElement[], FieldElement[]) + */ + public ArrayFieldVector(T[] v1, T[] v2) + throws NullArgumentException, ZeroException { + MathUtils.checkNotNull(v1); + MathUtils.checkNotNull(v2); + if (v1.length + v2.length == 0) { + throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT); + } + data = MathArrays.buildArray(v1[0].getField(), v1.length + v2.length); + System.arraycopy(v1, 0, data, 0, v1.length); + System.arraycopy(v2, 0, data, v1.length, v2.length); + field = data[0].getField(); + } + + /** + * Construct a vector by appending one vector to another vector. + * + * @param field Field to which the elements belong. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + * @throws NullArgumentException if {@code v1} or {@code v2} is + * {@code null}. + * @throws ZeroException if both arrays are empty. + * @see #ArrayFieldVector(FieldElement[], FieldElement[]) + */ + public ArrayFieldVector(Field field, T[] v1, T[] v2) + throws NullArgumentException, ZeroException { + MathUtils.checkNotNull(v1); + MathUtils.checkNotNull(v2); + if (v1.length + v2.length == 0) { + throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT); + } + data = MathArrays.buildArray(field, v1.length + v2.length); + System.arraycopy(v1, 0, data, 0, v1.length); + System.arraycopy(v2, 0, data, v1.length, v2.length); + this.field = field; + } + + /** {@inheritDoc} */ + public Field getField() { + return field; + } + + /** {@inheritDoc} */ + public FieldVector copy() { + return new ArrayFieldVector(this, true); + } + + /** {@inheritDoc} */ + public FieldVector add(FieldVector v) + throws DimensionMismatchException { + try { + return add((ArrayFieldVector) v); + } catch (ClassCastException cce) { + checkVectorDimensions(v); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].add(v.getEntry(i)); + } + return new ArrayFieldVector(field, out, false); + } + } + + /** + * Compute the sum of {@code this} and {@code v}. + * @param v vector to be added + * @return {@code this + v} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + */ + public ArrayFieldVector add(ArrayFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.data.length); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].add(v.data[i]); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector subtract(FieldVector v) + throws DimensionMismatchException { + try { + return subtract((ArrayFieldVector) v); + } catch (ClassCastException cce) { + checkVectorDimensions(v); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].subtract(v.getEntry(i)); + } + return new ArrayFieldVector(field, out, false); + } + } + + /** + * Compute {@code this} minus {@code v}. + * @param v vector to be subtracted + * @return {@code this - v} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + */ + public ArrayFieldVector subtract(ArrayFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.data.length); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].subtract(v.data[i]); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapAdd(T d) throws NullArgumentException { + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].add(d); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapAddToSelf(T d) throws NullArgumentException { + for (int i = 0; i < data.length; i++) { + data[i] = data[i].add(d); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapSubtract(T d) throws NullArgumentException { + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].subtract(d); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapSubtractToSelf(T d) throws NullArgumentException { + for (int i = 0; i < data.length; i++) { + data[i] = data[i].subtract(d); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapMultiply(T d) throws NullArgumentException { + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].multiply(d); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapMultiplyToSelf(T d) throws NullArgumentException { + for (int i = 0; i < data.length; i++) { + data[i] = data[i].multiply(d); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapDivide(T d) + throws NullArgumentException, MathArithmeticException { + MathUtils.checkNotNull(d); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].divide(d); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapDivideToSelf(T d) + throws NullArgumentException, MathArithmeticException { + MathUtils.checkNotNull(d); + for (int i = 0; i < data.length; i++) { + data[i] = data[i].divide(d); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapInv() throws MathArithmeticException { + T[] out = MathArrays.buildArray(field, data.length); + final T one = field.getOne(); + for (int i = 0; i < data.length; i++) { + try { + out[i] = one.divide(data[i]); + } catch (final MathArithmeticException e) { + throw new MathArithmeticException(LocalizedFormats.INDEX, i); + } + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector mapInvToSelf() throws MathArithmeticException { + final T one = field.getOne(); + for (int i = 0; i < data.length; i++) { + try { + data[i] = one.divide(data[i]); + } catch (final MathArithmeticException e) { + throw new MathArithmeticException(LocalizedFormats.INDEX, i); + } + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector ebeMultiply(FieldVector v) + throws DimensionMismatchException { + try { + return ebeMultiply((ArrayFieldVector) v); + } catch (ClassCastException cce) { + checkVectorDimensions(v); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].multiply(v.getEntry(i)); + } + return new ArrayFieldVector(field, out, false); + } + } + + /** + * Element-by-element multiplication. + * @param v vector by which instance elements must be multiplied + * @return a vector containing {@code this[i] * v[i]} for all {@code i} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + */ + public ArrayFieldVector ebeMultiply(ArrayFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.data.length); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + out[i] = data[i].multiply(v.data[i]); + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector ebeDivide(FieldVector v) + throws DimensionMismatchException, MathArithmeticException { + try { + return ebeDivide((ArrayFieldVector) v); + } catch (ClassCastException cce) { + checkVectorDimensions(v); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + try { + out[i] = data[i].divide(v.getEntry(i)); + } catch (final MathArithmeticException e) { + throw new MathArithmeticException(LocalizedFormats.INDEX, i); + } + } + return new ArrayFieldVector(field, out, false); + } + } + + /** + * Element-by-element division. + * @param v vector by which instance elements must be divided + * @return a vector containing {@code this[i] / v[i]} for all {@code i} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + * @throws MathArithmeticException if one entry of {@code v} is zero. + */ + public ArrayFieldVector ebeDivide(ArrayFieldVector v) + throws DimensionMismatchException, MathArithmeticException { + checkVectorDimensions(v.data.length); + T[] out = MathArrays.buildArray(field, data.length); + for (int i = 0; i < data.length; i++) { + try { + out[i] = data[i].divide(v.data[i]); + } catch (final MathArithmeticException e) { + throw new MathArithmeticException(LocalizedFormats.INDEX, i); + } + } + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public T[] getData() { + return data.clone(); + } + + /** + * Returns a reference to the underlying data array. + *

    Does not make a fresh copy of the underlying data.

    + * @return array of entries + */ + public T[] getDataRef() { + return data; + } + + /** {@inheritDoc} */ + public T dotProduct(FieldVector v) + throws DimensionMismatchException { + try { + return dotProduct((ArrayFieldVector) v); + } catch (ClassCastException cce) { + checkVectorDimensions(v); + T dot = field.getZero(); + for (int i = 0; i < data.length; i++) { + dot = dot.add(data[i].multiply(v.getEntry(i))); + } + return dot; + } + } + + /** + * Compute the dot product. + * @param v vector with which dot product should be computed + * @return the scalar dot product of {@code this} and {@code v} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + */ + public T dotProduct(ArrayFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.data.length); + T dot = field.getZero(); + for (int i = 0; i < data.length; i++) { + dot = dot.add(data[i].multiply(v.data[i])); + } + return dot; + } + + /** {@inheritDoc} */ + public FieldVector projection(FieldVector v) + throws DimensionMismatchException, MathArithmeticException { + return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v))); + } + + /** Find the orthogonal projection of this vector onto another vector. + * @param v vector onto which {@code this} must be projected + * @return projection of {@code this} onto {@code v} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} + * @throws MathArithmeticException if {@code v} is the null vector. + */ + public ArrayFieldVector projection(ArrayFieldVector v) + throws DimensionMismatchException, MathArithmeticException { + return (ArrayFieldVector) v.mapMultiply(dotProduct(v).divide(v.dotProduct(v))); + } + + /** {@inheritDoc} */ + public FieldMatrix outerProduct(FieldVector v) { + try { + return outerProduct((ArrayFieldVector) v); + } catch (ClassCastException cce) { + final int m = data.length; + final int n = v.getDimension(); + final FieldMatrix out = new Array2DRowFieldMatrix(field, m, n); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + out.setEntry(i, j, data[i].multiply(v.getEntry(j))); + } + } + return out; + } + } + + /** + * Compute the outer product. + * @param v vector with which outer product should be computed + * @return the matrix outer product between instance and v + */ + public FieldMatrix outerProduct(ArrayFieldVector v) { + final int m = data.length; + final int n = v.data.length; + final FieldMatrix out = new Array2DRowFieldMatrix(field, m, n); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + out.setEntry(i, j, data[i].multiply(v.data[j])); + } + } + return out; + } + + /** {@inheritDoc} */ + public T getEntry(int index) { + return data[index]; + } + + /** {@inheritDoc} */ + public int getDimension() { + return data.length; + } + + /** {@inheritDoc} */ + public FieldVector append(FieldVector v) { + try { + return append((ArrayFieldVector) v); + } catch (ClassCastException cce) { + return new ArrayFieldVector(this,new ArrayFieldVector(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 append(ArrayFieldVector v) { + return new ArrayFieldVector(this, v); + } + + /** {@inheritDoc} */ + public FieldVector append(T in) { + final T[] out = MathArrays.buildArray(field, data.length + 1); + System.arraycopy(data, 0, out, 0, data.length); + out[data.length] = in; + return new ArrayFieldVector(field, out, false); + } + + /** {@inheritDoc} */ + public FieldVector getSubVector(int index, int n) + throws OutOfRangeException, NotPositiveException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n); + } + ArrayFieldVector out = new ArrayFieldVector(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 v) throws OutOfRangeException { + try { + try { + set(index, (ArrayFieldVector) v); + } catch (ClassCastException cce) { + for (int i = index; i < index + v.getDimension(); ++i) { + data[i] = v.getEntry(i-index); + } + } + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + checkIndex(index + v.getDimension() - 1); + } + } + + /** + * Set a set of consecutive elements. + * + * @param index index of first element to be set. + * @param v vector containing the values to set. + * @throws OutOfRangeException if the index is invalid. + */ + public void set(int index, ArrayFieldVector v) throws OutOfRangeException { + try { + System.arraycopy(v.data, 0, data, index, v.data.length); + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + checkIndex(index + v.data.length - 1); + } + } + + /** {@inheritDoc} */ + public void set(T value) { + Arrays.fill(data, value); + } + + /** {@inheritDoc} */ + public T[] toArray(){ + return data.clone(); + } + + /** + * Check if instance and specified vectors have the same dimension. + * @param v vector to compare instance with + * @exception DimensionMismatchException if the vectors do not + * have the same dimensions + */ + protected void checkVectorDimensions(FieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + } + + /** + * Check if instance dimension is equal to some expected value. + * + * @param n Expected dimension. + * @throws DimensionMismatchException if the dimension is not equal to the + * size of {@code this} vector. + */ + protected void checkVectorDimensions(int n) + throws DimensionMismatchException { + if (data.length != n) { + throw new DimensionMismatchException(data.length, n); + } + } + + /** + * Visits (but does not alter) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorPreservingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorPreservingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (but does not alter) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** + * Visits (and possibly alters) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process and modify the entries + * of this vector + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorChangingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorChangingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (and possibly change) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** + * Test for the equality of two vectors. + * + * @param other Object to test for equality. + * @return {@code true} if two vector objects are equal, {@code false} + * otherwise. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + + try { + @SuppressWarnings("unchecked") // May fail, but we ignore ClassCastException + FieldVector rhs = (FieldVector) 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. + *

    All NaN values have the same hash code.

    + * @return a hash code value for this object + */ + @Override + public int hashCode() { + int h = 3542; + for (final T a : data) { + h ^= a.hashCode(); + } + return h; + } + + /** + * Check if an index is valid. + * + * @param index Index to check. + * @exception OutOfRangeException if the index is not valid. + */ + private void checkIndex(final int index) throws OutOfRangeException { + if (index < 0 || index >= getDimension()) { + throw new OutOfRangeException(LocalizedFormats.INDEX, + index, 0, getDimension() - 1); + } + } + + /** + * Checks that the indices of a subvector are valid. + * + * @param start the index of the first entry of the subvector + * @param end the index of the last entry of the subvector (inclusive) + * @throws OutOfRangeException if {@code start} of {@code end} are not valid + * @throws NumberIsTooSmallException if {@code end < start} + * @since 3.3 + */ + private void checkIndices(final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + final int dim = getDimension(); + if ((start < 0) || (start >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, + dim - 1); + } + if ((end < 0) || (end >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, + dim - 1); + } + if (end < start) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, + end, start, false); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayRealVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayRealVector.java new file mode 100644 index 000000000..b6396d31a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ArrayRealVector.java @@ -0,0 +1,954 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Iterator; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements the {@link RealVector} interface with a double array. + * @since 2.0 + */ +public class ArrayRealVector extends RealVector implements Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = -1097961340710804027L; + /** Default format. */ + private static final RealVectorFormat DEFAULT_FORMAT = RealVectorFormat.getInstance(); + + /** Entries of the vector. */ + private double data[]; + + /** + * Build a 0-length vector. + * Zero-length vectors may be used to initialized construction of vectors + * by data gathering. We start with zero-length and use either the {@link + * #ArrayRealVector(ArrayRealVector, ArrayRealVector)} constructor + * or one of the {@code append} method ({@link #append(double)}, + * {@link #append(ArrayRealVector)}) to gather data into this vector. + */ + public ArrayRealVector() { + data = new double[0]; + } + + /** + * Construct a vector of zeroes. + * + * @param size Size of the vector. + */ + public ArrayRealVector(int size) { + data = new double[size]; + } + + /** + * Construct a vector with preset values. + * + * @param size Size of the vector + * @param preset All entries will be set with this value. + */ + public ArrayRealVector(int size, double preset) { + data = new double[size]; + Arrays.fill(data, preset); + } + + /** + * Construct a vector from an array, copying the input array. + * + * @param d Array. + */ + public ArrayRealVector(double[] d) { + data = d.clone(); + } + + /** + * Create a new ArrayRealVector using the input array as the underlying + * data array. + * If an array is built specially in order to be embedded in a + * ArrayRealVector and not used directly, the {@code copyArray} may be + * set to {@code false}. This will prevent the copying and improve + * performance as no new array will be built and no data will be copied. + * + * @param d Data for the new vector. + * @param copyArray if {@code true}, the input array will be copied, + * otherwise it will be referenced. + * @throws NullArgumentException if {@code d} is {@code null}. + * @see #ArrayRealVector(double[]) + */ + public ArrayRealVector(double[] d, boolean copyArray) + throws NullArgumentException { + if (d == null) { + throw new NullArgumentException(); + } + data = copyArray ? d.clone() : d; + } + + /** + * Construct a vector from part of a array. + * + * @param d Array. + * @param pos Position of first entry. + * @param size Number of entries to copy. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NumberIsTooLargeException if the size of {@code d} is less + * than {@code pos + size}. + */ + public ArrayRealVector(double[] d, int pos, int size) + throws NullArgumentException, NumberIsTooLargeException { + if (d == null) { + throw new NullArgumentException(); + } + if (d.length < pos + size) { + throw new NumberIsTooLargeException(pos + size, d.length, true); + } + data = new double[size]; + System.arraycopy(d, pos, data, 0, size); + } + + /** + * Construct a vector from an array. + * + * @param d Array of {@code Double}s. + */ + public ArrayRealVector(Double[] d) { + data = new double[d.length]; + for (int i = 0; i < d.length; i++) { + data[i] = d[i].doubleValue(); + } + } + + /** + * Construct a vector from part of an array. + * + * @param d Array. + * @param pos Position of first entry. + * @param size Number of entries to copy. + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws NumberIsTooLargeException if the size of {@code d} is less + * than {@code pos + size}. + */ + public ArrayRealVector(Double[] d, int pos, int size) + throws NullArgumentException, NumberIsTooLargeException { + if (d == null) { + throw new NullArgumentException(); + } + if (d.length < pos + size) { + throw new NumberIsTooLargeException(pos + size, d.length, true); + } + data = new double[size]; + for (int i = pos; i < pos + size; i++) { + data[i - pos] = d[i].doubleValue(); + } + } + + /** + * Construct a vector from another vector, using a deep copy. + * + * @param v vector to copy. + * @throws NullArgumentException if {@code v} is {@code null}. + */ + public ArrayRealVector(RealVector v) throws NullArgumentException { + if (v == null) { + throw new NullArgumentException(); + } + data = new double[v.getDimension()]; + for (int i = 0; i < data.length; ++i) { + data[i] = v.getEntry(i); + } + } + + /** + * Construct a vector from another vector, using a deep copy. + * + * @param v Vector to copy. + * @throws NullArgumentException if {@code v} is {@code null}. + */ + public ArrayRealVector(ArrayRealVector v) throws NullArgumentException { + this(v, true); + } + + /** + * Construct a vector from another vector. + * + * @param v Vector to copy. + * @param deep If {@code true} perform a deep copy, otherwise perform a + * shallow copy. + */ + public ArrayRealVector(ArrayRealVector v, boolean deep) { + data = deep ? v.data.clone() : v.data; + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + */ + public ArrayRealVector(ArrayRealVector v1, ArrayRealVector v2) { + data = new double[v1.data.length + v2.data.length]; + System.arraycopy(v1.data, 0, data, 0, v1.data.length); + System.arraycopy(v2.data, 0, data, v1.data.length, v2.data.length); + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + */ + public ArrayRealVector(ArrayRealVector v1, RealVector v2) { + final int l1 = v1.data.length; + final int l2 = v2.getDimension(); + data = new double[l1 + l2]; + System.arraycopy(v1.data, 0, data, 0, l1); + for (int i = 0; i < l2; ++i) { + data[l1 + i] = v2.getEntry(i); + } + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + */ + public ArrayRealVector(RealVector v1, ArrayRealVector v2) { + final int l1 = v1.getDimension(); + final int l2 = v2.data.length; + data = new double[l1 + l2]; + for (int i = 0; i < l1; ++i) { + data[i] = v1.getEntry(i); + } + System.arraycopy(v2.data, 0, data, l1, l2); + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + */ + public ArrayRealVector(ArrayRealVector v1, double[] v2) { + final int l1 = v1.getDimension(); + final int l2 = v2.length; + data = new double[l1 + l2]; + System.arraycopy(v1.data, 0, data, 0, l1); + System.arraycopy(v2, 0, data, l1, l2); + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 First vector (will be put in front of the new vector). + * @param v2 Second vector (will be put at back of the new vector). + */ + public ArrayRealVector(double[] v1, ArrayRealVector v2) { + final int l1 = v1.length; + final int l2 = v2.getDimension(); + data = new double[l1 + l2]; + System.arraycopy(v1, 0, data, 0, l1); + System.arraycopy(v2.data, 0, data, l1, l2); + } + + /** + * Construct a vector by appending one vector to another vector. + * @param v1 first vector (will be put in front of the new vector) + * @param v2 second vector (will be put at back of the new vector) + */ + public ArrayRealVector(double[] v1, double[] v2) { + final int l1 = v1.length; + final int l2 = v2.length; + data = new double[l1 + l2]; + System.arraycopy(v1, 0, data, 0, l1); + System.arraycopy(v2, 0, data, l1, l2); + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector copy() { + return new ArrayRealVector(this, true); + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector add(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + final int dim = vData.length; + checkVectorDimensions(dim); + ArrayRealVector result = new ArrayRealVector(dim); + double[] resultData = result.data; + for (int i = 0; i < dim; i++) { + resultData[i] = data[i] + vData[i]; + } + return result; + } else { + checkVectorDimensions(v); + double[] out = data.clone(); + Iterator it = v.iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + out[e.getIndex()] += e.getValue(); + } + return new ArrayRealVector(out, false); + } + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector subtract(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + final int dim = vData.length; + checkVectorDimensions(dim); + ArrayRealVector result = new ArrayRealVector(dim); + double[] resultData = result.data; + for (int i = 0; i < dim; i++) { + resultData[i] = data[i] - vData[i]; + } + return result; + } else { + checkVectorDimensions(v); + double[] out = data.clone(); + Iterator it = v.iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + out[e.getIndex()] -= e.getValue(); + } + return new ArrayRealVector(out, false); + } + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector map(UnivariateFunction function) { + return copy().mapToSelf(function); + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector mapToSelf(UnivariateFunction function) { + for (int i = 0; i < data.length; i++) { + data[i] = function.value(data[i]); + } + return this; + } + + /** {@inheritDoc} */ + @Override + public RealVector mapAddToSelf(double d) { + for (int i = 0; i < data.length; i++) { + data[i] += d; + } + return this; + } + + /** {@inheritDoc} */ + @Override + public RealVector mapSubtractToSelf(double d) { + for (int i = 0; i < data.length; i++) { + data[i] -= d; + } + return this; + } + + /** {@inheritDoc} */ + @Override + public RealVector mapMultiplyToSelf(double d) { + for (int i = 0; i < data.length; i++) { + data[i] *= d; + } + return this; + } + + /** {@inheritDoc} */ + @Override + public RealVector mapDivideToSelf(double d) { + for (int i = 0; i < data.length; i++) { + data[i] /= d; + } + return this; + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector ebeMultiply(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + final int dim = vData.length; + checkVectorDimensions(dim); + ArrayRealVector result = new ArrayRealVector(dim); + double[] resultData = result.data; + for (int i = 0; i < dim; i++) { + resultData[i] = data[i] * vData[i]; + } + return result; + } else { + checkVectorDimensions(v); + double[] out = data.clone(); + for (int i = 0; i < data.length; i++) { + out[i] *= v.getEntry(i); + } + return new ArrayRealVector(out, false); + } + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector ebeDivide(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + final int dim = vData.length; + checkVectorDimensions(dim); + ArrayRealVector result = new ArrayRealVector(dim); + double[] resultData = result.data; + for (int i = 0; i < dim; i++) { + resultData[i] = data[i] / vData[i]; + } + return result; + } else { + checkVectorDimensions(v); + double[] out = data.clone(); + for (int i = 0; i < data.length; i++) { + out[i] /= v.getEntry(i); + } + return new ArrayRealVector(out, false); + } + } + + /** + * Get a reference to the underlying data array. + * This method does not make a fresh copy of the underlying data. + * + * @return the array of entries. + */ + public double[] getDataRef() { + return data; + } + + /** {@inheritDoc} */ + @Override + public double dotProduct(RealVector v) throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + checkVectorDimensions(vData.length); + double dot = 0; + for (int i = 0; i < data.length; i++) { + dot += data[i] * vData[i]; + } + return dot; + } + return super.dotProduct(v); + } + + /** {@inheritDoc} */ + @Override + public double getNorm() { + double sum = 0; + for (double a : data) { + sum += a * a; + } + return FastMath.sqrt(sum); + } + + /** {@inheritDoc} */ + @Override + public double getL1Norm() { + double sum = 0; + for (double a : data) { + sum += FastMath.abs(a); + } + return sum; + } + + /** {@inheritDoc} */ + @Override + public double getLInfNorm() { + double max = 0; + for (double a : data) { + max = FastMath.max(max, FastMath.abs(a)); + } + return max; + } + + /** {@inheritDoc} */ + @Override + public double getDistance(RealVector v) throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + checkVectorDimensions(vData.length); + double sum = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - vData[i]; + sum += delta * delta; + } + return FastMath.sqrt(sum); + } else { + checkVectorDimensions(v); + double sum = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - v.getEntry(i); + sum += delta * delta; + } + return FastMath.sqrt(sum); + } + } + + /** {@inheritDoc} */ + @Override + public double getL1Distance(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + checkVectorDimensions(vData.length); + double sum = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - vData[i]; + sum += FastMath.abs(delta); + } + return sum; + } else { + checkVectorDimensions(v); + double sum = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - v.getEntry(i); + sum += FastMath.abs(delta); + } + return sum; + } + } + + /** {@inheritDoc} */ + @Override + public double getLInfDistance(RealVector v) + throws DimensionMismatchException { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + checkVectorDimensions(vData.length); + double max = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - vData[i]; + max = FastMath.max(max, FastMath.abs(delta)); + } + return max; + } else { + checkVectorDimensions(v); + double max = 0; + for (int i = 0; i < data.length; ++i) { + final double delta = data[i] - v.getEntry(i); + max = FastMath.max(max, FastMath.abs(delta)); + } + return max; + } + } + + /** {@inheritDoc} */ + @Override + public RealMatrix outerProduct(RealVector v) { + if (v instanceof ArrayRealVector) { + final double[] vData = ((ArrayRealVector) v).data; + final int m = data.length; + final int n = vData.length; + final RealMatrix out = MatrixUtils.createRealMatrix(m, n); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + out.setEntry(i, j, data[i] * vData[j]); + } + } + return out; + } else { + final int m = data.length; + final int n = v.getDimension(); + final RealMatrix out = MatrixUtils.createRealMatrix(m, n); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + out.setEntry(i, j, data[i] * v.getEntry(j)); + } + } + return out; + } + } + + /** {@inheritDoc} */ + @Override + public double getEntry(int index) throws OutOfRangeException { + try { + return data[index]; + } catch (IndexOutOfBoundsException e) { + throw new OutOfRangeException(LocalizedFormats.INDEX, index, 0, + getDimension() - 1); + } + } + + /** {@inheritDoc} */ + @Override + public int getDimension() { + return data.length; + } + + /** {@inheritDoc} */ + @Override + public RealVector append(RealVector v) { + try { + return new ArrayRealVector(this, (ArrayRealVector) v); + } catch (ClassCastException cce) { + return new ArrayRealVector(this, v); + } + } + + /** + * Construct a vector by appending a vector to this vector. + * + * @param v Vector to append to this one. + * @return a new vector. + */ + public ArrayRealVector append(ArrayRealVector v) { + return new ArrayRealVector(this, v); + } + + /** {@inheritDoc} */ + @Override + public RealVector append(double in) { + final double[] out = new double[data.length + 1]; + System.arraycopy(data, 0, out, 0, data.length); + out[data.length] = in; + return new ArrayRealVector(out, false); + } + + /** {@inheritDoc} */ + @Override + public RealVector getSubVector(int index, int n) + throws OutOfRangeException, NotPositiveException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n); + } + ArrayRealVector out = new ArrayRealVector(n); + try { + System.arraycopy(data, index, out.data, 0, n); + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + checkIndex(index + n - 1); + } + return out; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(int index, double value) throws OutOfRangeException { + try { + data[index] = value; + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + } + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(int index, double increment) + throws OutOfRangeException { + try { + data[index] += increment; + } catch(IndexOutOfBoundsException e){ + throw new OutOfRangeException(LocalizedFormats.INDEX, + index, 0, data.length - 1); + } + } + + /** {@inheritDoc} */ + @Override + public void setSubVector(int index, RealVector v) + throws OutOfRangeException { + if (v instanceof ArrayRealVector) { + setSubVector(index, ((ArrayRealVector) v).data); + } else { + try { + for (int i = index; i < index + v.getDimension(); ++i) { + data[i] = v.getEntry(i - index); + } + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + checkIndex(index + v.getDimension() - 1); + } + } + } + + /** + * Set a set of consecutive elements. + * + * @param index Index of first element to be set. + * @param v Vector containing the values to set. + * @throws OutOfRangeException if the index is inconsistent with the vector + * size. + */ + public void setSubVector(int index, double[] v) + throws OutOfRangeException { + try { + System.arraycopy(v, 0, data, index, v.length); + } catch (IndexOutOfBoundsException e) { + checkIndex(index); + checkIndex(index + v.length - 1); + } + } + + /** {@inheritDoc} */ + @Override + public void set(double value) { + Arrays.fill(data, value); + } + + /** {@inheritDoc} */ + @Override + public double[] toArray(){ + return data.clone(); + } + + /** {@inheritDoc} */ + @Override + public String toString(){ + return DEFAULT_FORMAT.format(this); + } + + /** + * Check if instance and specified vectors have the same dimension. + * + * @param v Vector to compare instance with. + * @throws DimensionMismatchException if the vectors do not + * have the same dimension. + */ + @Override + protected void checkVectorDimensions(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + } + + /** + * Check if instance dimension is equal to some expected value. + * + * @param n Expected dimension. + * @throws DimensionMismatchException if the dimension is + * inconsistent with vector size. + */ + @Override + protected void checkVectorDimensions(int n) + throws DimensionMismatchException { + if (data.length != n) { + throw new DimensionMismatchException(data.length, n); + } + } + + /** + * Check if any coordinate of this vector is {@code NaN}. + * + * @return {@code true} if any coordinate of this vector is {@code NaN}, + * {@code false} otherwise. + */ + @Override + public boolean isNaN() { + for (double v : data) { + if (Double.isNaN(v)) { + return true; + } + } + return false; + } + + /** + * Check whether any coordinate of this vector is infinite and none + * are {@code NaN}. + * + * @return {@code true} if any coordinate of this vector is infinite and + * none are {@code NaN}, {@code false} otherwise. + */ + @Override + public boolean isInfinite() { + if (isNaN()) { + return false; + } + + for (double v : data) { + if (Double.isInfinite(v)) { + return true; + } + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof RealVector)) { + return false; + } + + RealVector rhs = (RealVector) other; + if (data.length != rhs.getDimension()) { + return false; + } + + if (rhs.isNaN()) { + return this.isNaN(); + } + + for (int i = 0; i < data.length; ++i) { + if (data[i] != rhs.getEntry(i)) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} All {@code NaN} values have the same hash code. + */ + @Override + public int hashCode() { + if (isNaN()) { + return 9; + } + return MathUtils.hash(data); + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector combine(double a, double b, RealVector y) + throws DimensionMismatchException { + return copy().combineToSelf(a, b, y); + } + + /** {@inheritDoc} */ + @Override + public ArrayRealVector combineToSelf(double a, double b, RealVector y) + throws DimensionMismatchException { + if (y instanceof ArrayRealVector) { + final double[] yData = ((ArrayRealVector) y).data; + checkVectorDimensions(yData.length); + for (int i = 0; i < this.data.length; i++) { + data[i] = a * data[i] + b * yData[i]; + } + } else { + checkVectorDimensions(y); + for (int i = 0; i < this.data.length; i++) { + data[i] = a * data[i] + b * y.getEntry(i); + } + } + return this; + } + + /** {@inheritDoc} */ + @Override + public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor) { + visitor.start(data.length, 0, data.length - 1); + for (int i = 0; i < data.length; i++) { + visitor.visit(i, data[i]); + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor, + final int start, final int end) throws NumberIsTooSmallException, + OutOfRangeException { + checkIndices(start, end); + visitor.start(data.length, start, end); + for (int i = start; i <= end; i++) { + visitor.visit(i, data[i]); + } + return visitor.end(); + } + + /** + * {@inheritDoc} + * + * In this implementation, the optimized order is the default order. + */ + @Override + public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * {@inheritDoc} + * + * In this implementation, the optimized order is the default order. + */ + @Override + public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor, + final int start, final int end) throws NumberIsTooSmallException, + OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** {@inheritDoc} */ + @Override + public double walkInDefaultOrder(final RealVectorChangingVisitor visitor) { + visitor.start(data.length, 0, data.length - 1); + for (int i = 0; i < data.length; i++) { + data[i] = visitor.visit(i, data[i]); + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInDefaultOrder(final RealVectorChangingVisitor visitor, + final int start, final int end) throws NumberIsTooSmallException, + OutOfRangeException { + checkIndices(start, end); + visitor.start(data.length, start, end); + for (int i = start; i <= end; i++) { + data[i] = visitor.visit(i, data[i]); + } + return visitor.end(); + } + + /** + * {@inheritDoc} + * + * In this implementation, the optimized order is the default order. + */ + @Override + public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * {@inheritDoc} + * + * In this implementation, the optimized order is the default order. + */ + @Override + public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor, + final int start, final int end) throws NumberIsTooSmallException, + OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BiDiagonalTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BiDiagonalTransformer.java new file mode 100644 index 000000000..a0fbb8e4f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BiDiagonalTransformer.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Class transforming any matrix to bi-diagonal shape. + *

    Any m × n matrix A can be written as the product of three matrices: + * A = U × B × VT 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.

    + *

    Transformation to bi-diagonal shape is often not a goal by itself, but it is + * an intermediate step in more general decomposition algorithms like {@link + * SingularValueDecomposition Singular Value Decomposition}. This class is therefore + * intended for internal use by the library and is not public. As a consequence of + * this explicitly limited scope, many methods directly returns references to + * internal arrays, not copies.

    + * @since 2.0 + */ +class BiDiagonalTransformer { + + /** Householder vectors. */ + private final double householderVectors[][]; + + /** Main diagonal. */ + private final double[] main; + + /** Secondary diagonal. */ + private final double[] secondary; + + /** Cached value of U. */ + private RealMatrix cachedU; + + /** Cached value of B. */ + private RealMatrix cachedB; + + /** Cached value of V. */ + private RealMatrix cachedV; + + /** + * Build the transformation to bi-diagonal shape of a matrix. + * @param matrix the matrix to transform. + */ + BiDiagonalTransformer(RealMatrix matrix) { + + final int m = matrix.getRowDimension(); + final int n = matrix.getColumnDimension(); + final int p = FastMath.min(m, n); + householderVectors = matrix.getData(); + main = new double[p]; + secondary = new double[p - 1]; + cachedU = null; + cachedB = null; + cachedV = null; + + // transform matrix + if (m >= n) { + transformToUpperBiDiagonal(); + } else { + transformToLowerBiDiagonal(); + } + + } + + /** + * Returns the matrix U of the transform. + *

    U is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the U matrix + */ + public RealMatrix getU() { + + if (cachedU == null) { + + final int m = householderVectors.length; + final int n = householderVectors[0].length; + final int p = main.length; + final int diagOffset = (m >= n) ? 0 : 1; + final double[] diagonal = (m >= n) ? main : secondary; + double[][] ua = new double[m][m]; + + // fill up the part of the matrix not affected by Householder transforms + for (int k = m - 1; k >= p; --k) { + ua[k][k] = 1; + } + + // build up first part of the matrix by applying Householder transforms + for (int k = p - 1; k >= diagOffset; --k) { + final double[] hK = householderVectors[k]; + ua[k][k] = 1; + if (hK[k - diagOffset] != 0.0) { + for (int j = k; j < m; ++j) { + double alpha = 0; + for (int i = k; i < m; ++i) { + alpha -= ua[i][j] * householderVectors[i][k - diagOffset]; + } + alpha /= diagonal[k - diagOffset] * hK[k - diagOffset]; + + for (int i = k; i < m; ++i) { + ua[i][j] += -alpha * householderVectors[i][k - diagOffset]; + } + } + } + } + if (diagOffset > 0) { + ua[0][0] = 1; + } + cachedU = MatrixUtils.createRealMatrix(ua); + } + + // return the cached matrix + return cachedU; + + } + + /** + * Returns the bi-diagonal matrix B of the transform. + * @return the B matrix + */ + public RealMatrix getB() { + + if (cachedB == null) { + + final int m = householderVectors.length; + final int n = householderVectors[0].length; + double[][] ba = new double[m][n]; + for (int i = 0; i < main.length; ++i) { + ba[i][i] = main[i]; + if (m < n) { + if (i > 0) { + ba[i][i-1] = secondary[i - 1]; + } + } else { + if (i < main.length - 1) { + ba[i][i+1] = secondary[i]; + } + } + } + cachedB = MatrixUtils.createRealMatrix(ba); + } + + // return the cached matrix + return cachedB; + + } + + /** + * Returns the matrix V of the transform. + *

    V is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the V matrix + */ + public RealMatrix getV() { + + if (cachedV == null) { + + final int m = householderVectors.length; + final int n = householderVectors[0].length; + final int p = main.length; + final int diagOffset = (m >= n) ? 1 : 0; + final double[] diagonal = (m >= n) ? secondary : main; + double[][] va = new double[n][n]; + + // fill up the part of the matrix not affected by Householder transforms + for (int k = n - 1; k >= p; --k) { + va[k][k] = 1; + } + + // build up first part of the matrix by applying Householder transforms + for (int k = p - 1; k >= diagOffset; --k) { + final double[] hK = householderVectors[k - diagOffset]; + va[k][k] = 1; + if (hK[k] != 0.0) { + for (int j = k; j < n; ++j) { + double beta = 0; + for (int i = k; i < n; ++i) { + beta -= va[i][j] * hK[i]; + } + beta /= diagonal[k - diagOffset] * hK[k]; + + for (int i = k; i < n; ++i) { + va[i][j] += -beta * hK[i]; + } + } + } + } + if (diagOffset > 0) { + va[0][0] = 1; + } + cachedV = MatrixUtils.createRealMatrix(va); + } + + // return the cached matrix + return cachedV; + + } + + /** + * Get the Householder vectors of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the main diagonal elements of the B matrix + */ + double[][] getHouseholderVectorsRef() { + return householderVectors; + } + + /** + * Get the main diagonal elements of the matrix B of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the main diagonal elements of the B matrix + */ + double[] getMainDiagonalRef() { + return main; + } + + /** + * Get the secondary diagonal elements of the matrix B of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the secondary diagonal elements of the B matrix + */ + double[] getSecondaryDiagonalRef() { + return secondary; + } + + /** + * Check if the matrix is transformed to upper bi-diagonal. + * @return true if the matrix is transformed to upper bi-diagonal + */ + boolean isUpperBiDiagonal() { + return householderVectors.length >= householderVectors[0].length; + } + + /** + * Transform original matrix to upper bi-diagonal form. + *

    Transformation is done using alternate Householder transforms + * on columns and rows.

    + */ + private void transformToUpperBiDiagonal() { + + final int m = householderVectors.length; + final int n = householderVectors[0].length; + for (int k = 0; k < n; k++) { + + //zero-out a column + double xNormSqr = 0; + for (int i = k; i < m; ++i) { + final double c = householderVectors[i][k]; + xNormSqr += c * c; + } + final double[] hK = householderVectors[k]; + final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + main[k] = a; + if (a != 0.0) { + hK[k] -= a; + for (int j = k + 1; j < n; ++j) { + double alpha = 0; + for (int i = k; i < m; ++i) { + final double[] hI = householderVectors[i]; + alpha -= hI[j] * hI[k]; + } + alpha /= a * householderVectors[k][k]; + for (int i = k; i < m; ++i) { + final double[] hI = householderVectors[i]; + hI[j] -= alpha * hI[k]; + } + } + } + + if (k < n - 1) { + //zero-out a row + xNormSqr = 0; + for (int j = k + 1; j < n; ++j) { + final double c = hK[j]; + xNormSqr += c * c; + } + final double b = (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + secondary[k] = b; + if (b != 0.0) { + hK[k + 1] -= b; + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + double beta = 0; + for (int j = k + 1; j < n; ++j) { + beta -= hI[j] * hK[j]; + } + beta /= b * hK[k + 1]; + for (int j = k + 1; j < n; ++j) { + hI[j] -= beta * hK[j]; + } + } + } + } + + } + } + + /** + * Transform original matrix to lower bi-diagonal form. + *

    Transformation is done using alternate Householder transforms + * on rows and columns.

    + */ + private void transformToLowerBiDiagonal() { + + final int m = householderVectors.length; + final int n = householderVectors[0].length; + for (int k = 0; k < m; k++) { + + //zero-out a row + final double[] hK = householderVectors[k]; + double xNormSqr = 0; + for (int j = k; j < n; ++j) { + final double c = hK[j]; + xNormSqr += c * c; + } + final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + main[k] = a; + if (a != 0.0) { + hK[k] -= a; + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + double alpha = 0; + for (int j = k; j < n; ++j) { + alpha -= hI[j] * hK[j]; + } + alpha /= a * householderVectors[k][k]; + for (int j = k; j < n; ++j) { + hI[j] -= alpha * hK[j]; + } + } + } + + if (k < m - 1) { + //zero-out a column + final double[] hKp1 = householderVectors[k + 1]; + xNormSqr = 0; + for (int i = k + 1; i < m; ++i) { + final double c = householderVectors[i][k]; + xNormSqr += c * c; + } + final double b = (hKp1[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + secondary[k] = b; + if (b != 0.0) { + hKp1[k] -= b; + for (int j = k + 1; j < n; ++j) { + double beta = 0; + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + beta -= hI[j] * hI[k]; + } + beta /= b * hKp1[k]; + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + hI[j] -= beta * hI[k]; + } + } + } + } + + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockFieldMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockFieldMatrix.java new file mode 100644 index 000000000..eda351067 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockFieldMatrix.java @@ -0,0 +1,1592 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Cache-friendly implementation of FieldMatrix using a flat arrays to store + * square blocks of the matrix. + *

    + * 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. + *

    + *

    + * 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. + *

    + *

    + * 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}2 elements long for regular blocks. The blocks are themselves + * organized in row major order. + *

    + *

    + * 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. + *

    + *

    + * The layout complexity overhead versus simple mapping of matrices to java + * arrays is negligible for small matrices (about 1%). The gain from cache efficiency leads + * to up to 3-fold improvements for matrices of moderate to large size. + *

    + * @param the type of the field elements + * @since 2.0 + */ +public class BlockFieldMatrix> extends AbstractFieldMatrix implements Serializable { + /** Block size. */ + public static final int BLOCK_SIZE = 36; + /** Serializable version identifier. */ + private static final long serialVersionUID = -4602336630143123183L; + /** Blocks of matrix entries. */ + private final T blocks[][]; + /** Number of rows of the matrix. */ + private final int rows; + /** Number of columns of the matrix. */ + private final int columns; + /** Number of block rows of the matrix. */ + private final int blockRows; + /** Number of block columns of the matrix. */ + private final int blockColumns; + + /** + * Create a new matrix with the supplied row and column dimensions. + * + * @param field Field to which the elements belong. + * @param rows Number of rows in the new matrix. + * @param columns Number of columns in the new matrix. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + */ + public BlockFieldMatrix(final Field field, final int rows, + final int columns) + throws NotStrictlyPositiveException { + super(field, rows, columns); + this.rows = rows; + this.columns = columns; + + // number of blocks + blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + // allocate storage blocks, taking care of smaller ones at right and bottom + blocks = createBlocksLayout(field, rows, columns); + } + + /** + * Create a new dense matrix copying entries from raw layout data. + *

    The input array must already be in raw layout.

    + *

    Calling this constructor is equivalent to call: + *

    matrix = new BlockFieldMatrix(getField(), rawData.length, rawData[0].length,
    +     *                                   toBlocksLayout(rawData), false);
    + *

    + * + * @param rawData Data for the new matrix, in raw layout. + * @throws DimensionMismatchException if the {@code blockData} shape is + * inconsistent with block layout. + * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean) + */ + public BlockFieldMatrix(final T[][] rawData) + throws DimensionMismatchException { + this(rawData.length, rawData[0].length, toBlocksLayout(rawData), false); + } + + /** + * Create a new dense matrix copying entries from block layout data. + *

    The input array must already be in blocks layout.

    + * @param rows the number of rows in the new matrix + * @param columns the number of columns in the new matrix + * @param blockData data for new matrix + * @param copyArray if true, the input array will be copied, otherwise + * it will be referenced + * + * @throws DimensionMismatchException if the {@code blockData} shape is + * inconsistent with block layout. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @see #createBlocksLayout(Field, int, int) + * @see #toBlocksLayout(FieldElement[][]) + * @see #BlockFieldMatrix(FieldElement[][]) + */ + public BlockFieldMatrix(final int rows, final int columns, + final T[][] blockData, final boolean copyArray) + throws DimensionMismatchException, NotStrictlyPositiveException { + super(extractField(blockData), rows, columns); + this.rows = rows; + this.columns = columns; + + // number of blocks + blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (copyArray) { + // allocate storage blocks, taking care of smaller ones at right and bottom + blocks = MathArrays.buildArray(getField(), blockRows * blockColumns, -1); + } else { + // reference existing array + blocks = blockData; + } + + int index = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock, ++index) { + if (blockData[index].length != iHeight * blockWidth(jBlock)) { + throw new DimensionMismatchException(blockData[index].length, + iHeight * blockWidth(jBlock)); + } + if (copyArray) { + blocks[index] = blockData[index].clone(); + } + } + } + } + + /** + * Convert a data array from raw layout to blocks layout. + *

    + * Raw layout is the straightforward layout where element at row i and + * column j is in array element rawData[i][j]. 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. + *

    + *

    + * This method creates an array in blocks layout from an input array in raw layout. + * It can be used to provide the array argument of the {@link + * #BlockFieldMatrix(int, int, FieldElement[][], boolean)} + * constructor. + *

    + * @param Type of the field elements. + * @param rawData Data array in raw layout. + * @return a new data array containing the same entries but in blocks layout + * @throws DimensionMismatchException if {@code rawData} is not rectangular + * (not all rows have the same length). + * @see #createBlocksLayout(Field, int, int) + * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean) + */ + public static > T[][] toBlocksLayout(final T[][] rawData) + throws DimensionMismatchException { + + final int rows = rawData.length; + final int columns = rawData[0].length; + final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + // safety checks + for (int i = 0; i < rawData.length; ++i) { + final int length = rawData[i].length; + if (length != columns) { + throw new DimensionMismatchException(columns, length); + } + } + + // convert array + final Field field = extractField(rawData); + final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int iHeight = pEnd - pStart; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final int jWidth = qEnd - qStart; + + // allocate new block + final T[] block = MathArrays.buildArray(field, iHeight * jWidth); + blocks[blockIndex] = block; + + // copy data + int index = 0; + for (int p = pStart; p < pEnd; ++p) { + System.arraycopy(rawData[p], qStart, block, index, jWidth); + index += jWidth; + } + + ++blockIndex; + } + } + + return blocks; + } + + /** + * Create a data array in blocks layout. + *

    + * This method can be used to create the array argument of the {@link + * #BlockFieldMatrix(int, int, FieldElement[][], boolean)} + * constructor. + *

    + * @param Type of the field elements. + * @param field Field to which the elements belong. + * @param rows Number of rows in the new matrix. + * @param columns Number of columns in the new matrix. + * @return a new data array in blocks layout. + * @see #toBlocksLayout(FieldElement[][]) + * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean) + */ + public static > T[][] createBlocksLayout(final Field field, + final int rows, final int columns) { + final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int iHeight = pEnd - pStart; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final int jWidth = qEnd - qStart; + blocks[blockIndex] = MathArrays.buildArray(field, iHeight * jWidth); + ++blockIndex; + } + } + + return blocks; + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + return new BlockFieldMatrix(getField(), rowDimension, + columnDimension); + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix copy() { + + // create an empty matrix + BlockFieldMatrix copied = new BlockFieldMatrix(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 add(final FieldMatrix m) + throws MatrixDimensionMismatchException { + try { + return add((BlockFieldMatrix) m); + } catch (ClassCastException cce) { + + // safety check + checkAdditionCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(getField(), rows, columns); + + // perform addition block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + + // perform addition on the current block + final T[] outBlock = out.blocks[blockIndex]; + final T[] tBlock = blocks[blockIndex]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[k].add(m.getEntry(p, q)); + ++k; + } + } + + // go to next block + ++blockIndex; + + } + } + + return out; + } + } + + /** + * Compute the sum of {@code this} and {@code m}. + * + * @param m matrix to be added + * @return {@code this + m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this} + */ + public BlockFieldMatrix add(final BlockFieldMatrix m) + throws MatrixDimensionMismatchException { + + // safety check + checkAdditionCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(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 subtract(final FieldMatrix m) + throws MatrixDimensionMismatchException { + try { + return subtract((BlockFieldMatrix) m); + } catch (ClassCastException cce) { + + // safety check + checkSubtractionCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(getField(), rows, columns); + + // perform subtraction block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + + // perform subtraction on the current block + final T[] outBlock = out.blocks[blockIndex]; + final T[] tBlock = blocks[blockIndex]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[k].subtract(m.getEntry(p, q)); + ++k; + } + } + + // go to next block + ++blockIndex; + + } + } + + return out; + } + } + + /** + * Compute {@code this - m}. + * + * @param m matrix to be subtracted + * @return {@code this - m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this} + */ + public BlockFieldMatrix subtract(final BlockFieldMatrix m) throws MatrixDimensionMismatchException { + // safety check + checkSubtractionCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(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 scalarAdd(final T d) { + final BlockFieldMatrix out = new BlockFieldMatrix(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 scalarMultiply(final T d) { + + final BlockFieldMatrix out = new BlockFieldMatrix(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 multiply(final FieldMatrix m) + throws DimensionMismatchException { + try { + return multiply((BlockFieldMatrix) m); + } catch (ClassCastException cce) { + + // safety check + checkMultiplicationCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(getField(), rows, m.getColumnDimension()); + final T zero = getField().getZero(); + + // perform multiplication block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, m.getColumnDimension()); + + // select current block + final T[] outBlock = out.blocks[blockIndex]; + + // perform multiplication on current block + for (int kBlock = 0; kBlock < blockColumns; ++kBlock) { + final int kWidth = blockWidth(kBlock); + final T[] tBlock = blocks[iBlock * blockColumns + kBlock]; + final int rStart = kBlock * BLOCK_SIZE; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lStart = (p - pStart) * kWidth; + final int lEnd = lStart + kWidth; + for (int q = qStart; q < qEnd; ++q) { + T sum = zero; + int r = rStart; + for (int l = lStart; l < lEnd; ++l) { + sum = sum.add(tBlock[l].multiply(m.getEntry(r, q))); + ++r; + } + outBlock[k] = outBlock[k].add(sum); + ++k; + } + } + } + + // go to next block + ++blockIndex; + + } + } + + return out; + } + } + + /** + * Returns the result of postmultiplying {@code this} by {@code m}. + * + * @param m matrix to postmultiply by + * @return {@code this * m} + * @throws DimensionMismatchException if the matrices are not compatible. + */ + public BlockFieldMatrix multiply(BlockFieldMatrix m) + throws DimensionMismatchException { + + // safety check + checkMultiplicationCompatible(m); + + final BlockFieldMatrix out = new BlockFieldMatrix(getField(), rows, m.columns); + final T zero = getField().getZero(); + + // perform multiplication block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + final int jWidth = out.blockWidth(jBlock); + final int jWidth2 = jWidth + jWidth; + final int jWidth3 = jWidth2 + jWidth; + final int jWidth4 = jWidth3 + jWidth; + + // select current block + final T[] outBlock = out.blocks[blockIndex]; + + // perform multiplication on current block + for (int kBlock = 0; kBlock < blockColumns; ++kBlock) { + final int kWidth = blockWidth(kBlock); + final T[] tBlock = blocks[iBlock * blockColumns + kBlock]; + final T[] mBlock = m.blocks[kBlock * m.blockColumns + jBlock]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lStart = (p - pStart) * kWidth; + final int lEnd = lStart + kWidth; + for (int nStart = 0; nStart < jWidth; ++nStart) { + T sum = zero; + int l = lStart; + int n = nStart; + while (l < lEnd - 3) { + sum = sum. + add(tBlock[l].multiply(mBlock[n])). + add(tBlock[l + 1].multiply(mBlock[n + jWidth])). + add(tBlock[l + 2].multiply(mBlock[n + jWidth2])). + add(tBlock[l + 3].multiply(mBlock[n + jWidth3])); + l += 4; + n += jWidth4; + } + while (l < lEnd) { + sum = sum.add(tBlock[l++].multiply(mBlock[n])); + n += jWidth; + } + outBlock[k] = outBlock[k].add(sum); + ++k; + } + } + } + + // go to next block + ++blockIndex; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public T[][] getData() { + + final T[][] data = MathArrays.buildArray(getField(), getRowDimension(), getColumnDimension()); + final int lastColumns = columns - (blockColumns - 1) * BLOCK_SIZE; + + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + int regularPos = 0; + int lastPos = 0; + for (int p = pStart; p < pEnd; ++p) { + final T[] dataP = data[p]; + int blockIndex = iBlock * blockColumns; + int dataPos = 0; + for (int jBlock = 0; jBlock < blockColumns - 1; ++jBlock) { + System.arraycopy(blocks[blockIndex++], regularPos, dataP, dataPos, BLOCK_SIZE); + dataPos += BLOCK_SIZE; + } + System.arraycopy(blocks[blockIndex], lastPos, dataP, dataPos, lastColumns); + regularPos += BLOCK_SIZE; + lastPos += lastColumns; + } + } + + return data; + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix getSubMatrix(final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + // safety checks + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + + // create the output matrix + final BlockFieldMatrix out = + new BlockFieldMatrix(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 + *

    This method can be called only when the specified part fits in both + * blocks, no verification is done here.

    + * @param srcBlock source block + * @param srcWidth source block width ({@link #BLOCK_SIZE} or smaller) + * @param srcStartRow start row in the source block + * @param srcEndRow end row (exclusive) in the source block + * @param srcStartColumn start column in the source block + * @param srcEndColumn end column (exclusive) in the source block + * @param dstBlock destination block + * @param dstWidth destination block width ({@link #BLOCK_SIZE} or smaller) + * @param dstStartRow start row in the destination block + * @param dstStartColumn start column in the destination block + */ + private void copyBlockPart(final T[] srcBlock, final int srcWidth, + final int srcStartRow, final int srcEndRow, + final int srcStartColumn, final int srcEndColumn, + final T[] dstBlock, final int dstWidth, + final int dstStartRow, final int dstStartColumn) { + final int length = srcEndColumn - srcStartColumn; + int srcPos = srcStartRow * srcWidth + srcStartColumn; + int dstPos = dstStartRow * dstWidth + dstStartColumn; + for (int srcRow = srcStartRow; srcRow < srcEndRow; ++srcRow) { + System.arraycopy(srcBlock, srcPos, dstBlock, dstPos, length); + srcPos += srcWidth; + dstPos += dstWidth; + } + } + + /** {@inheritDoc} */ + @Override + public void setSubMatrix(final T[][] subMatrix, final int row, + final int column) + throws DimensionMismatchException, OutOfRangeException, + NoDataException, NullArgumentException { + // safety checks + MathUtils.checkNotNull(subMatrix); + final int refLength = subMatrix[0].length; + if (refLength == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + final int endRow = row + subMatrix.length - 1; + final int endColumn = column + refLength - 1; + checkSubMatrixIndex(row, endRow, column, endColumn); + for (final T[] subRow : subMatrix) { + if (subRow.length != refLength) { + throw new DimensionMismatchException(refLength, subRow.length); + } + } + + // compute blocks bounds + final int blockStartRow = row / BLOCK_SIZE; + final int blockEndRow = (endRow + BLOCK_SIZE) / BLOCK_SIZE; + final int blockStartColumn = column / BLOCK_SIZE; + final int blockEndColumn = (endColumn + BLOCK_SIZE) / BLOCK_SIZE; + + // perform copy block-wise, to ensure good cache behavior + for (int iBlock = blockStartRow; iBlock < blockEndRow; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final int firstRow = iBlock * BLOCK_SIZE; + final int iStart = FastMath.max(row, firstRow); + final int iEnd = FastMath.min(endRow + 1, firstRow + iHeight); + + for (int jBlock = blockStartColumn; jBlock < blockEndColumn; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int firstColumn = jBlock * BLOCK_SIZE; + final int jStart = FastMath.max(column, firstColumn); + final int jEnd = FastMath.min(endColumn + 1, firstColumn + jWidth); + final int jLength = jEnd - jStart; + + // handle one block, row by row + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = iStart; i < iEnd; ++i) { + System.arraycopy(subMatrix[i - row], jStart - column, + block, (i - firstRow) * jWidth + (jStart - firstColumn), + jLength); + } + + } + } + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix getRowMatrix(final int row) + throws OutOfRangeException { + checkRowIndex(row); + final BlockFieldMatrix out = new BlockFieldMatrix(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 matrix) + throws MatrixDimensionMismatchException, OutOfRangeException { + try { + setRowMatrix(row, (BlockFieldMatrix) matrix); + } catch (ClassCastException cce) { + super.setRowMatrix(row, matrix); + } + } + + /** + * Sets the entries in row number row + * as a row matrix. Row indices start at 0. + * + * @param row the row to be set + * @param matrix row matrix (must have one row and the same number of columns + * as the instance) + * @throws MatrixDimensionMismatchException if the matrix dimensions do + * not match one instance row. + * @throws OutOfRangeException if the specified row index is invalid. + */ + public void setRowMatrix(final int row, final BlockFieldMatrix matrix) + throws MatrixDimensionMismatchException, OutOfRangeException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + if ((matrix.getRowDimension() != 1) || + (matrix.getColumnDimension() != nCols)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + 1, nCols); + } + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int mBlockIndex = 0; + int mIndex = 0; + T[] mBlock = matrix.blocks[mBlockIndex]; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + final int available = mBlock.length - mIndex; + if (jWidth > available) { + System.arraycopy(mBlock, mIndex, block, iRow * jWidth, available); + mBlock = matrix.blocks[++mBlockIndex]; + System.arraycopy(mBlock, 0, block, iRow * jWidth, jWidth - available); + mIndex = jWidth - available; + } else { + System.arraycopy(mBlock, mIndex, block, iRow * jWidth, jWidth); + mIndex += jWidth; + } + } + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix getColumnMatrix(final int column) + throws OutOfRangeException { + checkColumnIndex(column); + final BlockFieldMatrix out = new BlockFieldMatrix(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 matrix) + throws MatrixDimensionMismatchException, OutOfRangeException { + try { + setColumnMatrix(column, (BlockFieldMatrix) matrix); + } catch (ClassCastException cce) { + super.setColumnMatrix(column, matrix); + } + } + + /** + * Sets the entries in column number {@code column} + * as a column matrix. Column indices start at 0. + * + * @param column Column to be set. + * @param matrix Column matrix (must have one column and the same number of rows + * as the instance). + * @throws MatrixDimensionMismatchException if the matrix dimensions do + * not match one instance column. + * @throws OutOfRangeException if the specified column index is invalid. + */ + void setColumnMatrix(final int column, final BlockFieldMatrix matrix) + throws MatrixDimensionMismatchException, OutOfRangeException { + checkColumnIndex(column); + final int nRows = getRowDimension(); + if ((matrix.getRowDimension() != nRows) || + (matrix.getColumnDimension() != 1)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + nRows, 1); + } + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int mBlockIndex = 0; + int mIndex = 0; + T[] mBlock = matrix.blocks[mBlockIndex]; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + if (mIndex >= mBlock.length) { + mBlock = matrix.blocks[++mBlockIndex]; + mIndex = 0; + } + block[i * jWidth + jColumn] = mBlock[mIndex++]; + } + } + } + + /** {@inheritDoc} */ + @Override + public FieldVector getRowVector(final int row) + throws OutOfRangeException { + checkRowIndex(row); + final T[] outData = MathArrays.buildArray(getField(), columns); + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(block, iRow * jWidth, outData, outIndex, jWidth); + outIndex += jWidth; + } + + return new ArrayFieldVector(getField(), outData, false); + } + + /** {@inheritDoc} */ + @Override + public void setRowVector(final int row, final FieldVector vector) + throws MatrixDimensionMismatchException, OutOfRangeException { + try { + setRow(row, ((ArrayFieldVector) vector).getDataRef()); + } catch (ClassCastException cce) { + super.setRowVector(row, vector); + } + } + + /** {@inheritDoc} */ + @Override + public FieldVector getColumnVector(final int column) + throws OutOfRangeException { + checkColumnIndex(column); + final T[] outData = MathArrays.buildArray(getField(), rows); + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + outData[outIndex++] = block[i * jWidth + jColumn]; + } + } + + return new ArrayFieldVector(getField(), outData, false); + } + + /** {@inheritDoc} */ + @Override + public void setColumnVector(final int column, final FieldVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + try { + setColumn(column, ((ArrayFieldVector) vector).getDataRef()); + } catch (ClassCastException cce) { + super.setColumnVector(column, vector); + } + } + + /** {@inheritDoc} */ + @Override + public T[] getRow(final int row) throws OutOfRangeException { + checkRowIndex(row); + final T[] out = MathArrays.buildArray(getField(), columns); + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(block, iRow * jWidth, out, outIndex, jWidth); + outIndex += jWidth; + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setRow(final int row, final T[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + checkRowIndex(row); + final int nCols = getColumnDimension(); + if (array.length != nCols) { + throw new MatrixDimensionMismatchException(1, array.length, 1, nCols); + } + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(array, outIndex, block, iRow * jWidth, jWidth); + outIndex += jWidth; + } + } + + /** {@inheritDoc} */ + @Override + public T[] getColumn(final int column) throws OutOfRangeException { + checkColumnIndex(column); + final T[] out = MathArrays.buildArray(getField(), rows); + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + out[outIndex++] = block[i * jWidth + jColumn]; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setColumn(final int column, final T[] array) + throws MatrixDimensionMismatchException, OutOfRangeException { + checkColumnIndex(column); + final int nRows = getRowDimension(); + if (array.length != nRows) { + throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1); + } + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + block[i * jWidth + jColumn] = array[outIndex++]; + } + } + } + + /** {@inheritDoc} */ + @Override + public T getEntry(final int row, final int column) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + + return blocks[iBlock * blockColumns + jBlock][k]; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(final int row, final int column, final T value) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + + blocks[iBlock * blockColumns + jBlock][k] = value; + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(final int row, final int column, final T increment) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + final T[] blockIJ = blocks[iBlock * blockColumns + jBlock]; + + blockIJ[k] = blockIJ[k].add(increment); + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(final int row, final int column, final T factor) + throws OutOfRangeException { + checkRowIndex(row); + checkColumnIndex(column); + + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + final T[] blockIJ = blocks[iBlock * blockColumns + jBlock]; + + blockIJ[k] = blockIJ[k].multiply(factor); + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix transpose() { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + final BlockFieldMatrix out = new BlockFieldMatrix(getField(), nCols, nRows); + + // perform transpose block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockColumns; ++iBlock) { + for (int jBlock = 0; jBlock < blockRows; ++jBlock) { + + // transpose current block + final T[] outBlock = out.blocks[blockIndex]; + final T[] tBlock = blocks[jBlock * blockColumns + iBlock]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, columns); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, rows); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lInc = pEnd - pStart; + int l = p - pStart; + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[l]; + ++k; + l+= lInc; + } + } + + // go to next block + ++blockIndex; + + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return rows; + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return columns; + } + + /** {@inheritDoc} */ + @Override + public T[] operate(final T[] v) throws DimensionMismatchException { + if (v.length != columns) { + throw new DimensionMismatchException(v.length, columns); + } + final T[] out = MathArrays.buildArray(getField(), rows); + final T zero = getField().getZero(); + + // perform multiplication block-wise, to ensure good cache behavior + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final T[] block = blocks[iBlock * blockColumns + jBlock]; + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + T sum = zero; + int q = qStart; + while (q < qEnd - 3) { + sum = sum. + add(block[k].multiply(v[q])). + add(block[k + 1].multiply(v[q + 1])). + add(block[k + 2].multiply(v[q + 2])). + add(block[k + 3].multiply(v[q + 3])); + k += 4; + q += 4; + } + while (q < qEnd) { + sum = sum.add(block[k++].multiply(v[q++])); + } + out[p] = out[p].add(sum); + } + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public T[] preMultiply(final T[] v) throws DimensionMismatchException { + + if (v.length != rows) { + throw new DimensionMismatchException(v.length, rows); + } + final T[] out = MathArrays.buildArray(getField(), columns); + final T zero = getField().getZero(); + + // perform multiplication block-wise, to ensure good cache behavior + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int jWidth2 = jWidth + jWidth; + final int jWidth3 = jWidth2 + jWidth; + final int jWidth4 = jWidth3 + jWidth; + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final T[] block = blocks[iBlock * blockColumns + jBlock]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int q = qStart; q < qEnd; ++q) { + int k = q - qStart; + T sum = zero; + int p = pStart; + while (p < pEnd - 3) { + sum = sum. + add(block[k].multiply(v[p])). + add(block[k + jWidth].multiply(v[p + 1])). + add(block[k + jWidth2].multiply(v[p + 2])). + add(block[k + jWidth3].multiply(v[p + 3])); + k += jWidth4; + p += 4; + } + while (p < pEnd) { + sum = sum.add(block[k].multiply(v[p++])); + k += jWidth; + } + out[q] = out[q].add(sum); + } + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - pStart) * jWidth; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - pStart) * jWidth; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInRowOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInOptimizedOrder(final FieldMatrixChangingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final T[] block = blocks[blockIndex]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + ++blockIndex; + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final T[] block = blocks[blockIndex]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + ++blockIndex; + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInOptimizedOrder(final FieldMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int p = pStart; p < pEnd; ++p) { + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + checkSubMatrixIndex(startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final T[] block = blocks[iBlock * blockColumns + jBlock]; + for (int p = pStart; p < pEnd; ++p) { + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** + * Get the height of a block. + * @param blockRow row index (in block sense) of the block + * @return height (number of rows) of the block + */ + private int blockHeight(final int blockRow) { + return (blockRow == blockRows - 1) ? rows - blockRow * BLOCK_SIZE : BLOCK_SIZE; + } + + /** + * Get the width of a block. + * @param blockColumn column index (in block sense) of the block + * @return width (number of columns) of the block + */ + private int blockWidth(final int blockColumn) { + return (blockColumn == blockColumns - 1) ? columns - blockColumn * BLOCK_SIZE : BLOCK_SIZE; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockRealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockRealMatrix.java new file mode 100644 index 000000000..88e0ac0eb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/BlockRealMatrix.java @@ -0,0 +1,1581 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Cache-friendly implementation of RealMatrix using a flat arrays to store + * square blocks of the matrix. + *

    + * 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. + *

    + *

    + * 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. + *

    + *

    + * 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}2 elements long for regular blocks. The blocks are themselves + * organized in row major order. + *

    + *

    + * 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. + *

    + *

    + * The layout complexity overhead versus simple mapping of matrices to java + * arrays is negligible for small matrices (about 1%). The gain from cache efficiency leads + * to up to 3-fold improvements for matrices of moderate to large size. + *

    + * @since 2.0 + */ +public class BlockRealMatrix extends AbstractRealMatrix implements Serializable { + /** Block size. */ + public static final int BLOCK_SIZE = 52; + /** Serializable version identifier */ + private static final long serialVersionUID = 4991895511313664478L; + /** Blocks of matrix entries. */ + private final double blocks[][]; + /** Number of rows of the matrix. */ + private final int rows; + /** Number of columns of the matrix. */ + private final int columns; + /** Number of block rows of the matrix. */ + private final int blockRows; + /** Number of block columns of the matrix. */ + private final int blockColumns; + + /** + * Create a new matrix with the supplied row and column dimensions. + * + * @param rows the number of rows in the new matrix + * @param columns the number of columns in the new matrix + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + */ + public BlockRealMatrix(final int rows, final int columns) + throws NotStrictlyPositiveException { + super(rows, columns); + this.rows = rows; + this.columns = columns; + + // number of blocks + blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + // allocate storage blocks, taking care of smaller ones at right and bottom + blocks = createBlocksLayout(rows, columns); + } + + /** + * Create a new dense matrix copying entries from raw layout data. + *

    The input array must already be in raw layout.

    + *

    Calling this constructor is equivalent to call: + *

    matrix = new BlockRealMatrix(rawData.length, rawData[0].length,
    +     *                                   toBlocksLayout(rawData), false);
    + *

    + * + * @param rawData data for new matrix, in raw layout + * @throws DimensionMismatchException if the shape of {@code blockData} is + * inconsistent with block layout. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @see #BlockRealMatrix(int, int, double[][], boolean) + */ + public BlockRealMatrix(final double[][] rawData) + throws DimensionMismatchException, NotStrictlyPositiveException { + this(rawData.length, rawData[0].length, toBlocksLayout(rawData), false); + } + + /** + * Create a new dense matrix copying entries from block layout data. + *

    The input array must already be in blocks layout.

    + * + * @param rows Number of rows in the new matrix. + * @param columns Number of columns in the new matrix. + * @param blockData data for new matrix + * @param copyArray Whether the input array will be copied or referenced. + * @throws DimensionMismatchException if the shape of {@code blockData} is + * inconsistent with block layout. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @see #createBlocksLayout(int, int) + * @see #toBlocksLayout(double[][]) + * @see #BlockRealMatrix(double[][]) + */ + public BlockRealMatrix(final int rows, final int columns, + final double[][] blockData, final boolean copyArray) + throws DimensionMismatchException, NotStrictlyPositiveException { + super(rows, columns); + this.rows = rows; + this.columns = columns; + + // number of blocks + blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (copyArray) { + // allocate storage blocks, taking care of smaller ones at right and bottom + blocks = new double[blockRows * blockColumns][]; + } else { + // reference existing array + blocks = blockData; + } + + int index = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock, ++index) { + if (blockData[index].length != iHeight * blockWidth(jBlock)) { + throw new DimensionMismatchException(blockData[index].length, + iHeight * blockWidth(jBlock)); + } + if (copyArray) { + blocks[index] = blockData[index].clone(); + } + } + } + } + + /** + * Convert a data array from raw layout to blocks layout. + *

    + * Raw layout is the straightforward layout where element at row i and + * column j is in array element rawData[i][j]. 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. + *

    + *

    + * This method creates an array in blocks layout from an input array in raw layout. + * It can be used to provide the array argument of the {@link + * #BlockRealMatrix(int, int, double[][], boolean)} constructor. + *

    + * @param rawData Data array in raw layout. + * @return a new data array containing the same entries but in blocks layout. + * @throws DimensionMismatchException if {@code rawData} is not rectangular. + * @see #createBlocksLayout(int, int) + * @see #BlockRealMatrix(int, int, double[][], boolean) + */ + public static double[][] toBlocksLayout(final double[][] rawData) + throws DimensionMismatchException { + final int rows = rawData.length; + final int columns = rawData[0].length; + final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + // safety checks + for (int i = 0; i < rawData.length; ++i) { + final int length = rawData[i].length; + if (length != columns) { + throw new DimensionMismatchException(columns, length); + } + } + + // convert array + final double[][] blocks = new double[blockRows * blockColumns][]; + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int iHeight = pEnd - pStart; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final int jWidth = qEnd - qStart; + + // allocate new block + final double[] block = new double[iHeight * jWidth]; + blocks[blockIndex] = block; + + // copy data + int index = 0; + for (int p = pStart; p < pEnd; ++p) { + System.arraycopy(rawData[p], qStart, block, index, jWidth); + index += jWidth; + } + ++blockIndex; + } + } + + return blocks; + } + + /** + * Create a data array in blocks layout. + *

    + * This method can be used to create the array argument of the {@link + * #BlockRealMatrix(int, int, double[][], boolean)} constructor. + *

    + * @param rows Number of rows in the new matrix. + * @param columns Number of columns in the new matrix. + * @return a new data array in blocks layout. + * @see #toBlocksLayout(double[][]) + * @see #BlockRealMatrix(int, int, double[][], boolean) + */ + public static double[][] createBlocksLayout(final int rows, final int columns) { + final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE; + final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE; + + final double[][] blocks = new double[blockRows * blockColumns][]; + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int iHeight = pEnd - pStart; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final int jWidth = qEnd - qStart; + blocks[blockIndex] = new double[iHeight * jWidth]; + ++blockIndex; + } + } + + return blocks; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException { + return new BlockRealMatrix(rowDimension, columnDimension); + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix copy() { + // create an empty matrix + BlockRealMatrix copied = new BlockRealMatrix(rows, columns); + + // copy the blocks + for (int i = 0; i < blocks.length; ++i) { + System.arraycopy(blocks[i], 0, copied.blocks[i], 0, blocks[i].length); + } + + return copied; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix add(final RealMatrix m) + throws MatrixDimensionMismatchException { + try { + return add((BlockRealMatrix) m); + } catch (ClassCastException cce) { + // safety check + MatrixUtils.checkAdditionCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform addition block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + + // perform addition on the current block + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[k] + m.getEntry(p, q); + ++k; + } + } + // go to next block + ++blockIndex; + } + } + + return out; + } + } + + /** + * Compute the sum of this matrix and {@code m}. + * + * @param m Matrix to be added. + * @return {@code this} + m. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as this matrix. + */ + public BlockRealMatrix add(final BlockRealMatrix m) + throws MatrixDimensionMismatchException { + // safety check + MatrixUtils.checkAdditionCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform addition block-wise, to ensure good cache behavior + for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) { + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + final double[] mBlock = m.blocks[blockIndex]; + for (int k = 0; k < outBlock.length; ++k) { + outBlock[k] = tBlock[k] + mBlock[k]; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix subtract(final RealMatrix m) + throws MatrixDimensionMismatchException { + try { + return subtract((BlockRealMatrix) m); + } catch (ClassCastException cce) { + // safety check + MatrixUtils.checkSubtractionCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform subtraction block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + + // perform subtraction on the current block + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[k] - m.getEntry(p, q); + ++k; + } + } + // go to next block + ++blockIndex; + } + } + + return out; + } + } + + /** + * Subtract {@code m} from this matrix. + * + * @param m Matrix to be subtracted. + * @return {@code this} - m. + * @throws MatrixDimensionMismatchException if {@code m} is not the + * same size as this matrix. + */ + public BlockRealMatrix subtract(final BlockRealMatrix m) + throws MatrixDimensionMismatchException { + // safety check + MatrixUtils.checkSubtractionCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform subtraction block-wise, to ensure good cache behavior + for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) { + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + final double[] mBlock = m.blocks[blockIndex]; + for (int k = 0; k < outBlock.length; ++k) { + outBlock[k] = tBlock[k] - mBlock[k]; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix scalarAdd(final double d) { + + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform subtraction block-wise, to ensure good cache behavior + for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) { + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + for (int k = 0; k < outBlock.length; ++k) { + outBlock[k] = tBlock[k] + d; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public RealMatrix scalarMultiply(final double d) { + final BlockRealMatrix out = new BlockRealMatrix(rows, columns); + + // perform subtraction block-wise, to ensure good cache behavior + for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) { + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[blockIndex]; + for (int k = 0; k < outBlock.length; ++k) { + outBlock[k] = tBlock[k] * d; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix multiply(final RealMatrix m) + throws DimensionMismatchException { + try { + return multiply((BlockRealMatrix) m); + } catch (ClassCastException cce) { + // safety check + MatrixUtils.checkMultiplicationCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, m.getColumnDimension()); + + // perform multiplication block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, m.getColumnDimension()); + + // select current block + final double[] outBlock = out.blocks[blockIndex]; + + // perform multiplication on current block + for (int kBlock = 0; kBlock < blockColumns; ++kBlock) { + final int kWidth = blockWidth(kBlock); + final double[] tBlock = blocks[iBlock * blockColumns + kBlock]; + final int rStart = kBlock * BLOCK_SIZE; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lStart = (p - pStart) * kWidth; + final int lEnd = lStart + kWidth; + for (int q = qStart; q < qEnd; ++q) { + double sum = 0; + int r = rStart; + for (int l = lStart; l < lEnd; ++l) { + sum += tBlock[l] * m.getEntry(r, q); + ++r; + } + outBlock[k] += sum; + ++k; + } + } + } + // go to next block + ++blockIndex; + } + } + + return out; + } + } + + /** + * Returns the result of postmultiplying this by {@code m}. + * + * @param m Matrix to postmultiply by. + * @return {@code this} * m. + * @throws DimensionMismatchException if the matrices are not compatible. + */ + public BlockRealMatrix multiply(BlockRealMatrix m) + throws DimensionMismatchException { + // safety check + MatrixUtils.checkMultiplicationCompatible(this, m); + + final BlockRealMatrix out = new BlockRealMatrix(rows, m.columns); + + // perform multiplication block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + final int jWidth = out.blockWidth(jBlock); + final int jWidth2 = jWidth + jWidth; + final int jWidth3 = jWidth2 + jWidth; + final int jWidth4 = jWidth3 + jWidth; + + // select current block + final double[] outBlock = out.blocks[blockIndex]; + + // perform multiplication on current block + for (int kBlock = 0; kBlock < blockColumns; ++kBlock) { + final int kWidth = blockWidth(kBlock); + final double[] tBlock = blocks[iBlock * blockColumns + kBlock]; + final double[] mBlock = m.blocks[kBlock * m.blockColumns + jBlock]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lStart = (p - pStart) * kWidth; + final int lEnd = lStart + kWidth; + for (int nStart = 0; nStart < jWidth; ++nStart) { + double sum = 0; + int l = lStart; + int n = nStart; + while (l < lEnd - 3) { + sum += tBlock[l] * mBlock[n] + + tBlock[l + 1] * mBlock[n + jWidth] + + tBlock[l + 2] * mBlock[n + jWidth2] + + tBlock[l + 3] * mBlock[n + jWidth3]; + l += 4; + n += jWidth4; + } + while (l < lEnd) { + sum += tBlock[l++] * mBlock[n]; + n += jWidth; + } + outBlock[k] += sum; + ++k; + } + } + } + // go to next block + ++blockIndex; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public double[][] getData() { + final double[][] data = new double[getRowDimension()][getColumnDimension()]; + final int lastColumns = columns - (blockColumns - 1) * BLOCK_SIZE; + + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + int regularPos = 0; + int lastPos = 0; + for (int p = pStart; p < pEnd; ++p) { + final double[] dataP = data[p]; + int blockIndex = iBlock * blockColumns; + int dataPos = 0; + for (int jBlock = 0; jBlock < blockColumns - 1; ++jBlock) { + System.arraycopy(blocks[blockIndex++], regularPos, dataP, dataPos, BLOCK_SIZE); + dataPos += BLOCK_SIZE; + } + System.arraycopy(blocks[blockIndex], lastPos, dataP, dataPos, lastColumns); + regularPos += BLOCK_SIZE; + lastPos += lastColumns; + } + } + + return data; + } + + /** {@inheritDoc} */ + @Override + public double getNorm() { + final double[] colSums = new double[BLOCK_SIZE]; + double maxColSum = 0; + for (int jBlock = 0; jBlock < blockColumns; jBlock++) { + final int jWidth = blockWidth(jBlock); + Arrays.fill(colSums, 0, jWidth, 0.0); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int j = 0; j < jWidth; ++j) { + double sum = 0; + for (int i = 0; i < iHeight; ++i) { + sum += FastMath.abs(block[i * jWidth + j]); + } + colSums[j] += sum; + } + } + for (int j = 0; j < jWidth; ++j) { + maxColSum = FastMath.max(maxColSum, colSums[j]); + } + } + return maxColSum; + } + + /** {@inheritDoc} */ + @Override + public double getFrobeniusNorm() { + double sum2 = 0; + for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) { + for (final double entry : blocks[blockIndex]) { + sum2 += entry * entry; + } + } + return FastMath.sqrt(sum2); + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix getSubMatrix(final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + // safety checks + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + + // create the output matrix + final BlockRealMatrix out = + new BlockRealMatrix(endRow - startRow + 1, endColumn - startColumn + 1); + + // compute blocks shifts + final int blockStartRow = startRow / BLOCK_SIZE; + final int rowsShift = startRow % BLOCK_SIZE; + final int blockStartColumn = startColumn / BLOCK_SIZE; + final int columnsShift = startColumn % BLOCK_SIZE; + + // perform extraction block-wise, to ensure good cache behavior + int pBlock = blockStartRow; + for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) { + final int iHeight = out.blockHeight(iBlock); + int qBlock = blockStartColumn; + for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) { + final int jWidth = out.blockWidth(jBlock); + + // handle one block of the output matrix + final int outIndex = iBlock * out.blockColumns + jBlock; + final double[] outBlock = out.blocks[outIndex]; + final int index = pBlock * blockColumns + qBlock; + final int width = blockWidth(qBlock); + + final int heightExcess = iHeight + rowsShift - BLOCK_SIZE; + final int widthExcess = jWidth + columnsShift - BLOCK_SIZE; + if (heightExcess > 0) { + // the submatrix block spans on two blocks rows from the original matrix + if (widthExcess > 0) { + // the submatrix block spans on two blocks columns from the original matrix + final int width2 = blockWidth(qBlock + 1); + copyBlockPart(blocks[index], width, + rowsShift, BLOCK_SIZE, + columnsShift, BLOCK_SIZE, + outBlock, jWidth, 0, 0); + copyBlockPart(blocks[index + 1], width2, + rowsShift, BLOCK_SIZE, + 0, widthExcess, + outBlock, jWidth, 0, jWidth - widthExcess); + copyBlockPart(blocks[index + blockColumns], width, + 0, heightExcess, + columnsShift, BLOCK_SIZE, + outBlock, jWidth, iHeight - heightExcess, 0); + copyBlockPart(blocks[index + blockColumns + 1], width2, + 0, heightExcess, + 0, widthExcess, + outBlock, jWidth, iHeight - heightExcess, jWidth - widthExcess); + } else { + // the submatrix block spans on one block column from the original matrix + copyBlockPart(blocks[index], width, + rowsShift, BLOCK_SIZE, + columnsShift, jWidth + columnsShift, + outBlock, jWidth, 0, 0); + copyBlockPart(blocks[index + blockColumns], width, + 0, heightExcess, + columnsShift, jWidth + columnsShift, + outBlock, jWidth, iHeight - heightExcess, 0); + } + } else { + // the submatrix block spans on one block row from the original matrix + if (widthExcess > 0) { + // the submatrix block spans on two blocks columns from the original matrix + final int width2 = blockWidth(qBlock + 1); + copyBlockPart(blocks[index], width, + rowsShift, iHeight + rowsShift, + columnsShift, BLOCK_SIZE, + outBlock, jWidth, 0, 0); + copyBlockPart(blocks[index + 1], width2, + rowsShift, iHeight + rowsShift, + 0, widthExcess, + outBlock, jWidth, 0, jWidth - widthExcess); + } else { + // the submatrix block spans on one block column from the original matrix + copyBlockPart(blocks[index], width, + rowsShift, iHeight + rowsShift, + columnsShift, jWidth + columnsShift, + outBlock, jWidth, 0, 0); + } + } + ++qBlock; + } + ++pBlock; + } + + return out; + } + + /** + * Copy a part of a block into another one + *

    This method can be called only when the specified part fits in both + * blocks, no verification is done here.

    + * @param srcBlock source block + * @param srcWidth source block width ({@link #BLOCK_SIZE} or smaller) + * @param srcStartRow start row in the source block + * @param srcEndRow end row (exclusive) in the source block + * @param srcStartColumn start column in the source block + * @param srcEndColumn end column (exclusive) in the source block + * @param dstBlock destination block + * @param dstWidth destination block width ({@link #BLOCK_SIZE} or smaller) + * @param dstStartRow start row in the destination block + * @param dstStartColumn start column in the destination block + */ + private void copyBlockPart(final double[] srcBlock, final int srcWidth, + final int srcStartRow, final int srcEndRow, + final int srcStartColumn, final int srcEndColumn, + final double[] dstBlock, final int dstWidth, + final int dstStartRow, final int dstStartColumn) { + final int length = srcEndColumn - srcStartColumn; + int srcPos = srcStartRow * srcWidth + srcStartColumn; + int dstPos = dstStartRow * dstWidth + dstStartColumn; + for (int srcRow = srcStartRow; srcRow < srcEndRow; ++srcRow) { + System.arraycopy(srcBlock, srcPos, dstBlock, dstPos, length); + srcPos += srcWidth; + dstPos += dstWidth; + } + } + + /** {@inheritDoc} */ + @Override + public void setSubMatrix(final double[][] subMatrix, final int row, + final int column) + throws OutOfRangeException, NoDataException, NullArgumentException, + DimensionMismatchException { + // safety checks + MathUtils.checkNotNull(subMatrix); + final int refLength = subMatrix[0].length; + if (refLength == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + final int endRow = row + subMatrix.length - 1; + final int endColumn = column + refLength - 1; + MatrixUtils.checkSubMatrixIndex(this, row, endRow, column, endColumn); + for (final double[] subRow : subMatrix) { + if (subRow.length != refLength) { + throw new DimensionMismatchException(refLength, subRow.length); + } + } + + // compute blocks bounds + final int blockStartRow = row / BLOCK_SIZE; + final int blockEndRow = (endRow + BLOCK_SIZE) / BLOCK_SIZE; + final int blockStartColumn = column / BLOCK_SIZE; + final int blockEndColumn = (endColumn + BLOCK_SIZE) / BLOCK_SIZE; + + // perform copy block-wise, to ensure good cache behavior + for (int iBlock = blockStartRow; iBlock < blockEndRow; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final int firstRow = iBlock * BLOCK_SIZE; + final int iStart = FastMath.max(row, firstRow); + final int iEnd = FastMath.min(endRow + 1, firstRow + iHeight); + + for (int jBlock = blockStartColumn; jBlock < blockEndColumn; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int firstColumn = jBlock * BLOCK_SIZE; + final int jStart = FastMath.max(column, firstColumn); + final int jEnd = FastMath.min(endColumn + 1, firstColumn + jWidth); + final int jLength = jEnd - jStart; + + // handle one block, row by row + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = iStart; i < iEnd; ++i) { + System.arraycopy(subMatrix[i - row], jStart - column, + block, (i - firstRow) * jWidth + (jStart - firstColumn), + jLength); + } + + } + } + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix getRowMatrix(final int row) + throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + final BlockRealMatrix out = new BlockRealMatrix(1, columns); + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outBlockIndex = 0; + int outIndex = 0; + double[] outBlock = out.blocks[outBlockIndex]; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + final int available = outBlock.length - outIndex; + if (jWidth > available) { + System.arraycopy(block, iRow * jWidth, outBlock, outIndex, available); + outBlock = out.blocks[++outBlockIndex]; + System.arraycopy(block, iRow * jWidth, outBlock, 0, jWidth - available); + outIndex = jWidth - available; + } else { + System.arraycopy(block, iRow * jWidth, outBlock, outIndex, jWidth); + outIndex += jWidth; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setRowMatrix(final int row, final RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + try { + setRowMatrix(row, (BlockRealMatrix) matrix); + } catch (ClassCastException cce) { + super.setRowMatrix(row, matrix); + } + } + + /** + * Sets the entries in row number row + * as a row matrix. Row indices start at 0. + * + * @param row the row to be set + * @param matrix row matrix (must have one row and the same number of columns + * as the instance) + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the matrix dimensions do + * not match one instance row. + */ + public void setRowMatrix(final int row, final BlockRealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + if ((matrix.getRowDimension() != 1) || + (matrix.getColumnDimension() != nCols)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + 1, nCols); + } + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int mBlockIndex = 0; + int mIndex = 0; + double[] mBlock = matrix.blocks[mBlockIndex]; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + final int available = mBlock.length - mIndex; + if (jWidth > available) { + System.arraycopy(mBlock, mIndex, block, iRow * jWidth, available); + mBlock = matrix.blocks[++mBlockIndex]; + System.arraycopy(mBlock, 0, block, iRow * jWidth, jWidth - available); + mIndex = jWidth - available; + } else { + System.arraycopy(mBlock, mIndex, block, iRow * jWidth, jWidth); + mIndex += jWidth; + } + } + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix getColumnMatrix(final int column) + throws OutOfRangeException { + MatrixUtils.checkColumnIndex(this, column); + final BlockRealMatrix out = new BlockRealMatrix(rows, 1); + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outBlockIndex = 0; + int outIndex = 0; + double[] outBlock = out.blocks[outBlockIndex]; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + if (outIndex >= outBlock.length) { + outBlock = out.blocks[++outBlockIndex]; + outIndex = 0; + } + outBlock[outIndex++] = block[i * jWidth + jColumn]; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setColumnMatrix(final int column, final RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + try { + setColumnMatrix(column, (BlockRealMatrix) matrix); + } catch (ClassCastException cce) { + super.setColumnMatrix(column, matrix); + } + } + + /** + * Sets the entries in column number column + * as a column matrix. Column indices start at 0. + * + * @param column the column to be set + * @param matrix column matrix (must have one column and the same number of rows + * as the instance) + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the matrix dimensions do + * not match one instance column. + */ + void setColumnMatrix(final int column, final BlockRealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + if ((matrix.getRowDimension() != nRows) || + (matrix.getColumnDimension() != 1)) { + throw new MatrixDimensionMismatchException(matrix.getRowDimension(), + matrix.getColumnDimension(), + nRows, 1); + } + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int mBlockIndex = 0; + int mIndex = 0; + double[] mBlock = matrix.blocks[mBlockIndex]; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + if (mIndex >= mBlock.length) { + mBlock = matrix.blocks[++mBlockIndex]; + mIndex = 0; + } + block[i * jWidth + jColumn] = mBlock[mIndex++]; + } + } + } + + /** {@inheritDoc} */ + @Override + public RealVector getRowVector(final int row) + throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + final double[] outData = new double[columns]; + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(block, iRow * jWidth, outData, outIndex, jWidth); + outIndex += jWidth; + } + + return new ArrayRealVector(outData, false); + } + + /** {@inheritDoc} */ + @Override + public void setRowVector(final int row, final RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + try { + setRow(row, ((ArrayRealVector) vector).getDataRef()); + } catch (ClassCastException cce) { + super.setRowVector(row, vector); + } + } + + /** {@inheritDoc} */ + @Override + public RealVector getColumnVector(final int column) + throws OutOfRangeException { + MatrixUtils.checkColumnIndex(this, column); + final double[] outData = new double[rows]; + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + outData[outIndex++] = block[i * jWidth + jColumn]; + } + } + + return new ArrayRealVector(outData, false); + } + + /** {@inheritDoc} */ + @Override + public void setColumnVector(final int column, final RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException { + try { + setColumn(column, ((ArrayRealVector) vector).getDataRef()); + } catch (ClassCastException cce) { + super.setColumnVector(column, vector); + } + } + + /** {@inheritDoc} */ + @Override + public double[] getRow(final int row) throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + final double[] out = new double[columns]; + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(block, iRow * jWidth, out, outIndex, jWidth); + outIndex += jWidth; + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setRow(final int row, final double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkRowIndex(this, row); + final int nCols = getColumnDimension(); + if (array.length != nCols) { + throw new MatrixDimensionMismatchException(1, array.length, 1, nCols); + } + + // perform copy block-wise, to ensure good cache behavior + final int iBlock = row / BLOCK_SIZE; + final int iRow = row - iBlock * BLOCK_SIZE; + int outIndex = 0; + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + System.arraycopy(array, outIndex, block, iRow * jWidth, jWidth); + outIndex += jWidth; + } + } + + /** {@inheritDoc} */ + @Override + public double[] getColumn(final int column) throws OutOfRangeException { + MatrixUtils.checkColumnIndex(this, column); + final double[] out = new double[rows]; + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + out[outIndex++] = block[i * jWidth + jColumn]; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public void setColumn(final int column, final double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException { + MatrixUtils.checkColumnIndex(this, column); + final int nRows = getRowDimension(); + if (array.length != nRows) { + throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1); + } + + // perform copy block-wise, to ensure good cache behavior + final int jBlock = column / BLOCK_SIZE; + final int jColumn = column - jBlock * BLOCK_SIZE; + final int jWidth = blockWidth(jBlock); + int outIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int iHeight = blockHeight(iBlock); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int i = 0; i < iHeight; ++i) { + block[i * jWidth + jColumn] = array[outIndex++]; + } + } + } + + /** {@inheritDoc} */ + @Override + public double getEntry(final int row, final int column) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + return blocks[iBlock * blockColumns + jBlock][k]; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(final int row, final int column, final double value) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + blocks[iBlock * blockColumns + jBlock][k] = value; + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(final int row, final int column, + final double increment) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + blocks[iBlock * blockColumns + jBlock][k] += increment; + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(final int row, final int column, + final double factor) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + final int iBlock = row / BLOCK_SIZE; + final int jBlock = column / BLOCK_SIZE; + final int k = (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + + (column - jBlock * BLOCK_SIZE); + blocks[iBlock * blockColumns + jBlock][k] *= factor; + } + + /** {@inheritDoc} */ + @Override + public BlockRealMatrix transpose() { + final int nRows = getRowDimension(); + final int nCols = getColumnDimension(); + final BlockRealMatrix out = new BlockRealMatrix(nCols, nRows); + + // perform transpose block-wise, to ensure good cache behavior + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockColumns; ++iBlock) { + for (int jBlock = 0; jBlock < blockRows; ++jBlock) { + // transpose current block + final double[] outBlock = out.blocks[blockIndex]; + final double[] tBlock = blocks[jBlock * blockColumns + iBlock]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, columns); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, rows); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + final int lInc = pEnd - pStart; + int l = p - pStart; + for (int q = qStart; q < qEnd; ++q) { + outBlock[k] = tBlock[l]; + ++k; + l+= lInc; + } + } + // go to next block + ++blockIndex; + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return rows; + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return columns; + } + + /** {@inheritDoc} */ + @Override + public double[] operate(final double[] v) + throws DimensionMismatchException { + if (v.length != columns) { + throw new DimensionMismatchException(v.length, columns); + } + final double[] out = new double[rows]; + + // perform multiplication block-wise, to ensure good cache behavior + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final double[] block = blocks[iBlock * blockColumns + jBlock]; + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + double sum = 0; + int q = qStart; + while (q < qEnd - 3) { + sum += block[k] * v[q] + + block[k + 1] * v[q + 1] + + block[k + 2] * v[q + 2] + + block[k + 3] * v[q + 3]; + k += 4; + q += 4; + } + while (q < qEnd) { + sum += block[k++] * v[q++]; + } + out[p] += sum; + } + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public double[] preMultiply(final double[] v) + throws DimensionMismatchException { + if (v.length != rows) { + throw new DimensionMismatchException(v.length, rows); + } + final double[] out = new double[columns]; + + // perform multiplication block-wise, to ensure good cache behavior + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int jWidth2 = jWidth + jWidth; + final int jWidth3 = jWidth2 + jWidth; + final int jWidth4 = jWidth3 + jWidth; + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final double[] block = blocks[iBlock * blockColumns + jBlock]; + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int q = qStart; q < qEnd; ++q) { + int k = q - qStart; + double sum = 0; + int p = pStart; + while (p < pEnd - 3) { + sum += block[k] * v[p] + + block[k + jWidth] * v[p + 1] + + block[k + jWidth2] * v[p + 2] + + block[k + jWidth3] * v[p + 3]; + k += jWidth4; + p += 4; + } + while (p < pEnd) { + sum += block[k] * v[p++]; + k += jWidth; + } + out[q] += sum; + } + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixChangingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - pStart) * jWidth; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - pStart) * jWidth; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInRowOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int p = pStart; p < pEnd; ++p) { + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final double[] block = blocks[blockIndex]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + ++blockIndex; + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor) { + visitor.start(rows, columns, 0, rows - 1, 0, columns - 1); + int blockIndex = 0; + for (int iBlock = 0; iBlock < blockRows; ++iBlock) { + final int pStart = iBlock * BLOCK_SIZE; + final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows); + for (int jBlock = 0; jBlock < blockColumns; ++jBlock) { + final int qStart = jBlock * BLOCK_SIZE; + final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns); + final double[] block = blocks[blockIndex]; + int k = 0; + for (int p = pStart; p < pEnd; ++p) { + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + ++blockIndex; + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int p = pStart; p < pEnd; ++p) { + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + block[k] = visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** {@inheritDoc} */ + @Override + public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor, + final int startRow, final int endRow, + final int startColumn, + final int endColumn) + throws OutOfRangeException, NumberIsTooSmallException { + MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn); + visitor.start(rows, columns, startRow, endRow, startColumn, endColumn); + for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) { + final int p0 = iBlock * BLOCK_SIZE; + final int pStart = FastMath.max(startRow, p0); + final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow); + for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) { + final int jWidth = blockWidth(jBlock); + final int q0 = jBlock * BLOCK_SIZE; + final int qStart = FastMath.max(startColumn, q0); + final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn); + final double[] block = blocks[iBlock * blockColumns + jBlock]; + for (int p = pStart; p < pEnd; ++p) { + int k = (p - p0) * jWidth + qStart - q0; + for (int q = qStart; q < qEnd; ++q) { + visitor.visit(p, q, block[k]); + ++k; + } + } + } + } + return visitor.end(); + } + + /** + * Get the height of a block. + * @param blockRow row index (in block sense) of the block + * @return height (number of rows) of the block + */ + private int blockHeight(final int blockRow) { + return (blockRow == blockRows - 1) ? rows - blockRow * BLOCK_SIZE : BLOCK_SIZE; + } + + /** + * Get the width of a block. + * @param blockColumn column index (in block sense) of the block + * @return width (number of columns) of the block + */ + private int blockWidth(final int blockColumn) { + return (blockColumn == blockColumns - 1) ? columns - blockColumn * BLOCK_SIZE : BLOCK_SIZE; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/CholeskyDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/CholeskyDecomposition.java new file mode 100644 index 000000000..4cf94f35d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/CholeskyDecomposition.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Calculates the Cholesky decomposition of a matrix. + *

    The Cholesky decomposition of a real symmetric positive-definite + * matrix A consists of a lower triangular matrix L with same size such + * that: A = LLT. In a sense, this is the square root of A.

    + *

    This class is based on the class with similar name from the + * JAMA library, with the + * following changes:

    + *
      + *
    • a {@link #getLT() getLT} method has been added,
    • + *
    • the {@code isspd} method has been removed, since the constructor of + * this class throws a {@link NonPositiveDefiniteMatrixException} when a + * matrix cannot be decomposed,
    • + *
    • a {@link #getDeterminant() getDeterminant} method has been added,
    • + *
    • the {@code solve} method has been replaced by a {@link #getSolver() + * getSolver} method and the equivalent method provided by the returned + * {@link DecompositionSolver}.
    • + *
    + * + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class CholeskyDecomposition { + /** + * Default threshold above which off-diagonal elements are considered too different + * and matrix not symmetric. + */ + public static final double DEFAULT_RELATIVE_SYMMETRY_THRESHOLD = 1.0e-15; + /** + * Default threshold below which diagonal elements are considered null + * and matrix not positive definite. + */ + public static final double DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD = 1.0e-10; + /** Row-oriented storage for LT 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. + *

    + * Calling this constructor is equivalent to call {@link + * #CholeskyDecomposition(RealMatrix, double, double)} with the + * thresholds set to the default values {@link + * #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD} and {@link + * #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD} + *

    + * @param matrix the matrix to decompose + * @throws NonSquareMatrixException if the matrix is not square. + * @throws NonSymmetricMatrixException if the matrix is not symmetric. + * @throws NonPositiveDefiniteMatrixException if the matrix is not + * strictly positive definite. + * @see #CholeskyDecomposition(RealMatrix, double, double) + * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD + * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD + */ + public CholeskyDecomposition(final RealMatrix matrix) { + this(matrix, DEFAULT_RELATIVE_SYMMETRY_THRESHOLD, + DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD); + } + + /** + * Calculates the Cholesky decomposition of the given matrix. + * @param matrix the matrix to decompose + * @param relativeSymmetryThreshold threshold above which off-diagonal + * elements are considered too different and matrix not symmetric + * @param absolutePositivityThreshold threshold below which diagonal + * elements are considered null and matrix not positive definite + * @throws NonSquareMatrixException if the matrix is not square. + * @throws NonSymmetricMatrixException if the matrix is not symmetric. + * @throws NonPositiveDefiniteMatrixException if the matrix is not + * strictly positive definite. + * @see #CholeskyDecomposition(RealMatrix) + * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD + * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD + */ + public CholeskyDecomposition(final RealMatrix matrix, + final double relativeSymmetryThreshold, + final double absolutePositivityThreshold) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + final int order = matrix.getRowDimension(); + lTData = matrix.getData(); + cachedL = null; + cachedLT = null; + + // check the matrix before transformation + for (int i = 0; i < order; ++i) { + final double[] lI = lTData[i]; + + // check off-diagonal elements (and reset them to 0) + for (int j = i + 1; j < order; ++j) { + final double[] lJ = lTData[j]; + final double lIJ = lI[j]; + final double lJI = lJ[i]; + final double maxDelta = + relativeSymmetryThreshold * FastMath.max(FastMath.abs(lIJ), FastMath.abs(lJI)); + if (FastMath.abs(lIJ - lJI) > maxDelta) { + throw new NonSymmetricMatrixException(i, j, relativeSymmetryThreshold); + } + lJ[i] = 0; + } + } + + // transform the matrix + for (int i = 0; i < order; ++i) { + + final double[] ltI = lTData[i]; + + // check diagonal element + if (ltI[i] <= absolutePositivityThreshold) { + throw new NonPositiveDefiniteMatrixException(ltI[i], i, absolutePositivityThreshold); + } + + ltI[i] = FastMath.sqrt(ltI[i]); + final double inverse = 1.0 / ltI[i]; + + for (int q = order - 1; q > i; --q) { + ltI[q] *= inverse; + final double[] ltQ = lTData[q]; + for (int p = q; p < order; ++p) { + ltQ[p] -= ltI[q] * ltI[p]; + } + } + } + } + + /** + * Returns the matrix L of the decomposition. + *

    L is an lower-triangular matrix

    + * @return the L matrix + */ + public RealMatrix getL() { + if (cachedL == null) { + cachedL = getLT().transpose(); + } + return cachedL; + } + + /** + * Returns the transpose of the matrix L of the decomposition. + *

    LT is an upper-triangular matrix

    + * @return the transpose of the matrix L of the decomposition + */ + public RealMatrix getLT() { + + if (cachedLT == null) { + cachedLT = MatrixUtils.createRealMatrix(lTData); + } + + // return the cached matrix + return cachedLT; + } + + /** + * Return the determinant of the matrix + * @return determinant of the matrix + */ + public double getDeterminant() { + double determinant = 1.0; + for (int i = 0; i < lTData.length; ++i) { + double lTii = lTData[i][i]; + determinant *= lTii * lTii; + } + return determinant; + } + + /** + * Get a solver for finding the A × X = B solution in least square sense. + * @return a solver + */ + public DecompositionSolver getSolver() { + return new Solver(lTData); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + /** Row-oriented storage for LT matrix data. */ + private final double[][] lTData; + + /** + * Build a solver from decomposed matrix. + * @param lTData row-oriented storage for LT matrix data + */ + private Solver(final double[][] lTData) { + this.lTData = lTData; + } + + /** {@inheritDoc} */ + public boolean isNonSingular() { + // if we get this far, the matrix was positive definite, hence non-singular + return true; + } + + /** {@inheritDoc} */ + public RealVector solve(final RealVector b) { + final int m = lTData.length; + if (b.getDimension() != m) { + throw new DimensionMismatchException(b.getDimension(), m); + } + + final double[] x = b.toArray(); + + // Solve LY = b + for (int j = 0; j < m; j++) { + final double[] lJ = lTData[j]; + x[j] /= lJ[j]; + final double xJ = x[j]; + for (int i = j + 1; i < m; i++) { + x[i] -= xJ * lJ[i]; + } + } + + // Solve LTX = Y + for (int j = m - 1; j >= 0; j--) { + x[j] /= lTData[j][j]; + final double xJ = x[j]; + for (int i = 0; i < j; i++) { + x[i] -= xJ * lTData[i][j]; + } + } + + return new ArrayRealVector(x, false); + } + + /** {@inheritDoc} */ + public RealMatrix solve(RealMatrix b) { + final int m = lTData.length; + if (b.getRowDimension() != m) { + throw new DimensionMismatchException(b.getRowDimension(), m); + } + + final int nColB = b.getColumnDimension(); + final double[][] x = b.getData(); + + // Solve LY = b + for (int j = 0; j < m; j++) { + final double[] lJ = lTData[j]; + final double lJJ = lJ[j]; + final double[] xJ = x[j]; + for (int k = 0; k < nColB; ++k) { + xJ[k] /= lJJ; + } + for (int i = j + 1; i < m; i++) { + final double[] xI = x[i]; + final double lJI = lJ[i]; + for (int k = 0; k < nColB; ++k) { + xI[k] -= xJ[k] * lJI; + } + } + } + + // Solve LTX = Y + for (int j = m - 1; j >= 0; j--) { + final double lJJ = lTData[j][j]; + final double[] xJ = x[j]; + for (int k = 0; k < nColB; ++k) { + xJ[k] /= lJJ; + } + for (int i = 0; i < j; i++) { + final double[] xI = x[i]; + final double lIJ = lTData[i][j]; + for (int k = 0; k < nColB; ++k) { + xI[k] -= xJ[k] * lIJ; + } + } + } + + return new Array2DRowRealMatrix(x); + } + + /** + * Get the inverse of the decomposed matrix. + * + * @return the inverse matrix. + */ + public RealMatrix getInverse() { + return solve(MatrixUtils.createRealIdentityMatrix(lTData.length)); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ConjugateGradient.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ConjugateGradient.java new file mode 100644 index 000000000..519c10f58 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/ConjugateGradient.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.util.IterationManager; + +/** + *

    + * This is an implementation of the conjugate gradient method for + * {@link RealLinearOperator}. It follows closely the template by Barrett et al. (1994) (figure 2.5). The linear system at + * hand is A · x = b, and the residual is r = b - A · x. + *

    + *

    Default stopping criterion

    + *

    + * A default stopping criterion is implemented. The iterations stop when || r || + * ≤ δ || b ||, where b is the right-hand side vector, r the current + * estimate of the residual, and δ a user-specified tolerance. It should + * be noted that r is the so-called updated residual, which might + * differ from the true residual due to rounding-off errors (see e.g. Strakos and Tichy, 2002). + *

    + *

    Iteration count

    + *

    + * In the present context, an iteration should be understood as one evaluation + * of the matrix-vector product A · x. The initialization phase therefore + * counts as one iteration. + *

    + *

    Exception context

    + *

    + * Besides standard {@link DimensionMismatchException}, this class might throw + * {@link NonPositiveDefiniteOperatorException} if the linear operator or + * the preconditioner are not positive definite. In this case, the + * {@link ExceptionContext} provides some more information + *

      + *
    • key {@code "operator"} points to the offending linear operator, say L,
    • + *
    • key {@code "vector"} points to the offending vector, say x, such that + * xT · L · x < 0.
    • + *
    + *

    + *

    References

    + *
    + *
    Barret et al. (1994)
    + *
    R. Barrett, M. Berry, T. F. Chan, J. Demmel, J. M. Donato, J. Dongarra, + * V. Eijkhout, R. Pozo, C. Romine and H. Van der Vorst, + * + * Templates for the Solution of Linear Systems: Building Blocks for Iterative + * Methods, SIAM
    + *
    Strakos and Tichy (2002) + *
    + *
    Z. Strakos and P. Tichy, + * On error estimation in the conjugate gradient method and why it works + * in finite precision computations, Electronic Transactions on + * Numerical Analysis 13: 56-80, 2002
    + *
    + * + * @since 3.0 + */ +public class ConjugateGradient + extends PreconditionedIterativeLinearSolver { + + /** Key for the exception context. */ + public static final String OPERATOR = "operator"; + + /** Key for the exception context. */ + public static final String VECTOR = "vector"; + + /** + * {@code true} if positive-definiteness of matrix and preconditioner should + * be checked. + */ + private boolean check; + + /** The value of δ, for the default stopping criterion. */ + private final double delta; + + /** + * Creates a new instance of this class, with default + * stopping criterion. + * + * @param maxIterations the maximum number of iterations + * @param delta the δ parameter for the default stopping criterion + * @param check {@code true} if positive definiteness of both matrix and + * preconditioner should be checked + */ + public ConjugateGradient(final int maxIterations, final double delta, + final boolean check) { + super(maxIterations); + this.delta = delta; + this.check = check; + } + + /** + * Creates a new instance of this class, with default + * stopping criterion and custom iteration manager. + * + * @param manager the custom iteration manager + * @param delta the δ parameter for the default stopping criterion + * @param check {@code true} if positive definiteness of both matrix and + * preconditioner should be checked + * @throws NullArgumentException if {@code manager} is {@code null} + */ + public ConjugateGradient(final IterationManager manager, + final double delta, final boolean check) + throws NullArgumentException { + super(manager); + this.delta = delta; + this.check = check; + } + + /** + * Returns {@code true} if positive-definiteness should be checked for both + * matrix and preconditioner. + * + * @return {@code true} if the tests are to be performed + */ + public final boolean getCheck() { + return check; + } + + /** + * {@inheritDoc} + * + * @throws NonPositiveDefiniteOperatorException if {@code a} or {@code m} is + * not positive definite + */ + @Override + public RealVector solveInPlace(final RealLinearOperator a, + final RealLinearOperator m, + final RealVector b, + final RealVector x0) + throws NullArgumentException, NonPositiveDefiniteOperatorException, + NonSquareOperatorException, DimensionMismatchException, + MaxCountExceededException { + checkParameters(a, m, b, x0); + final IterationManager manager = getIterationManager(); + // Initialization of default stopping criterion + manager.resetIterationCount(); + final double rmax = delta * b.getNorm(); + final RealVector bro = RealVector.unmodifiableRealVector(b); + + // Initialization phase counts as one iteration. + manager.incrementIterationCount(); + // p and x are constructed as copies of x0, since presumably, the type + // of x is optimized for the calculation of the matrix-vector product + // A.x. + final RealVector x = x0; + final RealVector xro = RealVector.unmodifiableRealVector(x); + final RealVector p = x.copy(); + RealVector q = a.operate(p); + + final RealVector r = b.combine(1, -1, q); + final RealVector rro = RealVector.unmodifiableRealVector(r); + double rnorm = r.getNorm(); + RealVector z; + if (m == null) { + z = r; + } else { + z = null; + } + IterativeLinearSolverEvent evt; + evt = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), xro, bro, rro, rnorm); + manager.fireInitializationEvent(evt); + if (rnorm <= rmax) { + manager.fireTerminationEvent(evt); + return x; + } + double rhoPrev = 0.; + while (true) { + manager.incrementIterationCount(); + evt = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), xro, bro, rro, rnorm); + manager.fireIterationStartedEvent(evt); + if (m != null) { + z = m.operate(r); + } + final double rhoNext = r.dotProduct(z); + if (check && (rhoNext <= 0.)) { + final NonPositiveDefiniteOperatorException e; + e = new NonPositiveDefiniteOperatorException(); + final ExceptionContext context = e.getContext(); + context.setValue(OPERATOR, m); + context.setValue(VECTOR, r); + throw e; + } + if (manager.getIterations() == 2) { + p.setSubVector(0, z); + } else { + p.combineToSelf(rhoNext / rhoPrev, 1., z); + } + q = a.operate(p); + final double pq = p.dotProduct(q); + if (check && (pq <= 0.)) { + final NonPositiveDefiniteOperatorException e; + e = new NonPositiveDefiniteOperatorException(); + final ExceptionContext context = e.getContext(); + context.setValue(OPERATOR, a); + context.setValue(VECTOR, p); + throw e; + } + final double alpha = rhoNext / pq; + x.combineToSelf(1., alpha, p); + r.combineToSelf(1., -alpha, q); + rhoPrev = rhoNext; + rnorm = r.getNorm(); + evt = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), xro, bro, rro, rnorm); + manager.fireIterationPerformedEvent(evt); + if (rnorm <= rmax) { + manager.fireTerminationEvent(evt); + return x; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DecompositionSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DecompositionSolver.java new file mode 100644 index 000000000..0888b8f3c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DecompositionSolver.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * Interface handling decomposition algorithms that can solve A × X = B. + *

    + * 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. + *

    + * 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. + * + * @since 2.0 + */ +public interface DecompositionSolver { + + /** + * Solve the linear equation A × X = B for matrices A. + *

    + * The A matrix is implicit, it is provided by the underlying + * decomposition algorithm. + * + * @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 DimensionMismatchException + * if the matrices dimensions do not match. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + RealVector solve(final RealVector b) throws SingularMatrixException; + + /** + * Solve the linear equation A × X = B for matrices A. + *

    + * The A matrix is implicit, it is provided by the underlying + * decomposition algorithm. + * + * @param b right-hand side of the equation A × X = B + * @return a matrix X that minimizes the two norm of A × X - B + * @throws DimensionMismatchException + * if the matrices dimensions do not match. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + RealMatrix solve(final RealMatrix b) throws SingularMatrixException; + + /** + * Check if the decomposed matrix is non-singular. + * @return true if the decomposed matrix is non-singular. + */ + boolean isNonSingular(); + + /** + * Get the pseudo-inverse + * of the decomposed matrix. + *

    + * This is equal to the inverse of the decomposed matrix, if such an inverse exists. + *

    + * If no such inverse exists, then the result has properties that resemble that of an inverse. + *

    + * In particular, in this case, if the decomposed matrix is A, then the system of equations + * \( A x = b \) may have no solutions, or many. If it has no solutions, then the pseudo-inverse + * \( A^+ \) gives the "closest" solution \( z = A^+ b \), meaning \( \left \| A z - b \right \|_2 \) + * is minimized. If there are many solutions, then \( z = A^+ b \) is the smallest solution, + * meaning \( \left \| z \right \|_2 \) is minimized. + *

    + * Note however that some decompositions cannot compute a pseudo-inverse for all matrices. + * For example, the {@link LUDecomposition} is not defined for non-square matrices to begin + * with. The {@link QRDecomposition} can operate on non-square matrices, but will throw + * {@link SingularMatrixException} if the decomposed matrix is singular. Refer to the javadoc + * of specific decomposition implementations for more details. + * + * @return pseudo-inverse matrix (which is the inverse, if it exists), + * if the decomposition can pseudo-invert the decomposed matrix + * @throws SingularMatrixException if the decomposed matrix is singular and the decomposition + * can not compute a pseudo-inverse + */ + RealMatrix getInverse() throws SingularMatrixException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java new file mode 100644 index 000000000..d4ec4c4a8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Default implementation of the {@link FieldMatrixChangingVisitor} interface. + *

    + * This class is a convenience to create custom visitors without defining all + * methods. This class provides default implementations that do nothing. + *

    + * + * @param the type of the field elements + * @since 2.0 + */ +public class DefaultFieldMatrixChangingVisitor> + implements FieldMatrixChangingVisitor { + /** Zero element of the field. */ + private final T zero; + + /** Build a new instance. + * @param zero additive identity of the field + */ + public DefaultFieldMatrixChangingVisitor(final T zero) { + this.zero = zero; + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + } + + /** {@inheritDoc} */ + public T visit(int row, int column, T value) { + return value; + } + + /** {@inheritDoc} */ + public T end() { + return zero; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java new file mode 100644 index 000000000..c395f5e03 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Default implementation of the {@link FieldMatrixPreservingVisitor} interface. + *

    + * This class is a convenience to create custom visitors without defining all + * methods. This class provides default implementations that do nothing. + *

    + * + * @param the type of the field elements + * @since 2.0 + */ +public class DefaultFieldMatrixPreservingVisitor> + implements FieldMatrixPreservingVisitor { + /** Zero element of the field. */ + private final T zero; + + /** Build a new instance. + * @param zero additive identity of the field + */ + public DefaultFieldMatrixPreservingVisitor(final T zero) { + this.zero = zero; + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + } + + /** {@inheritDoc} */ + public void visit(int row, int column, T value) {} + + /** {@inheritDoc} */ + public T end() { + return zero; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.java new file mode 100644 index 000000000..51b8ab483 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; + +/** + * A default concrete implementation of the abstract class + * {@link IterativeLinearSolverEvent}. + * + */ +public class DefaultIterativeLinearSolverEvent extends IterativeLinearSolverEvent { + + /** */ + private static final long serialVersionUID = 20120129L; + + /** The right-hand side vector. */ + private final RealVector b; + + /** The current estimate of the residual. */ + private final RealVector r; + + /** The current estimate of the norm of the residual. */ + private final double rnorm; + + /** The current estimate of the solution. */ + private final RealVector x; + + /** + * Creates a new instance of this class. This implementation does + * not deep copy the specified vectors {@code x}, {@code b}, + * {@code r}. Therefore the user must make sure that these vectors are + * either unmodifiable views or deep copies of the same vectors actually + * used by the {@code source}. Failure to do so may compromise subsequent + * iterations of the {@code source}. If the residual vector {@code r} is + * {@code null}, then {@link #getResidual()} throws a + * {@link MathUnsupportedOperationException}, and + * {@link #providesResidual()} returns {@code false}. + * + * @param source the iterative solver which fired this event + * @param iterations the number of iterations performed at the time + * {@code this} event is created + * @param x the current estimate of the solution + * @param b the right-hand side vector + * @param r the current estimate of the residual (can be {@code null}) + * @param rnorm the norm of the current estimate of the residual + */ + public DefaultIterativeLinearSolverEvent(final Object source, final int iterations, + final RealVector x, final RealVector b, final RealVector r, + final double rnorm) { + super(source, iterations); + this.x = x; + this.b = b; + this.r = r; + this.rnorm = rnorm; + } + + /** + * Creates a new instance of this class. This implementation does + * not deep copy the specified vectors {@code x}, {@code b}. + * Therefore the user must make sure that these vectors are either + * unmodifiable views or deep copies of the same vectors actually used by + * the {@code source}. Failure to do so may compromise subsequent iterations + * of the {@code source}. Callling {@link #getResidual()} on instances + * returned by this constructor throws a + * {@link MathUnsupportedOperationException}, while + * {@link #providesResidual()} returns {@code false}. + * + * @param source the iterative solver which fired this event + * @param iterations the number of iterations performed at the time + * {@code this} event is created + * @param x the current estimate of the solution + * @param b the right-hand side vector + * @param rnorm the norm of the current estimate of the residual + */ + public DefaultIterativeLinearSolverEvent(final Object source, final int iterations, + final RealVector x, final RealVector b, final double rnorm) { + super(source, iterations); + this.x = x; + this.b = b; + this.r = null; + this.rnorm = rnorm; + } + + /** {@inheritDoc} */ + @Override + public double getNormOfResidual() { + return rnorm; + } + + /** + * {@inheritDoc} + * + * This implementation throws an {@link MathUnsupportedOperationException} + * if no residual vector {@code r} was provided at construction time. + */ + @Override + public RealVector getResidual() { + if (r != null) { + return r; + } + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector getRightHandSideVector() { + return b; + } + + /** {@inheritDoc} */ + @Override + public RealVector getSolution() { + return x; + } + + /** + * {@inheritDoc} + * + * This implementation returns {@code true} if a non-{@code null} value was + * specified for the residual vector {@code r} at construction time. + * + * @return {@code true} if {@code r != null} + */ + @Override + public boolean providesResidual() { + return r != null; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.java new file mode 100644 index 000000000..4bdd77969 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Default implementation of the {@link RealMatrixChangingVisitor} interface. + *

    + * This class is a convenience to create custom visitors without defining all + * methods. This class provides default implementations that do nothing. + *

    + * + * @since 2.0 + */ +public class DefaultRealMatrixChangingVisitor implements RealMatrixChangingVisitor { + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + } + + /** {@inheritDoc} */ + public double visit(int row, int column, double value) { + return value; + } + + /** {@inheritDoc} */ + public double end() { + return 0; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.java new file mode 100644 index 000000000..bf0870f32 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Default implementation of the {@link RealMatrixPreservingVisitor} interface. + *

    + * This class is a convenience to create custom visitors without defining all + * methods. This class provides default implementations that do nothing. + *

    + * + * @since 2.0 + */ +public class DefaultRealMatrixPreservingVisitor implements RealMatrixPreservingVisitor { + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + } + + /** {@inheritDoc} */ + public void visit(int row, int column, double value) {} + + /** {@inheritDoc} */ + public double end() { + return 0; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DiagonalMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DiagonalMatrix.java new file mode 100644 index 000000000..28ebf30ba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/DiagonalMatrix.java @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Implementation of a diagonal matrix. + * + * @since 3.1.1 + */ +public class DiagonalMatrix extends AbstractRealMatrix + implements Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = 20121229L; + /** Entries of the diagonal. */ + private final double[] data; + + /** + * Creates a matrix with the supplied dimension. + * + * @param dimension Number of rows and columns in the new matrix. + * @throws NotStrictlyPositiveException if the dimension is + * not positive. + */ + public DiagonalMatrix(final int dimension) + throws NotStrictlyPositiveException { + super(dimension, dimension); + data = new double[dimension]; + } + + /** + * Creates a matrix using the input array as the underlying data. + *
    + * The input array is copied, not referenced. + * + * @param d Data for the new matrix. + */ + public DiagonalMatrix(final double[] d) { + this(d, true); + } + + /** + * Creates a matrix using the input array as the underlying data. + *
    + * If an array is created specially in order to be embedded in a + * this instance and not used directly, the {@code copyArray} may be + * set to {@code false}. + * This will prevent the copying and improve performance as no new + * array will be built and no data will be copied. + * + * @param d Data for new matrix. + * @param copyArray if {@code true}, the input array will be copied, + * otherwise it will be referenced. + * @exception NullArgumentException if d is null + */ + public DiagonalMatrix(final double[] d, final boolean copyArray) + throws NullArgumentException { + MathUtils.checkNotNull(d); + data = copyArray ? d.clone() : d; + } + + /** + * {@inheritDoc} + * + * @throws DimensionMismatchException if the requested dimensions are not equal. + */ + @Override + public RealMatrix createMatrix(final int rowDimension, + final int columnDimension) + throws NotStrictlyPositiveException, + DimensionMismatchException { + if (rowDimension != columnDimension) { + throw new DimensionMismatchException(rowDimension, columnDimension); + } + + return new DiagonalMatrix(rowDimension); + } + + /** {@inheritDoc} */ + @Override + public RealMatrix copy() { + return new DiagonalMatrix(data); + } + + /** + * Compute the sum of {@code this} and {@code m}. + * + * @param m Matrix to be added. + * @return {@code this + m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public DiagonalMatrix add(final DiagonalMatrix m) + throws MatrixDimensionMismatchException { + // Safety check. + MatrixUtils.checkAdditionCompatible(this, m); + + final int dim = getRowDimension(); + final double[] outData = new double[dim]; + for (int i = 0; i < dim; i++) { + outData[i] = data[i] + m.data[i]; + } + + return new DiagonalMatrix(outData, false); + } + + /** + * Returns {@code this} minus {@code m}. + * + * @param m Matrix to be subtracted. + * @return {@code this - m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public DiagonalMatrix subtract(final DiagonalMatrix m) + throws MatrixDimensionMismatchException { + MatrixUtils.checkSubtractionCompatible(this, m); + + final int dim = getRowDimension(); + final double[] outData = new double[dim]; + for (int i = 0; i < dim; i++) { + outData[i] = data[i] - m.data[i]; + } + + return new DiagonalMatrix(outData, false); + } + + /** + * Returns the result of postmultiplying {@code this} by {@code m}. + * + * @param m matrix to postmultiply by + * @return {@code this * m} + * @throws DimensionMismatchException if + * {@code columnDimension(this) != rowDimension(m)} + */ + public DiagonalMatrix multiply(final DiagonalMatrix m) + throws DimensionMismatchException { + MatrixUtils.checkMultiplicationCompatible(this, m); + + final int dim = getRowDimension(); + final double[] outData = new double[dim]; + for (int i = 0; i < dim; i++) { + outData[i] = data[i] * m.data[i]; + } + + return new DiagonalMatrix(outData, false); + } + + /** + * Returns the result of postmultiplying {@code this} by {@code m}. + * + * @param m matrix to postmultiply by + * @return {@code this * m} + * @throws DimensionMismatchException if + * {@code columnDimension(this) != rowDimension(m)} + */ + @Override + public RealMatrix multiply(final RealMatrix m) + throws DimensionMismatchException { + if (m instanceof DiagonalMatrix) { + return multiply((DiagonalMatrix) m); + } else { + MatrixUtils.checkMultiplicationCompatible(this, m); + final int nRows = m.getRowDimension(); + final int nCols = m.getColumnDimension(); + final double[][] product = new double[nRows][nCols]; + for (int r = 0; r < nRows; r++) { + for (int c = 0; c < nCols; c++) { + product[r][c] = data[r] * m.getEntry(r, c); + } + } + return new Array2DRowRealMatrix(product, false); + } + } + + /** {@inheritDoc} */ + @Override + public double[][] getData() { + final int dim = getRowDimension(); + final double[][] out = new double[dim][dim]; + + for (int i = 0; i < dim; i++) { + out[i][i] = data[i]; + } + + return out; + } + + /** + * Gets a reference to the underlying data array. + * + * @return 1-dimensional array of entries. + */ + public double[] getDataRef() { + return data; + } + + /** {@inheritDoc} */ + @Override + public double getEntry(final int row, final int column) + throws OutOfRangeException { + MatrixUtils.checkMatrixIndex(this, row, column); + return row == column ? data[row] : 0; + } + + /** {@inheritDoc} + * @throws NumberIsTooLargeException if {@code row != column} and value is non-zero. + */ + @Override + public void setEntry(final int row, final int column, final double value) + throws OutOfRangeException, NumberIsTooLargeException { + if (row == column) { + MatrixUtils.checkRowIndex(this, row); + data[row] = value; + } else { + ensureZero(value); + } + } + + /** {@inheritDoc} + * @throws NumberIsTooLargeException if {@code row != column} and increment is non-zero. + */ + @Override + public void addToEntry(final int row, + final int column, + final double increment) + throws OutOfRangeException, NumberIsTooLargeException { + if (row == column) { + MatrixUtils.checkRowIndex(this, row); + data[row] += increment; + } else { + ensureZero(increment); + } + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(final int row, + final int column, + final double factor) + throws OutOfRangeException { + // we don't care about non-diagonal elements for multiplication + if (row == column) { + MatrixUtils.checkRowIndex(this, row); + data[row] *= factor; + } + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return data.length; + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return data.length; + } + + /** {@inheritDoc} */ + @Override + public double[] operate(final double[] v) + throws DimensionMismatchException { + return multiply(new DiagonalMatrix(v, false)).getDataRef(); + } + + /** {@inheritDoc} */ + @Override + public double[] preMultiply(final double[] v) + throws DimensionMismatchException { + return operate(v); + } + + /** {@inheritDoc} */ + @Override + public RealVector preMultiply(final RealVector v) throws DimensionMismatchException { + final double[] vectorData; + if (v instanceof ArrayRealVector) { + vectorData = ((ArrayRealVector) v).getDataRef(); + } else { + vectorData = v.toArray(); + } + return MatrixUtils.createRealVector(preMultiply(vectorData)); + } + + /** Ensure a value is zero. + * @param value value to check + * @exception NumberIsTooLargeException if value is not zero + */ + private void ensureZero(final double value) throws NumberIsTooLargeException { + if (!Precision.equals(0.0, value, 1)) { + throw new NumberIsTooLargeException(FastMath.abs(value), 0, true); + } + } + + /** + * Computes the inverse of this diagonal matrix. + *

    + * Note: this method will use a singularity threshold of 0, + * use {@link #inverse(double)} if a different threshold is needed. + * + * @return the inverse of {@code m} + * @throws SingularMatrixException if the matrix is singular + * @since 3.3 + */ + public DiagonalMatrix inverse() throws SingularMatrixException { + return inverse(0); + } + + /** + * Computes the inverse of this diagonal matrix. + * + * @param threshold Singularity threshold. + * @return the inverse of {@code m} + * @throws SingularMatrixException if the matrix is singular + * @since 3.3 + */ + public DiagonalMatrix inverse(double threshold) throws SingularMatrixException { + if (isSingular(threshold)) { + throw new SingularMatrixException(); + } + + final double[] result = new double[data.length]; + for (int i = 0; i < data.length; i++) { + result[i] = 1.0 / data[i]; + } + return new DiagonalMatrix(result, false); + } + + /** Returns whether this diagonal matrix is singular, i.e. any diagonal entry + * is equal to {@code 0} within the given threshold. + * + * @param threshold Singularity threshold. + * @return {@code true} if the matrix is singular, {@code false} otherwise + * @since 3.3 + */ + public boolean isSingular(double threshold) { + for (int i = 0; i < data.length; i++) { + if (Precision.equals(data[i], 0.0, threshold)) { + return true; + } + } + return false; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/EigenDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/EigenDecomposition.java new file mode 100644 index 000000000..2cdbbeb31 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/EigenDecomposition.java @@ -0,0 +1,970 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Calculates the eigen decomposition of a real matrix. + *

    The eigen decomposition of matrix A is a set of two matrices: + * V and D such that A = V × D × VT. + * A, V and D are all m × m matrices.

    + *

    This class is similar in spirit to the EigenvalueDecomposition + * class from the JAMA + * library, with the following changes:

    + *
      + *
    • a {@link #getVT() getVt} method has been added,
    • + *
    • two {@link #getRealEigenvalue(int) getRealEigenvalue} and {@link #getImagEigenvalue(int) + * getImagEigenvalue} methods to pick up a single eigenvalue have been added,
    • + *
    • a {@link #getEigenvector(int) getEigenvector} method to pick up a single + * eigenvector has been added,
    • + *
    • a {@link #getDeterminant() getDeterminant} method has been added.
    • + *
    • a {@link #getSolver() getSolver} method has been added.
    • + *
    + *

    + * As of 3.1, this class supports general real matrices (both symmetric and non-symmetric): + *

    + *

    + * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is diagonal and the eigenvector + * matrix V is orthogonal, i.e. A = V.multiply(D.multiply(V.transpose())) and + * V.multiply(V.transpose()) equals the identity matrix. + *

    + *

    + * If A is not symmetric, then the eigenvalue matrix D is block diagonal with the real eigenvalues + * in 1-by-1 blocks and any complex eigenvalues, lambda + i*mu, in 2-by-2 blocks: + *

    + *    [lambda, mu    ]
    + *    [   -mu, lambda]
    + * 
    + * The columns of V represent the eigenvectors in the sense that A*V = V*D, + * i.e. A.multiply(V) equals V.multiply(D). + * The matrix V may be badly conditioned, or even singular, so the validity of the equation + * A = V*D*inverse(V) depends upon the condition of V. + *

    + *

    + * This implementation is based on the paper by A. Drubrulle, R.S. Martin and + * J.H. Wilkinson "The Implicit QL Algorithm" in Wilksinson and Reinsch (1971) + * Handbook for automatic computation, vol. 2, Linear algebra, Springer-Verlag, + * New-York + *

    + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class EigenDecomposition { + /** Internally used epsilon criteria. */ + private static final double EPSILON = 1e-12; + /** Maximum number of iterations accepted in the implicit QL transformation */ + private byte maxIter = 30; + /** Main diagonal of the tridiagonal matrix. */ + private double[] main; + /** Secondary diagonal of the tridiagonal matrix. */ + private double[] secondary; + /** + * Transformer to tridiagonal (may be null if matrix is already + * tridiagonal). + */ + private TriDiagonalTransformer transformer; + /** Real part of the realEigenvalues. */ + private double[] realEigenvalues; + /** Imaginary part of the realEigenvalues. */ + private double[] imagEigenvalues; + /** Eigenvectors. */ + private ArrayRealVector[] eigenvectors; + /** Cached value of V. */ + private RealMatrix cachedV; + /** Cached value of D. */ + private RealMatrix cachedD; + /** Cached value of Vt. */ + private RealMatrix cachedVt; + /** Whether the matrix is symmetric. */ + private final boolean isSymmetric; + + /** + * Calculates the eigen decomposition of the given real matrix. + *

    + * Supports decomposition of a general matrix since 3.1. + * + * @param matrix Matrix to decompose. + * @throws MaxCountExceededException if the algorithm fails to converge. + * @throws MathArithmeticException if the decomposition of a general matrix + * results in a matrix with zero norm + * @since 3.1 + */ + public EigenDecomposition(final RealMatrix matrix) + throws MathArithmeticException { + final double symTol = 10 * matrix.getRowDimension() * matrix.getColumnDimension() * Precision.EPSILON; + isSymmetric = MatrixUtils.isSymmetric(matrix, symTol); + if (isSymmetric) { + transformToTridiagonal(matrix); + findEigenVectors(transformer.getQ().getData()); + } else { + final SchurTransformer t = transformToSchur(matrix); + findEigenVectorsFromSchur(t); + } + } + + /** + * Calculates the eigen decomposition of the given real matrix. + * + * @param matrix Matrix to decompose. + * @param splitTolerance Dummy parameter (present for backward + * compatibility only). + * @throws MathArithmeticException if the decomposition of a general matrix + * results in a matrix with zero norm + * @throws MaxCountExceededException if the algorithm fails to converge. + * @deprecated in 3.1 (to be removed in 4.0) due to unused parameter + */ + @Deprecated + public EigenDecomposition(final RealMatrix matrix, + final double splitTolerance) + throws MathArithmeticException { + this(matrix); + } + + /** + * Calculates the eigen decomposition of the symmetric tridiagonal + * matrix. The Householder matrix is assumed to be the identity matrix. + * + * @param main Main diagonal of the symmetric tridiagonal form. + * @param secondary Secondary of the tridiagonal form. + * @throws MaxCountExceededException if the algorithm fails to converge. + * @since 3.1 + */ + public EigenDecomposition(final double[] main, final double[] secondary) { + isSymmetric = true; + this.main = main.clone(); + this.secondary = secondary.clone(); + transformer = null; + final int size = main.length; + final double[][] z = new double[size][size]; + for (int i = 0; i < size; i++) { + z[i][i] = 1.0; + } + findEigenVectors(z); + } + + /** + * Calculates the eigen decomposition of the symmetric tridiagonal + * matrix. The Householder matrix is assumed to be the identity matrix. + * + * @param main Main diagonal of the symmetric tridiagonal form. + * @param secondary Secondary of the tridiagonal form. + * @param splitTolerance Dummy parameter (present for backward + * compatibility only). + * @throws MaxCountExceededException if the algorithm fails to converge. + * @deprecated in 3.1 (to be removed in 4.0) due to unused parameter + */ + @Deprecated + public EigenDecomposition(final double[] main, final double[] secondary, + final double splitTolerance) { + this(main, secondary); + } + + /** + * Gets the matrix V of the decomposition. + * V is an orthogonal matrix, i.e. its transpose is also its inverse. + * The columns of V are the eigenvectors of the original matrix. + * No assumption is made about the orientation of the system axes formed + * by the columns of V (e.g. in a 3-dimension space, V can form a left- + * or right-handed system). + * + * @return the V matrix. + */ + public RealMatrix getV() { + + if (cachedV == null) { + final int m = eigenvectors.length; + cachedV = MatrixUtils.createRealMatrix(m, m); + for (int k = 0; k < m; ++k) { + cachedV.setColumnVector(k, eigenvectors[k]); + } + } + // return the cached matrix + return cachedV; + } + + /** + * Gets the block diagonal matrix D of the decomposition. + * D is a block diagonal matrix. + * Real eigenvalues are on the diagonal while complex values are on + * 2x2 blocks { {real +imaginary}, {-imaginary, real} }. + * + * @return the D matrix. + * + * @see #getRealEigenvalues() + * @see #getImagEigenvalues() + */ + public RealMatrix getD() { + + if (cachedD == null) { + // cache the matrix for subsequent calls + cachedD = MatrixUtils.createRealDiagonalMatrix(realEigenvalues); + + for (int i = 0; i < imagEigenvalues.length; i++) { + if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) > 0) { + cachedD.setEntry(i, i+1, imagEigenvalues[i]); + } else if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) { + cachedD.setEntry(i, i-1, imagEigenvalues[i]); + } + } + } + return cachedD; + } + + /** + * Gets the transpose of the matrix V of the decomposition. + * V is an orthogonal matrix, i.e. its transpose is also its inverse. + * The columns of V are the eigenvectors of the original matrix. + * No assumption is made about the orientation of the system axes formed + * by the columns of V (e.g. in a 3-dimension space, V can form a left- + * or right-handed system). + * + * @return the transpose of the V matrix. + */ + public RealMatrix getVT() { + + if (cachedVt == null) { + final int m = eigenvectors.length; + cachedVt = MatrixUtils.createRealMatrix(m, m); + for (int k = 0; k < m; ++k) { + cachedVt.setRowVector(k, eigenvectors[k]); + } + } + + // return the cached matrix + return cachedVt; + } + + /** + * Returns whether the calculated eigen values are complex or real. + *

    The method performs a zero check for each element of the + * {@link #getImagEigenvalues()} array and returns {@code true} if any + * element is not equal to zero. + * + * @return {@code true} if the eigen values are complex, {@code false} otherwise + * @since 3.1 + */ + public boolean hasComplexEigenvalues() { + for (int i = 0; i < imagEigenvalues.length; i++) { + if (!Precision.equals(imagEigenvalues[i], 0.0, EPSILON)) { + return true; + } + } + return false; + } + + /** + * Gets a copy of the real parts of the eigenvalues of the original matrix. + * + * @return a copy of the real parts of the eigenvalues of the original matrix. + * + * @see #getD() + * @see #getRealEigenvalue(int) + * @see #getImagEigenvalues() + */ + public double[] getRealEigenvalues() { + return realEigenvalues.clone(); + } + + /** + * Returns the real part of the ith eigenvalue of the original + * matrix. + * + * @param i index of the eigenvalue (counting from 0) + * @return real part of the ith eigenvalue of the original + * matrix. + * + * @see #getD() + * @see #getRealEigenvalues() + * @see #getImagEigenvalue(int) + */ + public double getRealEigenvalue(final int i) { + return realEigenvalues[i]; + } + + /** + * Gets a copy of the imaginary parts of the eigenvalues of the original + * matrix. + * + * @return a copy of the imaginary parts of the eigenvalues of the original + * matrix. + * + * @see #getD() + * @see #getImagEigenvalue(int) + * @see #getRealEigenvalues() + */ + public double[] getImagEigenvalues() { + return imagEigenvalues.clone(); + } + + /** + * Gets the imaginary part of the ith eigenvalue of the original + * matrix. + * + * @param i Index of the eigenvalue (counting from 0). + * @return the imaginary part of the ith eigenvalue of the original + * matrix. + * + * @see #getD() + * @see #getImagEigenvalues() + * @see #getRealEigenvalue(int) + */ + public double getImagEigenvalue(final int i) { + return imagEigenvalues[i]; + } + + /** + * Gets a copy of the ith eigenvector of the original matrix. + * + * @param i Index of the eigenvector (counting from 0). + * @return a copy of the ith eigenvector of the original matrix. + * @see #getD() + */ + public RealVector getEigenvector(final int i) { + return eigenvectors[i].copy(); + } + + /** + * Computes the determinant of the matrix. + * + * @return the determinant of the matrix. + */ + public double getDeterminant() { + double determinant = 1; + for (double lambda : realEigenvalues) { + determinant *= lambda; + } + return determinant; + } + + /** + * Computes the square-root of the matrix. + * This implementation assumes that the matrix is symmetric and positive + * definite. + * + * @return the square-root of the matrix. + * @throws MathUnsupportedOperationException if the matrix is not + * symmetric or not positive definite. + * @since 3.1 + */ + public RealMatrix getSquareRoot() { + if (!isSymmetric) { + throw new MathUnsupportedOperationException(); + } + + final double[] sqrtEigenValues = new double[realEigenvalues.length]; + for (int i = 0; i < realEigenvalues.length; i++) { + final double eigen = realEigenvalues[i]; + if (eigen <= 0) { + throw new MathUnsupportedOperationException(); + } + sqrtEigenValues[i] = FastMath.sqrt(eigen); + } + final RealMatrix sqrtEigen = MatrixUtils.createRealDiagonalMatrix(sqrtEigenValues); + final RealMatrix v = getV(); + final RealMatrix vT = getVT(); + + return v.multiply(sqrtEigen).multiply(vT); + } + + /** + * Gets a solver for finding the A × X = B solution in exact + * linear sense. + *

    + * Since 3.1, eigen decomposition of a general matrix is supported, + * but the {@link DecompositionSolver} only supports real eigenvalues. + * + * @return a solver + * @throws MathUnsupportedOperationException if the decomposition resulted in + * complex eigenvalues + */ + public DecompositionSolver getSolver() { + if (hasComplexEigenvalues()) { + throw new MathUnsupportedOperationException(); + } + return new Solver(realEigenvalues, imagEigenvalues, eigenvectors); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + /** Real part of the realEigenvalues. */ + private double[] realEigenvalues; + /** Imaginary part of the realEigenvalues. */ + private double[] imagEigenvalues; + /** Eigenvectors. */ + private final ArrayRealVector[] eigenvectors; + + /** + * Builds a solver from decomposed matrix. + * + * @param realEigenvalues Real parts of the eigenvalues. + * @param imagEigenvalues Imaginary parts of the eigenvalues. + * @param eigenvectors Eigenvectors. + */ + private Solver(final double[] realEigenvalues, + final double[] imagEigenvalues, + final ArrayRealVector[] eigenvectors) { + this.realEigenvalues = realEigenvalues; + this.imagEigenvalues = imagEigenvalues; + this.eigenvectors = eigenvectors; + } + + /** + * Solves the linear equation A × X = B for symmetric matrices A. + *

    + * This method only finds exact linear solutions, i.e. solutions for + * which ||A × X - B|| is exactly 0. + *

    + * + * @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 DimensionMismatchException if the matrices dimensions do not match. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public RealVector solve(final RealVector b) { + if (!isNonSingular()) { + throw new SingularMatrixException(); + } + + final int m = realEigenvalues.length; + if (b.getDimension() != m) { + throw new DimensionMismatchException(b.getDimension(), m); + } + + final double[] bp = new double[m]; + for (int i = 0; i < m; ++i) { + final ArrayRealVector v = eigenvectors[i]; + final double[] vData = v.getDataRef(); + final double s = v.dotProduct(b) / realEigenvalues[i]; + for (int j = 0; j < m; ++j) { + bp[j] += s * vData[j]; + } + } + + return new ArrayRealVector(bp, false); + } + + /** {@inheritDoc} */ + public RealMatrix solve(RealMatrix b) { + + if (!isNonSingular()) { + throw new SingularMatrixException(); + } + + final int m = realEigenvalues.length; + if (b.getRowDimension() != m) { + throw new DimensionMismatchException(b.getRowDimension(), m); + } + + final int nColB = b.getColumnDimension(); + final double[][] bp = new double[m][nColB]; + final double[] tmpCol = new double[m]; + for (int k = 0; k < nColB; ++k) { + for (int i = 0; i < m; ++i) { + tmpCol[i] = b.getEntry(i, k); + bp[i][k] = 0; + } + for (int i = 0; i < m; ++i) { + final ArrayRealVector v = eigenvectors[i]; + final double[] vData = v.getDataRef(); + double s = 0; + for (int j = 0; j < m; ++j) { + s += v.getEntry(j) * tmpCol[j]; + } + s /= realEigenvalues[i]; + for (int j = 0; j < m; ++j) { + bp[j][k] += s * vData[j]; + } + } + } + + return new Array2DRowRealMatrix(bp, false); + + } + + /** + * Checks whether the decomposed matrix is non-singular. + * + * @return true if the decomposed matrix is non-singular. + */ + public boolean isNonSingular() { + double largestEigenvalueNorm = 0.0; + // Looping over all values (in case they are not sorted in decreasing + // order of their norm). + for (int i = 0; i < realEigenvalues.length; ++i) { + largestEigenvalueNorm = FastMath.max(largestEigenvalueNorm, eigenvalueNorm(i)); + } + // Corner case: zero matrix, all exactly 0 eigenvalues + if (largestEigenvalueNorm == 0.0) { + return false; + } + for (int i = 0; i < realEigenvalues.length; ++i) { + // Looking for eigenvalues that are 0, where we consider anything much much smaller + // than the largest eigenvalue to be effectively 0. + if (Precision.equals(eigenvalueNorm(i) / largestEigenvalueNorm, 0, EPSILON)) { + return false; + } + } + return true; + } + + /** + * @param i which eigenvalue to find the norm of + * @return the norm of ith (complex) eigenvalue. + */ + private double eigenvalueNorm(int i) { + final double re = realEigenvalues[i]; + final double im = imagEigenvalues[i]; + return FastMath.sqrt(re * re + im * im); + } + + /** + * Get the inverse of the decomposed matrix. + * + * @return the inverse matrix. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public RealMatrix getInverse() { + if (!isNonSingular()) { + throw new SingularMatrixException(); + } + + final int m = realEigenvalues.length; + final double[][] invData = new double[m][m]; + + for (int i = 0; i < m; ++i) { + final double[] invI = invData[i]; + for (int j = 0; j < m; ++j) { + double invIJ = 0; + for (int k = 0; k < m; ++k) { + final double[] vK = eigenvectors[k].getDataRef(); + invIJ += vK[i] * vK[j] / realEigenvalues[k]; + } + invI[j] = invIJ; + } + } + return MatrixUtils.createRealMatrix(invData); + } + } + + /** + * Transforms the matrix to tridiagonal form. + * + * @param matrix Matrix to transform. + */ + private void transformToTridiagonal(final RealMatrix matrix) { + // transform the matrix to tridiagonal + transformer = new TriDiagonalTransformer(matrix); + main = transformer.getMainDiagonalRef(); + secondary = transformer.getSecondaryDiagonalRef(); + } + + /** + * Find eigenvalues and eigenvectors (Dubrulle et al., 1971) + * + * @param householderMatrix Householder matrix of the transformation + * to tridiagonal form. + */ + private void findEigenVectors(final double[][] householderMatrix) { + final double[][]z = householderMatrix.clone(); + final int n = main.length; + realEigenvalues = new double[n]; + imagEigenvalues = new double[n]; + final double[] e = new double[n]; + for (int i = 0; i < n - 1; i++) { + realEigenvalues[i] = main[i]; + e[i] = secondary[i]; + } + realEigenvalues[n - 1] = main[n - 1]; + e[n - 1] = 0; + + // Determine the largest main and secondary value in absolute term. + double maxAbsoluteValue = 0; + for (int i = 0; i < n; i++) { + if (FastMath.abs(realEigenvalues[i]) > maxAbsoluteValue) { + maxAbsoluteValue = FastMath.abs(realEigenvalues[i]); + } + if (FastMath.abs(e[i]) > maxAbsoluteValue) { + maxAbsoluteValue = FastMath.abs(e[i]); + } + } + // Make null any main and secondary value too small to be significant + if (maxAbsoluteValue != 0) { + for (int i=0; i < n; i++) { + if (FastMath.abs(realEigenvalues[i]) <= Precision.EPSILON * maxAbsoluteValue) { + realEigenvalues[i] = 0; + } + if (FastMath.abs(e[i]) <= Precision.EPSILON * maxAbsoluteValue) { + e[i]=0; + } + } + } + + for (int j = 0; j < n; j++) { + int its = 0; + int m; + do { + for (m = j; m < n - 1; m++) { + double delta = FastMath.abs(realEigenvalues[m]) + + FastMath.abs(realEigenvalues[m + 1]); + if (FastMath.abs(e[m]) + delta == delta) { + break; + } + } + if (m != j) { + if (its == maxIter) { + throw new MaxCountExceededException(LocalizedFormats.CONVERGENCE_FAILED, + maxIter); + } + its++; + double q = (realEigenvalues[j + 1] - realEigenvalues[j]) / (2 * e[j]); + double t = FastMath.sqrt(1 + q * q); + if (q < 0.0) { + q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q - t); + } else { + q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q + t); + } + double u = 0.0; + double s = 1.0; + double c = 1.0; + int i; + for (i = m - 1; i >= j; i--) { + double p = s * e[i]; + double h = c * e[i]; + if (FastMath.abs(p) >= FastMath.abs(q)) { + c = q / p; + t = FastMath.sqrt(c * c + 1.0); + e[i + 1] = p * t; + s = 1.0 / t; + c *= s; + } else { + s = p / q; + t = FastMath.sqrt(s * s + 1.0); + e[i + 1] = q * t; + c = 1.0 / t; + s *= c; + } + if (e[i + 1] == 0.0) { + realEigenvalues[i + 1] -= u; + e[m] = 0.0; + break; + } + q = realEigenvalues[i + 1] - u; + t = (realEigenvalues[i] - q) * s + 2.0 * c * h; + u = s * t; + realEigenvalues[i + 1] = q + u; + q = c * t - h; + for (int ia = 0; ia < n; ia++) { + p = z[ia][i + 1]; + z[ia][i + 1] = s * z[ia][i] + c * p; + z[ia][i] = c * z[ia][i] - s * p; + } + } + if (t == 0.0 && i >= j) { + continue; + } + realEigenvalues[j] -= u; + e[j] = q; + e[m] = 0.0; + } + } while (m != j); + } + + //Sort the eigen values (and vectors) in increase order + for (int i = 0; i < n; i++) { + int k = i; + double p = realEigenvalues[i]; + for (int j = i + 1; j < n; j++) { + if (realEigenvalues[j] > p) { + k = j; + p = realEigenvalues[j]; + } + } + if (k != i) { + realEigenvalues[k] = realEigenvalues[i]; + realEigenvalues[i] = p; + for (int j = 0; j < n; j++) { + p = z[j][i]; + z[j][i] = z[j][k]; + z[j][k] = p; + } + } + } + + // Determine the largest eigen value in absolute term. + maxAbsoluteValue = 0; + for (int i = 0; i < n; i++) { + if (FastMath.abs(realEigenvalues[i]) > maxAbsoluteValue) { + maxAbsoluteValue=FastMath.abs(realEigenvalues[i]); + } + } + // Make null any eigen value too small to be significant + if (maxAbsoluteValue != 0.0) { + for (int i=0; i < n; i++) { + if (FastMath.abs(realEigenvalues[i]) < Precision.EPSILON * maxAbsoluteValue) { + realEigenvalues[i] = 0; + } + } + } + eigenvectors = new ArrayRealVector[n]; + final double[] tmp = new double[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + tmp[j] = z[j][i]; + } + eigenvectors[i] = new ArrayRealVector(tmp); + } + } + + /** + * Transforms the matrix to Schur form and calculates the eigenvalues. + * + * @param matrix Matrix to transform. + * @return the {@link SchurTransformer Shur transform} for this matrix + */ + private SchurTransformer transformToSchur(final RealMatrix matrix) { + final SchurTransformer schurTransform = new SchurTransformer(matrix); + final double[][] matT = schurTransform.getT().getData(); + + realEigenvalues = new double[matT.length]; + imagEigenvalues = new double[matT.length]; + + for (int i = 0; i < realEigenvalues.length; i++) { + if (i == (realEigenvalues.length - 1) || + Precision.equals(matT[i + 1][i], 0.0, EPSILON)) { + realEigenvalues[i] = matT[i][i]; + } else { + final double x = matT[i + 1][i + 1]; + final double p = 0.5 * (matT[i][i] - x); + final double z = FastMath.sqrt(FastMath.abs(p * p + matT[i + 1][i] * matT[i][i + 1])); + realEigenvalues[i] = x + p; + imagEigenvalues[i] = z; + realEigenvalues[i + 1] = x + p; + imagEigenvalues[i + 1] = -z; + i++; + } + } + return schurTransform; + } + + /** + * Performs a division of two complex numbers. + * + * @param xr real part of the first number + * @param xi imaginary part of the first number + * @param yr real part of the second number + * @param yi imaginary part of the second number + * @return result of the complex division + */ + private Complex cdiv(final double xr, final double xi, + final double yr, final double yi) { + return new Complex(xr, xi).divide(new Complex(yr, yi)); + } + + /** + * Find eigenvectors from a matrix transformed to Schur form. + * + * @param schur the schur transformation of the matrix + * @throws MathArithmeticException if the Schur form has a norm of zero + */ + private void findEigenVectorsFromSchur(final SchurTransformer schur) + throws MathArithmeticException { + final double[][] matrixT = schur.getT().getData(); + final double[][] matrixP = schur.getP().getData(); + + final int n = matrixT.length; + + // compute matrix norm + double norm = 0.0; + for (int i = 0; i < n; i++) { + for (int j = FastMath.max(i - 1, 0); j < n; j++) { + norm += FastMath.abs(matrixT[i][j]); + } + } + + // we can not handle a matrix with zero norm + if (Precision.equals(norm, 0.0, EPSILON)) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + // Backsubstitute to find vectors of upper triangular form + + double r = 0.0; + double s = 0.0; + double z = 0.0; + + for (int idx = n - 1; idx >= 0; idx--) { + double p = realEigenvalues[idx]; + double q = imagEigenvalues[idx]; + + if (Precision.equals(q, 0.0)) { + // Real vector + int l = idx; + matrixT[idx][idx] = 1.0; + for (int i = idx - 1; i >= 0; i--) { + double w = matrixT[i][i] - p; + r = 0.0; + for (int j = l; j <= idx; j++) { + r += matrixT[i][j] * matrixT[j][idx]; + } + if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) { + z = w; + s = r; + } else { + l = i; + if (Precision.equals(imagEigenvalues[i], 0.0)) { + if (w != 0.0) { + matrixT[i][idx] = -r / w; + } else { + matrixT[i][idx] = -r / (Precision.EPSILON * norm); + } + } else { + // Solve real equations + double x = matrixT[i][i + 1]; + double y = matrixT[i + 1][i]; + q = (realEigenvalues[i] - p) * (realEigenvalues[i] - p) + + imagEigenvalues[i] * imagEigenvalues[i]; + double t = (x * s - z * r) / q; + matrixT[i][idx] = t; + if (FastMath.abs(x) > FastMath.abs(z)) { + matrixT[i + 1][idx] = (-r - w * t) / x; + } else { + matrixT[i + 1][idx] = (-s - y * t) / z; + } + } + + // Overflow control + double t = FastMath.abs(matrixT[i][idx]); + if ((Precision.EPSILON * t) * t > 1) { + for (int j = i; j <= idx; j++) { + matrixT[j][idx] /= t; + } + } + } + } + } else if (q < 0.0) { + // Complex vector + int l = idx - 1; + + // Last vector component imaginary so matrix is triangular + if (FastMath.abs(matrixT[idx][idx - 1]) > FastMath.abs(matrixT[idx - 1][idx])) { + matrixT[idx - 1][idx - 1] = q / matrixT[idx][idx - 1]; + matrixT[idx - 1][idx] = -(matrixT[idx][idx] - p) / matrixT[idx][idx - 1]; + } else { + final Complex result = cdiv(0.0, -matrixT[idx - 1][idx], + matrixT[idx - 1][idx - 1] - p, q); + matrixT[idx - 1][idx - 1] = result.getReal(); + matrixT[idx - 1][idx] = result.getImaginary(); + } + + matrixT[idx][idx - 1] = 0.0; + matrixT[idx][idx] = 1.0; + + for (int i = idx - 2; i >= 0; i--) { + double ra = 0.0; + double sa = 0.0; + for (int j = l; j <= idx; j++) { + ra += matrixT[i][j] * matrixT[j][idx - 1]; + sa += matrixT[i][j] * matrixT[j][idx]; + } + double w = matrixT[i][i] - p; + + if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) { + z = w; + r = ra; + s = sa; + } else { + l = i; + if (Precision.equals(imagEigenvalues[i], 0.0)) { + final Complex c = cdiv(-ra, -sa, w, q); + matrixT[i][idx - 1] = c.getReal(); + matrixT[i][idx] = c.getImaginary(); + } else { + // Solve complex equations + double x = matrixT[i][i + 1]; + double y = matrixT[i + 1][i]; + double vr = (realEigenvalues[i] - p) * (realEigenvalues[i] - p) + + imagEigenvalues[i] * imagEigenvalues[i] - q * q; + final double vi = (realEigenvalues[i] - p) * 2.0 * q; + if (Precision.equals(vr, 0.0) && Precision.equals(vi, 0.0)) { + vr = Precision.EPSILON * norm * + (FastMath.abs(w) + FastMath.abs(q) + FastMath.abs(x) + + FastMath.abs(y) + FastMath.abs(z)); + } + final Complex c = cdiv(x * r - z * ra + q * sa, + x * s - z * sa - q * ra, vr, vi); + matrixT[i][idx - 1] = c.getReal(); + matrixT[i][idx] = c.getImaginary(); + + if (FastMath.abs(x) > (FastMath.abs(z) + FastMath.abs(q))) { + matrixT[i + 1][idx - 1] = (-ra - w * matrixT[i][idx - 1] + + q * matrixT[i][idx]) / x; + matrixT[i + 1][idx] = (-sa - w * matrixT[i][idx] - + q * matrixT[i][idx - 1]) / x; + } else { + final Complex c2 = cdiv(-r - y * matrixT[i][idx - 1], + -s - y * matrixT[i][idx], z, q); + matrixT[i + 1][idx - 1] = c2.getReal(); + matrixT[i + 1][idx] = c2.getImaginary(); + } + } + + // Overflow control + double t = FastMath.max(FastMath.abs(matrixT[i][idx - 1]), + FastMath.abs(matrixT[i][idx])); + if ((Precision.EPSILON * t) * t > 1) { + for (int j = i; j <= idx; j++) { + matrixT[j][idx - 1] /= t; + matrixT[j][idx] /= t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (int j = n - 1; j >= 0; j--) { + for (int i = 0; i <= n - 1; i++) { + z = 0.0; + for (int k = 0; k <= FastMath.min(j, n - 1); k++) { + z += matrixP[i][k] * matrixT[k][j]; + } + matrixP[i][j] = z; + } + } + + eigenvectors = new ArrayRealVector[n]; + final double[] tmp = new double[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + tmp[j] = matrixP[j][i]; + } + eigenvectors[i] = new ArrayRealVector(tmp); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldDecompositionSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldDecompositionSolver.java new file mode 100644 index 000000000..08c88f3fd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldDecompositionSolver.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.FieldElement; + + +/** + * Interface handling decomposition algorithms that can solve A × X = B. + *

    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.

    + *

    Some solvers like {@link FieldLUDecomposition} can only find the solution for + * square matrices and when the solution is an exact linear solution, i.e. when + * ||A × X - B|| is exactly 0. Other solvers can also find solutions + * with non-square matrix A and with non-null minimal norm. If an exact linear + * solution exists it is also the minimal norm solution.

    + * + * @param the type of the field elements + * @since 2.0 + */ +public interface FieldDecompositionSolver> { + + /** Solve the linear equation A × X = B for matrices A. + *

    The A matrix is implicit, it is provided by the underlying + * decomposition algorithm.

    + * @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 DimensionMismatchException + * if the matrices dimensions do not match. + * @throws SingularMatrixException + * if the decomposed matrix is singular. + */ + FieldVector solve(final FieldVector b); + + /** Solve the linear equation A × X = B for matrices A. + *

    The A matrix is implicit, it is provided by the underlying + * decomposition algorithm.

    + * @param b right-hand side of the equation A × X = B + * @return a matrix X that minimizes the two norm of A × X - B + * @throws DimensionMismatchException + * if the matrices dimensions do not match. + * @throws SingularMatrixException + * if the decomposed matrix is singular. + */ + FieldMatrix solve(final FieldMatrix b); + + /** + * Check if the decomposed matrix is non-singular. + * @return true if the decomposed matrix is non-singular + */ + boolean isNonSingular(); + + /** Get the inverse (or pseudo-inverse) of the decomposed matrix. + * @return inverse matrix + * @throws SingularMatrixException + * if the decomposed matrix is singular. + */ + FieldMatrix getInverse(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldLUDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldLUDecomposition.java new file mode 100644 index 000000000..fc254c15b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldLUDecomposition.java @@ -0,0 +1,446 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the LUP-decomposition of a square matrix. + *

    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.

    + *

    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.

    + *

    This class is based on the class with similar name from the + * JAMA library.

    + *
      + *
    • a {@link #getP() getP} method has been added,
    • + *
    • the {@code det} method has been renamed as {@link #getDeterminant() + * getDeterminant},
    • + *
    • the {@code getDoublePivot} method has been removed (but the int based + * {@link #getPivot() getPivot} method has been kept),
    • + *
    • the {@code solve} and {@code isNonSingular} methods have been replaced + * by a {@link #getSolver() getSolver} method and the equivalent methods + * provided by the returned {@link DecompositionSolver}.
    • + *
    + * + * @param the type of the field elements + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class FieldLUDecomposition> { + + /** Field to which the elements belong. */ + private final Field 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 cachedL; + + /** Cached value of U. */ + private FieldMatrix cachedU; + + /** Cached value of P. */ + private FieldMatrix cachedP; + + /** + * Calculates the LU-decomposition of the given matrix. + * @param matrix The matrix to decompose. + * @throws NonSquareMatrixException if matrix is not square + */ + public FieldLUDecomposition(FieldMatrix matrix) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + final int m = matrix.getColumnDimension(); + field = matrix.getField(); + lu = matrix.getData(); + pivot = new int[m]; + cachedL = null; + cachedU = null; + cachedP = null; + + // Initialize permutation array and parity + for (int row = 0; row < m; row++) { + pivot[row] = row; + } + even = true; + singular = false; + + // Loop over columns + for (int col = 0; col < m; col++) { + + T sum = field.getZero(); + + // upper + for (int row = 0; row < col; row++) { + final T[] luRow = lu[row]; + sum = luRow[col]; + for (int i = 0; i < row; i++) { + sum = sum.subtract(luRow[i].multiply(lu[i][col])); + } + luRow[col] = sum; + } + + // lower + int nonZero = col; // permutation row + for (int row = col; row < m; row++) { + final T[] luRow = lu[row]; + sum = luRow[col]; + for (int i = 0; i < col; i++) { + sum = sum.subtract(luRow[i].multiply(lu[i][col])); + } + luRow[col] = sum; + + if (lu[nonZero][col].equals(field.getZero())) { + // try to select a better permutation choice + ++nonZero; + } + } + + // Singularity check + if (nonZero >= m) { + singular = true; + return; + } + + // Pivot if necessary + if (nonZero != col) { + T tmp = field.getZero(); + for (int i = 0; i < m; i++) { + tmp = lu[nonZero][i]; + lu[nonZero][i] = lu[col][i]; + lu[col][i] = tmp; + } + int temp = pivot[nonZero]; + pivot[nonZero] = pivot[col]; + pivot[col] = temp; + even = !even; + } + + // Divide the lower elements by the "winning" diagonal elt. + final T luDiag = lu[col][col]; + for (int row = col + 1; row < m; row++) { + final T[] luRow = lu[row]; + luRow[col] = luRow[col].divide(luDiag); + } + } + + } + + /** + * Returns the matrix L of the decomposition. + *

    L is a lower-triangular matrix

    + * @return the L matrix (or null if decomposed matrix is singular) + */ + public FieldMatrix getL() { + if ((cachedL == null) && !singular) { + final int m = pivot.length; + cachedL = new Array2DRowFieldMatrix(field, m, m); + for (int i = 0; i < m; ++i) { + final T[] luI = lu[i]; + for (int j = 0; j < i; ++j) { + cachedL.setEntry(i, j, luI[j]); + } + cachedL.setEntry(i, i, field.getOne()); + } + } + return cachedL; + } + + /** + * Returns the matrix U of the decomposition. + *

    U is an upper-triangular matrix

    + * @return the U matrix (or null if decomposed matrix is singular) + */ + public FieldMatrix getU() { + if ((cachedU == null) && !singular) { + final int m = pivot.length; + cachedU = new Array2DRowFieldMatrix(field, m, m); + for (int i = 0; i < m; ++i) { + final T[] luI = lu[i]; + for (int j = i; j < m; ++j) { + cachedU.setEntry(i, j, luI[j]); + } + } + } + return cachedU; + } + + /** + * Returns the P rows permutation matrix. + *

    P 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.

    + *

    The positions of the 1 elements are given by the {@link #getPivot() + * pivot permutation vector}.

    + * @return the P rows permutation matrix (or null if decomposed matrix is singular) + * @see #getPivot() + */ + public FieldMatrix getP() { + if ((cachedP == null) && !singular) { + final int m = pivot.length; + cachedP = new Array2DRowFieldMatrix(field, m, m); + for (int i = 0; i < m; ++i) { + cachedP.setEntry(i, pivot[i], field.getOne()); + } + } + return cachedP; + } + + /** + * Returns the pivot permutation vector. + * @return the pivot permutation vector + * @see #getP() + */ + public int[] getPivot() { + return pivot.clone(); + } + + /** + * Return the determinant of the matrix. + * @return determinant of the matrix + */ + public T getDeterminant() { + if (singular) { + return field.getZero(); + } else { + final int m = pivot.length; + T determinant = even ? field.getOne() : field.getZero().subtract(field.getOne()); + for (int i = 0; i < m; i++) { + determinant = determinant.multiply(lu[i][i]); + } + return determinant; + } + } + + /** + * Get a solver for finding the A × X = B solution in exact linear sense. + * @return a solver + */ + public FieldDecompositionSolver getSolver() { + return new Solver(field, lu, pivot, singular); + } + + /** Specialized solver. + * @param the type of the field elements + */ + private static class Solver> implements FieldDecompositionSolver { + + /** Field to which the elements belong. */ + private final Field 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 field, final T[][] lu, + final int[] pivot, final boolean singular) { + this.field = field; + this.lu = lu; + this.pivot = pivot; + this.singular = singular; + } + + /** {@inheritDoc} */ + public boolean isNonSingular() { + return !singular; + } + + /** {@inheritDoc} */ + public FieldVector solve(FieldVector b) { + try { + return solve((ArrayFieldVector) b); + } catch (ClassCastException cce) { + + final int m = pivot.length; + if (b.getDimension() != m) { + throw new DimensionMismatchException(b.getDimension(), m); + } + if (singular) { + throw new SingularMatrixException(); + } + + // Apply permutations to b + final T[] bp = MathArrays.buildArray(field, m); + for (int row = 0; row < m; row++) { + bp[row] = b.getEntry(pivot[row]); + } + + // Solve LY = b + for (int col = 0; col < m; col++) { + final T bpCol = bp[col]; + for (int i = col + 1; i < m; i++) { + bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col])); + } + } + + // Solve UX = Y + for (int col = m - 1; col >= 0; col--) { + bp[col] = bp[col].divide(lu[col][col]); + final T bpCol = bp[col]; + for (int i = 0; i < col; i++) { + bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col])); + } + } + + return new ArrayFieldVector(field, bp, false); + + } + } + + /** Solve the linear equation A × X = B. + *

    The A matrix is implicit here. It is

    + * @param b right-hand side of the equation A × X = B + * @return a vector X such that A × X = B + * @throws DimensionMismatchException if the matrices dimensions do not match. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public ArrayFieldVector solve(ArrayFieldVector b) { + final int m = pivot.length; + final int length = b.getDimension(); + if (length != m) { + throw new DimensionMismatchException(length, m); + } + if (singular) { + throw new SingularMatrixException(); + } + + // Apply permutations to b + final T[] bp = MathArrays.buildArray(field, m); + for (int row = 0; row < m; row++) { + bp[row] = b.getEntry(pivot[row]); + } + + // Solve LY = b + for (int col = 0; col < m; col++) { + final T bpCol = bp[col]; + for (int i = col + 1; i < m; i++) { + bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col])); + } + } + + // Solve UX = Y + for (int col = m - 1; col >= 0; col--) { + bp[col] = bp[col].divide(lu[col][col]); + final T bpCol = bp[col]; + for (int i = 0; i < col; i++) { + bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col])); + } + } + + return new ArrayFieldVector(bp, false); + } + + /** {@inheritDoc} */ + public FieldMatrix solve(FieldMatrix b) { + final int m = pivot.length; + if (b.getRowDimension() != m) { + throw new DimensionMismatchException(b.getRowDimension(), m); + } + if (singular) { + throw new SingularMatrixException(); + } + + final int nColB = b.getColumnDimension(); + + // Apply permutations to b + final T[][] bp = MathArrays.buildArray(field, m, nColB); + for (int row = 0; row < m; row++) { + final T[] bpRow = bp[row]; + final int pRow = pivot[row]; + for (int col = 0; col < nColB; col++) { + bpRow[col] = b.getEntry(pRow, col); + } + } + + // Solve LY = b + for (int col = 0; col < m; col++) { + final T[] bpCol = bp[col]; + for (int i = col + 1; i < m; i++) { + final T[] bpI = bp[i]; + final T luICol = lu[i][col]; + for (int j = 0; j < nColB; j++) { + bpI[j] = bpI[j].subtract(bpCol[j].multiply(luICol)); + } + } + } + + // Solve UX = Y + for (int col = m - 1; col >= 0; col--) { + final T[] bpCol = bp[col]; + final T luDiag = lu[col][col]; + for (int j = 0; j < nColB; j++) { + bpCol[j] = bpCol[j].divide(luDiag); + } + for (int i = 0; i < col; i++) { + final T[] bpI = bp[i]; + final T luICol = lu[i][col]; + for (int j = 0; j < nColB; j++) { + bpI[j] = bpI[j].subtract(bpCol[j].multiply(luICol)); + } + } + } + + return new Array2DRowFieldMatrix(field, bp, false); + + } + + /** {@inheritDoc} */ + public FieldMatrix getInverse() { + final int m = pivot.length; + final T one = field.getOne(); + FieldMatrix identity = new Array2DRowFieldMatrix(field, m, m); + for (int i = 0; i < m; ++i) { + identity.setEntry(i, i, one); + } + return solve(identity); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrix.java new file mode 100644 index 000000000..9b8406079 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrix.java @@ -0,0 +1,815 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Interface defining field-valued matrix with basic algebraic operations. + *

    + * Matrix element indexing is 0-based -- e.g., getEntry(0, 0) + * returns the element in the first row, first column of the matrix.

    + * + * @param the type of the field elements + */ +public interface FieldMatrix> extends AnyMatrix { + /** + * Get the type of field elements of the matrix. + * + * @return the type of field elements of the matrix. + */ + Field getField(); + + /** + * Create a new FieldMatrix of the same type as the instance with + * the supplied row and column dimensions. + * + * @param rowDimension the number of rows in the new matrix + * @param columnDimension the number of columns in the new matrix + * @return a new matrix of the same type as the instance + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @since 2.0 + */ + FieldMatrix createMatrix(final int rowDimension, final int columnDimension) + throws NotStrictlyPositiveException; + + /** + * Make a (deep) copy of this. + * + * @return a copy of this matrix. + */ + FieldMatrix copy(); + + /** + * Compute the sum of this and m. + * + * @param m Matrix to be added. + * @return {@code this} + {@code m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this} matrix. + */ + FieldMatrix add(FieldMatrix m) throws MatrixDimensionMismatchException; + + /** + * Subtract {@code m} from this matrix. + * + * @param m Matrix to be subtracted. + * @return {@code this} - {@code m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this} matrix. + */ + FieldMatrix subtract(FieldMatrix m) throws MatrixDimensionMismatchException; + + /** + * Increment each entry of this matrix. + * + * @param d Value to be added to each entry. + * @return {@code d} + {@code this}. + */ + FieldMatrix scalarAdd(T d); + + /** + * Multiply each entry by {@code d}. + * + * @param d Value to multiply all entries by. + * @return {@code d} * {@code this}. + */ + FieldMatrix scalarMultiply(T d); + + /** + * Postmultiply this matrix by {@code m}. + * + * @param m Matrix to postmultiply by. + * @return {@code this} * {@code m}. + * @throws DimensionMismatchException if the number of columns of + * {@code this} matrix is not equal to the number of rows of matrix + * {@code m}. + */ + FieldMatrix multiply(FieldMatrix m) throws DimensionMismatchException; + + /** + * Premultiply this matrix by {@code m}. + * + * @param m Matrix to premultiply by. + * @return {@code m} * {@code this}. + * @throws DimensionMismatchException if the number of columns of {@code m} + * differs from the number of rows of {@code this} matrix. + */ + FieldMatrix preMultiply(FieldMatrix m) throws DimensionMismatchException; + + /** + * Returns the result multiplying this with itself p times. + * Depending on the type of the field elements, T, instability for high + * powers might occur. + * + * @param p raise this to power p + * @return this^p + * @throws NotPositiveException if {@code p < 0} + * @throws NonSquareMatrixException if {@code this matrix} is not square + */ + FieldMatrix power(final int p) throws NonSquareMatrixException, + NotPositiveException; + + /** + * Returns matrix entries as a two-dimensional array. + * + * @return a 2-dimensional array of entries. + */ + T[][] getData(); + + /** + * Get a submatrix. Rows and columns are indicated + * counting from 0 to n - 1. + * + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @return the matrix containing the data of the specified rows and columns. + * @throws NumberIsTooSmallException is {@code endRow < startRow} of + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + */ + FieldMatrix getSubMatrix(int startRow, int endRow, int startColumn, int endColumn) + throws NumberIsTooSmallException, OutOfRangeException; + + /** + * Get a submatrix. Rows and columns are indicated + * counting from 0 to n - 1. + * + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @return the matrix containing the data in the + * specified rows and columns. + * @throws NoDataException if {@code selectedRows} or + * {@code selectedColumns} is empty + * @throws NullArgumentException if {@code selectedRows} or + * {@code selectedColumns} is {@code null}. + * @throws OutOfRangeException if row or column selections are not valid. + */ + FieldMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns) + throws NoDataException, NullArgumentException, OutOfRangeException; + + /** + * Copy a submatrix. Rows and columns are 0-based. The designated submatrix + * is copied into the top left portion of the destination array. + * + * @param startRow Initial row index. + * @param endRow Final row index (inclusive). + * @param startColumn Initial column index. + * @param endColumn Final column index (inclusive). + * @param destination The array where the submatrix data should be copied + * (if larger than rows/columns counts, only the upper-left part will be modified). + * @throws MatrixDimensionMismatchException if the dimensions of + * {@code destination} are not large enough to hold the submatrix. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + */ + void copySubMatrix(int startRow, int endRow, int startColumn, int endColumn, + T[][] destination) + throws MatrixDimensionMismatchException, NumberIsTooSmallException, + OutOfRangeException; + + /** + * Copy a submatrix. Rows and columns are indicated + * counting from 0 to n - 1. + * + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @param destination Arrays where the submatrix data should be copied + * (if larger than rows/columns counts, only the upper-left part will be used) + * @throws MatrixDimensionMismatchException if the dimensions of + * {@code destination} do not match those of {@code this}. + * @throws NoDataException if {@code selectedRows} or + * {@code selectedColumns} is empty + * @throws NullArgumentException if {@code selectedRows} or + * {@code selectedColumns} is {@code null}. + * @throws OutOfRangeException if the indices are not valid. + */ + void copySubMatrix(int[] selectedRows, int[] selectedColumns, T[][] destination) + throws MatrixDimensionMismatchException, NoDataException, NullArgumentException, + OutOfRangeException; + + /** + * Replace the submatrix starting at {@code (row, column)} using data in the + * input {@code subMatrix} array. Indexes are 0-based. + *

    + * Example:
    + * Starting with + * + *

    +     * 1  2  3  4
    +     * 5  6  7  8
    +     * 9  0  1  2
    +     * 
    + * + * and subMatrix = {{3, 4} {5,6}}, invoking + * setSubMatrix(subMatrix,1,1)) will result in + * + *
    +     * 1  2  3  4
    +     * 5  3  4  8
    +     * 9  5  6  2
    +     * 
    + * + *

    + * + * @param subMatrix Array containing the submatrix replacement data. + * @param row Row coordinate of the top-left element to be replaced. + * @param column Column coordinate of the top-left element to be replaced. + * @throws OutOfRangeException if {@code subMatrix} does not fit into this + * matrix from element in {@code (row, column)}. + * @throws NoDataException if a row or column of {@code subMatrix} is empty. + * @throws DimensionMismatchException if {@code subMatrix} is not + * rectangular (not all rows have the same length). + * @throws NullArgumentException if {@code subMatrix} is {@code null}. + * @since 2.0 + */ + void setSubMatrix(T[][] subMatrix, int row, int column) + throws DimensionMismatchException, OutOfRangeException, + NoDataException, NullArgumentException; + + /** + * Get the entries in row number {@code row} + * as a row matrix. + * + * @param row Row to be fetched. + * @return a row matrix. + * @throws OutOfRangeException if the specified row index is invalid. + */ + FieldMatrix getRowMatrix(int row) throws OutOfRangeException; + + /** + * Set the entries in row number {@code row} + * as a row matrix. + * + * @param row Row to be set. + * @param matrix Row matrix (must have one row and the same number + * of columns as the instance). + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException + * if the matrix dimensions do not match one instance row. + */ + void setRowMatrix(int row, FieldMatrix matrix) + throws MatrixDimensionMismatchException, OutOfRangeException; + + /** + * Get the entries in column number {@code column} + * as a column matrix. + * + * @param column Column to be fetched. + * @return a column matrix. + * @throws OutOfRangeException if the specified column index is invalid. + */ + FieldMatrix getColumnMatrix(int column) throws OutOfRangeException; + + /** + * Set the entries in column number {@code column} + * as a column matrix. + * + * @param column Column to be set. + * @param matrix column matrix (must have one column and the same + * number of rows as the instance). + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the matrix dimensions do + * not match one instance column. + */ + void setColumnMatrix(int column, FieldMatrix matrix) + throws MatrixDimensionMismatchException, OutOfRangeException; + + /** + * Get the entries in row number {@code row} + * as a vector. + * + * @param row Row to be fetched + * @return a row vector. + * @throws OutOfRangeException if the specified row index is invalid. + */ + FieldVector getRowVector(int row) throws OutOfRangeException; + + /** + * Set the entries in row number {@code row} + * as a vector. + * + * @param row Row to be set. + * @param vector row vector (must have the same number of columns + * as the instance). + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the vector dimension does not + * match one instance row. + */ + void setRowVector(int row, FieldVector vector) + throws MatrixDimensionMismatchException, OutOfRangeException; + + /** + * Returns the entries in column number {@code column} + * as a vector. + * + * @param column Column to be fetched. + * @return a column vector. + * @throws OutOfRangeException if the specified column index is invalid. + */ + FieldVector getColumnVector(int column) throws OutOfRangeException; + + /** + * Set the entries in column number {@code column} + * as a vector. + * + * @param column Column to be set. + * @param vector Column vector (must have the same number of rows + * as the instance). + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the vector dimension does not + * match one instance column. + */ + void setColumnVector(int column, FieldVector vector) + throws MatrixDimensionMismatchException, OutOfRangeException; + + /** + * Get the entries in row number {@code row} as an array. + * + * @param row Row to be fetched. + * @return array of entries in the row. + * @throws OutOfRangeException if the specified row index is not valid. + */ + T[] getRow(int row) throws OutOfRangeException; + + /** + * Set the entries in row number {@code row} + * as a row matrix. + * + * @param row Row to be set. + * @param array Row matrix (must have the same number of columns as + * the instance). + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the array size does not match + * one instance row. + */ + void setRow(int row, T[] array) throws MatrixDimensionMismatchException, + OutOfRangeException; + + /** + * Get the entries in column number {@code col} as an array. + * + * @param column the column to be fetched + * @return array of entries in the column + * @throws OutOfRangeException if the specified column index is not valid. + */ + T[] getColumn(int column) throws OutOfRangeException; + + /** + * Set the entries in column number {@code column} + * as a column matrix. + * + * @param column the column to be set + * @param array column array (must have the same number of rows as the instance) + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the array size does not match + * one instance column. + */ + void setColumn(int column, T[] array) throws MatrixDimensionMismatchException, + OutOfRangeException; + + /** + * Returns the entry in the specified row and column. + * + * @param row row location of entry to be fetched + * @param column column location of entry to be fetched + * @return matrix entry in row,column + * @throws OutOfRangeException if the row or column index is not valid. + */ + T getEntry(int row, int column) throws OutOfRangeException; + + /** + * Set the entry in the specified row and column. + * + * @param row row location of entry to be set + * @param column column location of entry to be set + * @param value matrix entry to be set in row,column + * @throws OutOfRangeException if the row or column index is not valid. + * @since 2.0 + */ + void setEntry(int row, int column, T value) throws OutOfRangeException; + + /** + * Change an entry in the specified row and column. + * + * @param row Row location of entry to be set. + * @param column Column location of entry to be set. + * @param increment Value to add to the current matrix entry in + * {@code (row, column)}. + * @throws OutOfRangeException if the row or column index is not valid. + * @since 2.0 + */ + void addToEntry(int row, int column, T increment) throws OutOfRangeException; + + /** + * Change an entry in the specified row and column. + * + * @param row Row location of entry to be set. + * @param column Column location of entry to be set. + * @param factor Multiplication factor for the current matrix entry + * in {@code (row,column)} + * @throws OutOfRangeException if the row or column index is not valid. + * @since 2.0 + */ + void multiplyEntry(int row, int column, T factor) throws OutOfRangeException; + + /** + * Returns the transpose of this matrix. + * + * @return transpose matrix + */ + FieldMatrix transpose(); + + /** + * Returns the + * trace of the matrix (the sum of the elements on the main diagonal). + * + * @return trace + * @throws NonSquareMatrixException if the matrix is not square. + */ + T getTrace() throws NonSquareMatrixException; + + /** + * Returns the result of multiplying this by the vector {@code v}. + * + * @param v the vector to operate on + * @return {@code this * v} + * @throws DimensionMismatchException if the number of columns of + * {@code this} matrix is not equal to the size of the vector {@code v}. + */ + T[] operate(T[] v) throws DimensionMismatchException; + + /** + * Returns the result of multiplying this by the vector {@code v}. + * + * @param v the vector to operate on + * @return {@code this * v} + * @throws DimensionMismatchException if the number of columns of + * {@code this} matrix is not equal to the size of the vector {@code v}. + */ + FieldVector operate(FieldVector v) throws DimensionMismatchException; + + /** + * Returns the (row) vector result of premultiplying this by the vector + * {@code v}. + * + * @param v the row vector to premultiply by + * @return {@code v * this} + * @throws DimensionMismatchException if the number of rows of {@code this} + * matrix is not equal to the size of the vector {@code v} + */ + T[] preMultiply(T[] v) throws DimensionMismatchException; + + /** + * Returns the (row) vector result of premultiplying this by the vector + * {@code v}. + * + * @param v the row vector to premultiply by + * @return {@code v * this} + * @throws DimensionMismatchException if the number of rows of {@code this} + * matrix is not equal to the size of the vector {@code v} + */ + FieldVector preMultiply(FieldVector v) throws DimensionMismatchException; + + /** + * Visit (and possibly change) all matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInRowOrder(FieldMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInRowOrder(FieldMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInRowOrder(FieldMatrixChangingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (but don't change) some matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInRowOrder(FieldMatrixPreservingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (and possibly change) all matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInColumnOrder(FieldMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInColumnOrder(FieldMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInColumnOrder(FieldMatrixChangingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws NumberIsTooSmallException, OutOfRangeException; + + /** + * Visit (but don't change) some matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInColumnOrder(FieldMatrixPreservingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws NumberIsTooSmallException, OutOfRangeException; + + /** + * Visit (and possibly change) all matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInOptimizedOrder(FieldMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInOptimizedOrder(FieldMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end + * of the walk + */ + T walkInOptimizedOrder(FieldMatrixChangingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws NumberIsTooSmallException, OutOfRangeException; + + /** + * Visit (but don't change) some matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws OutOfRangeException if the indices are not valid. + * @see #walkInRowOrder(FieldMatrixChangingVisitor) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor) + * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor) + * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int) + * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end + * of the walk + */ + T walkInOptimizedOrder(FieldMatrixPreservingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws NumberIsTooSmallException, OutOfRangeException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.java new file mode 100644 index 000000000..7bec106aa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Interface defining a visitor for matrix entries. + * + * @param the type of the field elements + * @since 2.0 + */ +public interface FieldMatrixChangingVisitor> { + /** + * Start visiting a matrix. + *

    This method is called once before any entry of the matrix is visited.

    + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + */ + void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn); + + /** + * Visit one matrix entry. + * @param row row index of the entry + * @param column column index of the entry + * @param value current value of the entry + * @return the new value to be set for the entry + */ + T visit(int row, int column, T value); + + /** + * End visiting a matrix. + *

    This method is called once after all entries of the matrix have been visited.

    + * @return the value that the walkInXxxOrder must return + */ + T end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.java new file mode 100644 index 000000000..339a3a9f6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Interface defining a visitor for matrix entries. + * + * @param the type of the field elements + * @since 2.0 + */ +public interface FieldMatrixPreservingVisitor> { + /** + * Start visiting a matrix. + *

    This method is called once before any entry of the matrix is visited.

    + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + */ + void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn); + + /** + * Visit one matrix entry. + * @param row row index of the entry + * @param column column index of the entry + * @param value current value of the entry + */ + void visit(int row, int column, T value); + + /** + * End visiting a matrix. + *

    This method is called once after all entries of the matrix have been visited.

    + * @return the value that the walkInXxxOrder must return + */ + T end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVector.java new file mode 100644 index 000000000..9adff2b42 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVector.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Interface defining a field-valued vector with basic algebraic operations. + *

    + * vector element indexing is 0-based -- e.g., getEntry(0) + * returns the first element of the vector. + *

    + *

    + * The various mapXxx and mapXxxToSelf 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 mapXxx + * versions create a new vector to hold the result and do not change the instance. + * The mapXxxToSelf 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 fluent API + * style, like this: + *

    + *
    + *   RealVector result = v.mapAddToSelf(3.0).mapTanToSelf().mapSquareToSelf();
    + * 
    + *

    + * Note that as almost all operations on {@link FieldElement} throw {@link + * NullArgumentException} when operating on a null element, it is the responsibility + * of FieldVector implementations to make sure no null elements + * are inserted into the vector. This must be done in all constructors and + * all setters. + *

    + * + * @param the type of the field elements + * @since 2.0 + */ +public interface FieldVector> { + + /** + * Get the type of field elements of the vector. + * @return type of field elements of the vector + */ + Field getField(); + + /** + * Returns a (deep) copy of this. + * @return vector copy + */ + FieldVector copy(); + + /** + * Compute the sum of {@code this} and {@code v}. + * @param v vector to be added + * @return {@code this + v} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + */ + FieldVector add(FieldVector v) throws DimensionMismatchException; + + /** + * Compute {@code this} minus {@code v}. + * @param v vector to be subtracted + * @return {@code this - v} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + */ + FieldVector subtract(FieldVector v) throws DimensionMismatchException; + + /** + * Map an addition operation to each entry. + * @param d value to be added to each entry + * @return {@code this + d} + * @throws NullArgumentException if {@code d} is {@code null}. + */ + FieldVector mapAdd(T d) throws NullArgumentException; + + /** + * Map an addition operation to each entry. + *

    The instance is changed by this method.

    + * @param d value to be added to each entry + * @return for convenience, return {@code this} + * @throws NullArgumentException if {@code d} is {@code null}. + */ + FieldVector mapAddToSelf(T d) throws NullArgumentException; + + /** + * Map a subtraction operation to each entry. + * @param d value to be subtracted to each entry + * @return {@code this - d} + * @throws NullArgumentException if {@code d} is {@code null} + */ + FieldVector mapSubtract(T d) throws NullArgumentException; + + /** + * Map a subtraction operation to each entry. + *

    The instance is changed by this method.

    + * @param d value to be subtracted to each entry + * @return for convenience, return {@code this} + * @throws NullArgumentException if {@code d} is {@code null} + */ + FieldVector mapSubtractToSelf(T d) throws NullArgumentException; + + /** + * Map a multiplication operation to each entry. + * @param d value to multiply all entries by + * @return {@code this * d} + * @throws NullArgumentException if {@code d} is {@code null}. + */ + FieldVector mapMultiply(T d) throws NullArgumentException; + + /** + * Map a multiplication operation to each entry. + *

    The instance is changed by this method.

    + * @param d value to multiply all entries by + * @return for convenience, return {@code this} + * @throws NullArgumentException if {@code d} is {@code null}. + */ + FieldVector mapMultiplyToSelf(T d) throws NullArgumentException; + + /** + * Map a division operation to each entry. + * @param d value to divide all entries by + * @return {@code this / d} + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws MathArithmeticException if {@code d} is zero. + */ + FieldVector mapDivide(T d) + throws NullArgumentException, MathArithmeticException; + + /** + * Map a division operation to each entry. + *

    The instance is changed by this method.

    + * @param d value to divide all entries by + * @return for convenience, return {@code this} + * @throws NullArgumentException if {@code d} is {@code null}. + * @throws MathArithmeticException if {@code d} is zero. + */ + FieldVector mapDivideToSelf(T d) + throws NullArgumentException, MathArithmeticException; + + /** + * Map the 1/x function to each entry. + * @return a vector containing the result of applying the function to each entry. + * @throws MathArithmeticException if one of the entries is zero. + */ + FieldVector mapInv() throws MathArithmeticException; + + /** + * Map the 1/x function to each entry. + *

    The instance is changed by this method.

    + * @return for convenience, return {@code this} + * @throws MathArithmeticException if one of the entries is zero. + */ + FieldVector mapInvToSelf() throws MathArithmeticException; + + /** + * Element-by-element multiplication. + * @param v vector by which instance elements must be multiplied + * @return a vector containing {@code this[i] * v[i]} for all {@code i} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + */ + FieldVector ebeMultiply(FieldVector v) + throws DimensionMismatchException; + + /** + * Element-by-element division. + * @param v vector by which instance elements must be divided + * @return a vector containing {@code this[i] / v[i]} for all {@code i} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + * @throws MathArithmeticException if one entry of {@code v} is zero. + */ + FieldVector ebeDivide(FieldVector v) + throws DimensionMismatchException, MathArithmeticException; + + /** + * Returns vector entries as a T array. + * @return T array of entries + * @deprecated as of 3.1, to be removed in 4.0. Please use the {@link #toArray()} method instead. + */ + @Deprecated + T[] getData(); + + /** + * Compute the dot product. + * @param v vector with which dot product should be computed + * @return the scalar dot product of {@code this} and {@code v} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + */ + T dotProduct(FieldVector v) throws DimensionMismatchException; + + /** + * Find the orthogonal projection of this vector onto another vector. + * @param v vector onto which {@code this} must be projected + * @return projection of {@code this} onto {@code v} + * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} + * @throws MathArithmeticException if {@code v} is the null vector. + */ + FieldVector projection(FieldVector v) + throws DimensionMismatchException, MathArithmeticException; + + /** + * Compute the outer product. + * @param v vector with which outer product should be computed + * @return the matrix outer product between instance and v + */ + FieldMatrix outerProduct(FieldVector v); + + /** + * Returns the entry in the specified index. + * + * @param index Index location of entry to be fetched. + * @return the vector entry at {@code index}. + * @throws OutOfRangeException if the index is not valid. + * @see #setEntry(int, FieldElement) + */ + T getEntry(int index) throws OutOfRangeException; + + /** + * Set a single element. + * @param index element index. + * @param value new value for the element. + * @throws OutOfRangeException if the index is not valid. + * @see #getEntry(int) + */ + void setEntry(int index, T value) throws OutOfRangeException; + + /** + * Returns the size of the vector. + * @return size + */ + int getDimension(); + + /** + * Construct a vector by appending a vector to this vector. + * @param v vector to append to this one. + * @return a new vector + */ + FieldVector append(FieldVector v); + + /** + * Construct a vector by appending a T to this vector. + * @param d T to append. + * @return a new vector + */ + FieldVector append(T d); + + /** + * Get a subvector from consecutive elements. + * @param index index of first element. + * @param n number of elements to be retrieved. + * @return a vector containing n elements. + * @throws OutOfRangeException if the index is not valid. + * @throws NotPositiveException if the number of elements if not positive. + */ + FieldVector getSubVector(int index, int n) + throws OutOfRangeException, NotPositiveException; + + /** + * Set a set of consecutive elements. + * @param index index of first element to be set. + * @param v vector containing the values to set. + * @throws OutOfRangeException if the index is not valid. + */ + void setSubVector(int index, FieldVector v) throws OutOfRangeException; + + /** + * Set all elements to a single value. + * @param value single value to set for all elements + */ + void set(T value); + + /** + * Convert the vector to a T array. + *

    The array is independent from vector data, it's elements + * are copied.

    + * @return array containing a copy of vector elements + */ + T[] toArray(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorChangingVisitor.java new file mode 100644 index 000000000..67b367186 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorChangingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * This interface defines a visitor for the entries of a vector. Visitors + * implementing this interface may alter the entries of the vector being + * visited. + * + * @param the type of the field elements + * @since 3.3 + */ +public interface FieldVectorChangingVisitor> { + /** + * Start visiting a vector. This method is called once, before any entry + * of the vector is visited. + * + * @param dimension the size of the vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + */ + void start(int dimension, int start, int end); + + /** + * Visit one entry of the vector. + * + * @param index the index of the entry being visited + * @param value the value of the entry being visited + * @return the new value of the entry being visited + */ + T visit(int index, T value); + + /** + * End visiting a vector. This method is called once, after all entries of + * the vector have been visited. + * + * @return the value returned after visiting all entries + */ + T end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.java new file mode 100644 index 000000000..04ff067ff --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * This interface defines a visitor for the entries of a vector. Visitors + * implementing this interface do not alter the entries of the vector being + * visited. + * + * @param the type of the field elements + * @since 3.3 + */ +public interface FieldVectorPreservingVisitor> { + /** + * Start visiting a vector. This method is called once, before any entry + * of the vector is visited. + * + * @param dimension the size of the vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + */ + void start(int dimension, int start, int end); + + /** + * Visit one entry of the vector. + * + * @param index the index of the entry being visited + * @param value the value of the entry being visited + */ + void visit(int index, T value); + + /** + * End visiting a vector. This method is called once, after all entries of + * the vector have been visited. + * + * @return the value returned after visiting all entries + */ + T end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/HessenbergTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/HessenbergTransformer.java new file mode 100644 index 000000000..dcf3b2557 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/HessenbergTransformer.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Class transforming a general real matrix to Hessenberg form. + *

    A m × m matrix A can be written as the product of three matrices: A = P + * × H × PT with P an orthogonal matrix and H a Hessenberg + * matrix. Both P and H are m × m matrices.

    + *

    Transformation to Hessenberg form is often not a goal by itself, but it is an + * intermediate step in more general decomposition algorithms like + * {@link EigenDecomposition eigen decomposition}. This class is therefore + * intended for internal use by the library and is not public. As a consequence + * of this explicitly limited scope, many methods directly returns references to + * internal arrays, not copies.

    + *

    This class is based on the method orthes in class EigenvalueDecomposition + * from the JAMA library.

    + * + * @see MathWorld + * @see Householder Transformations + * @since 3.1 + */ +class HessenbergTransformer { + /** Householder vectors. */ + private final double householderVectors[][]; + /** Temporary storage vector. */ + private final double ort[]; + /** Cached value of P. */ + private RealMatrix cachedP; + /** Cached value of Pt. */ + private RealMatrix cachedPt; + /** Cached value of H. */ + private RealMatrix cachedH; + + /** + * Build the transformation to Hessenberg form of a general matrix. + * + * @param matrix matrix to transform + * @throws NonSquareMatrixException if the matrix is not square + */ + HessenbergTransformer(final RealMatrix matrix) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + final int m = matrix.getRowDimension(); + householderVectors = matrix.getData(); + ort = new double[m]; + cachedP = null; + cachedPt = null; + cachedH = null; + + // transform matrix + transform(); + } + + /** + * Returns the matrix P of the transform. + *

    P is an orthogonal matrix, i.e. its inverse is also its transpose.

    + * + * @return the P matrix + */ + public RealMatrix getP() { + if (cachedP == null) { + final int n = householderVectors.length; + final int high = n - 1; + final double[][] pa = new double[n][n]; + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + pa[i][j] = (i == j) ? 1 : 0; + } + } + + for (int m = high - 1; m >= 1; m--) { + if (householderVectors[m][m - 1] != 0.0) { + for (int i = m + 1; i <= high; i++) { + ort[i] = householderVectors[i][m - 1]; + } + + for (int j = m; j <= high; j++) { + double g = 0.0; + + for (int i = m; i <= high; i++) { + g += ort[i] * pa[i][j]; + } + + // Double division avoids possible underflow + g = (g / ort[m]) / householderVectors[m][m - 1]; + + for (int i = m; i <= high; i++) { + pa[i][j] += g * ort[i]; + } + } + } + } + + cachedP = MatrixUtils.createRealMatrix(pa); + } + return cachedP; + } + + /** + * Returns the transpose of the matrix P of the transform. + *

    P is an orthogonal matrix, i.e. its inverse is also its transpose.

    + * + * @return the transpose of the P matrix + */ + public RealMatrix getPT() { + if (cachedPt == null) { + cachedPt = getP().transpose(); + } + + // return the cached matrix + return cachedPt; + } + + /** + * Returns the Hessenberg matrix H of the transform. + * + * @return the H matrix + */ + public RealMatrix getH() { + if (cachedH == null) { + final int m = householderVectors.length; + final double[][] h = new double[m][m]; + for (int i = 0; i < m; ++i) { + if (i > 0) { + // copy the entry of the lower sub-diagonal + h[i][i - 1] = householderVectors[i][i - 1]; + } + + // copy upper triangular part of the matrix + for (int j = i; j < m; ++j) { + h[i][j] = householderVectors[i][j]; + } + } + cachedH = MatrixUtils.createRealMatrix(h); + } + + // return the cached matrix + return cachedH; + } + + /** + * Get the Householder vectors of the transform. + *

    Note that since this class is only intended for internal use, it returns + * directly a reference to its internal arrays, not a copy.

    + * + * @return the main diagonal elements of the B matrix + */ + double[][] getHouseholderVectorsRef() { + return householderVectors; + } + + /** + * Transform original matrix to Hessenberg form. + *

    Transformation is done using Householder transforms.

    + */ + private void transform() { + final int n = householderVectors.length; + final int high = n - 1; + + for (int m = 1; m <= high - 1; m++) { + // Scale column. + double scale = 0; + for (int i = m; i <= high; i++) { + scale += FastMath.abs(householderVectors[i][m - 1]); + } + + if (!Precision.equals(scale, 0)) { + // Compute Householder transformation. + double h = 0; + for (int i = high; i >= m; i--) { + ort[i] = householderVectors[i][m - 1] / scale; + h += ort[i] * ort[i]; + } + final double g = (ort[m] > 0) ? -FastMath.sqrt(h) : FastMath.sqrt(h); + + h -= ort[m] * g; + ort[m] -= g; + + // Apply Householder similarity transformation + // H = (I - u*u' / h) * H * (I - u*u' / h) + + for (int j = m; j < n; j++) { + double f = 0; + for (int i = high; i >= m; i--) { + f += ort[i] * householderVectors[i][j]; + } + f /= h; + for (int i = m; i <= high; i++) { + householderVectors[i][j] -= f * ort[i]; + } + } + + for (int i = 0; i <= high; i++) { + double f = 0; + for (int j = high; j >= m; j--) { + f += ort[j] * householderVectors[i][j]; + } + f /= h; + for (int j = m; j <= high; j++) { + householderVectors[i][j] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + householderVectors[m][m - 1] = scale * g; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IllConditionedOperatorException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IllConditionedOperatorException.java new file mode 100644 index 000000000..4aeb6496c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IllConditionedOperatorException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * An exception to be thrown when the condition number of a + * {@link RealLinearOperator} is too high. + * + * @since 3.0 + */ +public class IllConditionedOperatorException + extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -7883263944530490135L; + + /** + * Creates a new instance of this class. + * + * @param cond An estimate of the condition number of the offending linear + * operator. + */ + public IllConditionedOperatorException(final double cond) { + super(LocalizedFormats.ILL_CONDITIONED_OPERATOR, cond); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolver.java new file mode 100644 index 000000000..d40718dff --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolver.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.IterationManager; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * This abstract class defines an iterative solver for the linear system A + * · x = b. In what follows, the residual r is defined as r = b + * - A · x, where A is the linear operator of the linear system, b is the + * right-hand side vector, and x the current estimate of the solution. + * + * @since 3.0 + */ +public abstract class IterativeLinearSolver { + + /** The object in charge of managing the iterations. */ + private final IterationManager manager; + + /** + * Creates a new instance of this class, with default iteration manager. + * + * @param maxIterations the maximum number of iterations + */ + public IterativeLinearSolver(final int maxIterations) { + this.manager = new IterationManager(maxIterations); + } + + /** + * Creates a new instance of this class, with custom iteration manager. + * + * @param manager the custom iteration manager + * @throws NullArgumentException if {@code manager} is {@code null} + */ + public IterativeLinearSolver(final IterationManager manager) + throws NullArgumentException { + MathUtils.checkNotNull(manager); + this.manager = manager; + } + + /** + * Performs all dimension checks on the parameters of + * {@link #solve(RealLinearOperator, RealVector, RealVector) solve} and + * {@link #solveInPlace(RealLinearOperator, RealVector, RealVector) solveInPlace}, + * and throws an exception if one of the checks fails. + * + * @param a the linear operator A of the system + * @param b the right-hand side vector + * @param x0 the initial guess of the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} is not square + * @throws DimensionMismatchException if {@code b} or {@code x0} have + * dimensions inconsistent with {@code a} + */ + protected static void checkParameters(final RealLinearOperator a, + final RealVector b, final RealVector x0) throws + NullArgumentException, NonSquareOperatorException, + DimensionMismatchException { + MathUtils.checkNotNull(a); + MathUtils.checkNotNull(b); + MathUtils.checkNotNull(x0); + if (a.getRowDimension() != a.getColumnDimension()) { + throw new NonSquareOperatorException(a.getRowDimension(), + a.getColumnDimension()); + } + if (b.getDimension() != a.getRowDimension()) { + throw new DimensionMismatchException(b.getDimension(), + a.getRowDimension()); + } + if (x0.getDimension() != a.getColumnDimension()) { + throw new DimensionMismatchException(x0.getDimension(), + a.getColumnDimension()); + } + } + + /** + * Returns the iteration manager attached to this solver. + * + * @return the manager + */ + public IterationManager getIterationManager() { + return manager; + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. + * + * @param a the linear operator A of the system + * @param b the right-hand side vector + * @return a new vector containing the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} is not square + * @throws DimensionMismatchException if {@code b} has dimensions + * inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public RealVector solve(final RealLinearOperator a, final RealVector b) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + x.set(0.); + return solveInPlace(a, b, x); + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. + * + * @param a the linear operator A of the system + * @param b the right-hand side vector + * @param x0 the initial guess of the solution + * @return a new vector containing the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} is not square + * @throws DimensionMismatchException if {@code b} or {@code x0} have + * dimensions inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public RealVector solve(RealLinearOperator a, RealVector b, RealVector x0) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(x0); + return solveInPlace(a, b, x0.copy()); + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. The solution is computed in-place (initial guess is modified). + * + * @param a the linear operator A of the system + * @param b the right-hand side vector + * @param x0 initial guess of the solution + * @return a reference to {@code x0} (shallow copy) updated with the + * solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} is not square + * @throws DimensionMismatchException if {@code b} or {@code x0} have + * dimensions inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public abstract RealVector solveInPlace(RealLinearOperator a, RealVector b, + RealVector x0) throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolverEvent.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolverEvent.java new file mode 100644 index 000000000..7d267810c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/IterativeLinearSolverEvent.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.util.IterationEvent; + +/** + * This is the base class for all events occurring during the iterations of a + * {@link IterativeLinearSolver}. + * + * @since 3.0 + */ +public abstract class IterativeLinearSolverEvent + extends IterationEvent { + /** Serialization identifier. */ + private static final long serialVersionUID = 20120129L; + + /** + * Creates a new instance of this class. + * + * @param source the iterative algorithm on which the event initially + * occurred + * @param iterations the number of iterations performed at the time + * {@code this} event is created + */ + public IterativeLinearSolverEvent(final Object source, final int iterations) { + super(source, iterations); + } + + /** + * Returns the current right-hand side of the linear system to be solved. + * This method should return an unmodifiable view, or a deep copy of the + * actual right-hand side vector, in order not to compromise subsequent + * iterations of the source {@link IterativeLinearSolver}. + * + * @return the right-hand side vector, b + */ + public abstract RealVector getRightHandSideVector(); + + /** + * Returns the norm of the residual. The returned value is not required to + * be exact. Instead, the norm of the so-called updated + * residual (if available) should be returned. For example, the + * {@link ConjugateGradient conjugate gradient} method computes a sequence + * of residuals, the norm of which is cheap to compute. However, due to + * accumulation of round-off errors, this residual might differ from the + * true residual after some iterations. See e.g. A. Greenbaum and + * Z. Strakos, Predicting the Behavior of Finite Precision Lanzos and + * Conjugate Gradient Computations, Technical Report 538, Department of + * Computer Science, New York University, 1991 (available + * here). + * + * @return the norm of the residual, ||r|| + */ + public abstract double getNormOfResidual(); + + /** + *

    + * Returns the residual. This is an optional operation, as all iterative + * linear solvers do not provide cheap estimate of the updated residual + * vector, in which case + *

    + *
      + *
    • this method should throw a + * {@link MathUnsupportedOperationException},
    • + *
    • {@link #providesResidual()} returns {@code false}.
    • + *
    + *

    + * The default implementation throws a + * {@link MathUnsupportedOperationException}. If this method is overriden, + * then {@link #providesResidual()} should be overriden as well. + *

    + * + * @return the updated residual, r + */ + public RealVector getResidual() { + throw new MathUnsupportedOperationException(); + } + + /** + * Returns the current estimate of the solution to the linear system to be + * solved. This method should return an unmodifiable view, or a deep copy of + * the actual current solution, in order not to compromise subsequent + * iterations of the source {@link IterativeLinearSolver}. + * + * @return the solution, x + */ + public abstract RealVector getSolution(); + + /** + * Returns {@code true} if {@link #getResidual()} is supported. The default + * implementation returns {@code false}. + * + * @return {@code false} if {@link #getResidual()} throws a + * {@link MathUnsupportedOperationException} + */ + public boolean providesResidual() { + return false; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/JacobiPreconditioner.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/JacobiPreconditioner.java new file mode 100644 index 000000000..9a62ef7e2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/JacobiPreconditioner.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.analysis.function.Sqrt; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * This class implements the standard Jacobi (diagonal) preconditioner. For a + * matrix Aij, this preconditioner is + * M = diag(1 / A11, 1 / A22, …). + * + * @since 3.0 + */ +public class JacobiPreconditioner extends RealLinearOperator { + + /** The diagonal coefficients of the preconditioner. */ + private final ArrayRealVector diag; + + /** + * Creates a new instance of this class. + * + * @param diag the diagonal coefficients of the linear operator to be + * preconditioned + * @param deep {@code true} if a deep copy of the above array should be + * performed + */ + public JacobiPreconditioner(final double[] diag, final boolean deep) { + this.diag = new ArrayRealVector(diag, deep); + } + + /** + * Creates a new instance of this class. This method extracts the diagonal + * coefficients of the specified linear operator. If {@code a} does not + * extend {@link AbstractRealMatrix}, then the coefficients of the + * underlying matrix are not accessible, coefficient extraction is made by + * matrix-vector products with the basis vectors (and might therefore take + * some time). With matrices, direct entry access is carried out. + * + * @param a the linear operator for which the preconditioner should be built + * @return the diagonal preconditioner made of the inverse of the diagonal + * coefficients of the specified linear operator + * @throws NonSquareOperatorException if {@code a} is not square + */ + public static JacobiPreconditioner create(final RealLinearOperator a) + throws NonSquareOperatorException { + final int n = a.getColumnDimension(); + if (a.getRowDimension() != n) { + throw new NonSquareOperatorException(a.getRowDimension(), n); + } + final double[] diag = new double[n]; + if (a instanceof AbstractRealMatrix) { + final AbstractRealMatrix m = (AbstractRealMatrix) a; + for (int i = 0; i < n; i++) { + diag[i] = m.getEntry(i, i); + } + } else { + final ArrayRealVector x = new ArrayRealVector(n); + for (int i = 0; i < n; i++) { + x.set(0.); + x.setEntry(i, 1.); + diag[i] = a.operate(x).getEntry(i); + } + } + return new JacobiPreconditioner(diag, false); + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return diag.getDimension(); + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return diag.getDimension(); + } + + /** {@inheritDoc} */ + @Override + public RealVector operate(final RealVector x) { + // Dimension check is carried out by ebeDivide + return new ArrayRealVector(MathArrays.ebeDivide(x.toArray(), + diag.toArray()), + false); + } + + /** + * Returns the square root of {@code this} diagonal operator. More + * precisely, this method returns + * P = diag(1 / √A11, 1 / √A22, …). + * + * @return the square root of {@code this} preconditioner + * @since 3.1 + */ + public RealLinearOperator sqrt() { + final RealVector sqrtDiag = diag.map(new Sqrt()); + return new RealLinearOperator() { + /** {@inheritDoc} */ + @Override + public RealVector operate(final RealVector x) { + return new ArrayRealVector(MathArrays.ebeDivide(x.toArray(), + sqrtDiag.toArray()), + false); + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return sqrtDiag.getDimension(); + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return sqrtDiag.getDimension(); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/LUDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/LUDecomposition.java new file mode 100644 index 000000000..9c3dd437a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/LUDecomposition.java @@ -0,0 +1,390 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Calculates the LUP-decomposition of a square matrix. + *

    The LUP-decomposition of a matrix A consists of three matrices L, U and + * P that satisfy: P×A = L×U. L is lower triangular (with unit + * diagonal terms), U is upper triangular and P is a permutation matrix. All + * matrices are m×m.

    + *

    As shown by the presence of the P matrix, this decomposition is + * implemented using partial pivoting.

    + *

    This class is based on the class with similar name from the + * JAMA library.

    + *
      + *
    • a {@link #getP() getP} method has been added,
    • + *
    • the {@code det} method has been renamed as {@link #getDeterminant() + * getDeterminant},
    • + *
    • the {@code getDoublePivot} method has been removed (but the int based + * {@link #getPivot() getPivot} method has been kept),
    • + *
    • the {@code solve} and {@code isNonSingular} methods have been replaced + * by a {@link #getSolver() getSolver} method and the equivalent methods + * provided by the returned {@link DecompositionSolver}.
    • + *
    + * + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class LUDecomposition { + /** Default bound to determine effective singularity in LU decomposition. */ + private static final double DEFAULT_TOO_SMALL = 1e-11; + /** Entries of LU decomposition. */ + private final double[][] lu; + /** Pivot permutation associated with LU decomposition. */ + private final int[] pivot; + /** Parity of the permutation associated with the LU decomposition. */ + private boolean even; + /** Singularity indicator. */ + private boolean singular; + /** Cached value of L. */ + private RealMatrix cachedL; + /** Cached value of U. */ + private RealMatrix cachedU; + /** Cached value of P. */ + private RealMatrix cachedP; + + /** + * Calculates the LU-decomposition of the given matrix. + * This constructor uses 1e-11 as default value for the singularity + * threshold. + * + * @param matrix Matrix to decompose. + * @throws NonSquareMatrixException if matrix is not square. + */ + public LUDecomposition(RealMatrix matrix) { + this(matrix, DEFAULT_TOO_SMALL); + } + + /** + * Calculates the LU-decomposition of the given matrix. + * @param matrix The matrix to decompose. + * @param singularityThreshold threshold (based on partial row norm) + * under which a matrix is considered singular + * @throws NonSquareMatrixException if matrix is not square + */ + public LUDecomposition(RealMatrix matrix, double singularityThreshold) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + final int m = matrix.getColumnDimension(); + lu = matrix.getData(); + pivot = new int[m]; + cachedL = null; + cachedU = null; + cachedP = null; + + // Initialize permutation array and parity + for (int row = 0; row < m; row++) { + pivot[row] = row; + } + even = true; + singular = false; + + // Loop over columns + for (int col = 0; col < m; col++) { + + // upper + for (int row = 0; row < col; row++) { + final double[] luRow = lu[row]; + double sum = luRow[col]; + for (int i = 0; i < row; i++) { + sum -= luRow[i] * lu[i][col]; + } + luRow[col] = sum; + } + + // lower + int max = col; // permutation row + double largest = Double.NEGATIVE_INFINITY; + for (int row = col; row < m; row++) { + final double[] luRow = lu[row]; + double sum = luRow[col]; + for (int i = 0; i < col; i++) { + sum -= luRow[i] * lu[i][col]; + } + luRow[col] = sum; + + // maintain best permutation choice + if (FastMath.abs(sum) > largest) { + largest = FastMath.abs(sum); + max = row; + } + } + + // Singularity check + if (FastMath.abs(lu[max][col]) < singularityThreshold) { + singular = true; + return; + } + + // Pivot if necessary + if (max != col) { + double tmp = 0; + final double[] luMax = lu[max]; + final double[] luCol = lu[col]; + for (int i = 0; i < m; i++) { + tmp = luMax[i]; + luMax[i] = luCol[i]; + luCol[i] = tmp; + } + int temp = pivot[max]; + pivot[max] = pivot[col]; + pivot[col] = temp; + even = !even; + } + + // Divide the lower elements by the "winning" diagonal elt. + final double luDiag = lu[col][col]; + for (int row = col + 1; row < m; row++) { + lu[row][col] /= luDiag; + } + } + } + + /** + * Returns the matrix L of the decomposition. + *

    L is a lower-triangular matrix

    + * @return the L matrix (or null if decomposed matrix is singular) + */ + public RealMatrix getL() { + if ((cachedL == null) && !singular) { + final int m = pivot.length; + cachedL = MatrixUtils.createRealMatrix(m, m); + for (int i = 0; i < m; ++i) { + final double[] luI = lu[i]; + for (int j = 0; j < i; ++j) { + cachedL.setEntry(i, j, luI[j]); + } + cachedL.setEntry(i, i, 1.0); + } + } + return cachedL; + } + + /** + * Returns the matrix U of the decomposition. + *

    U is an upper-triangular matrix

    + * @return the U matrix (or null if decomposed matrix is singular) + */ + public RealMatrix getU() { + if ((cachedU == null) && !singular) { + final int m = pivot.length; + cachedU = MatrixUtils.createRealMatrix(m, m); + for (int i = 0; i < m; ++i) { + final double[] luI = lu[i]; + for (int j = i; j < m; ++j) { + cachedU.setEntry(i, j, luI[j]); + } + } + } + return cachedU; + } + + /** + * Returns the P rows permutation matrix. + *

    P 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.

    + *

    The positions of the 1 elements are given by the {@link #getPivot() + * pivot permutation vector}.

    + * @return the P rows permutation matrix (or null if decomposed matrix is singular) + * @see #getPivot() + */ + public RealMatrix getP() { + if ((cachedP == null) && !singular) { + final int m = pivot.length; + cachedP = MatrixUtils.createRealMatrix(m, m); + for (int i = 0; i < m; ++i) { + cachedP.setEntry(i, pivot[i], 1.0); + } + } + return cachedP; + } + + /** + * Returns the pivot permutation vector. + * @return the pivot permutation vector + * @see #getP() + */ + public int[] getPivot() { + return pivot.clone(); + } + + /** + * Return the determinant of the matrix + * @return determinant of the matrix + */ + public double getDeterminant() { + if (singular) { + return 0; + } else { + final int m = pivot.length; + double determinant = even ? 1 : -1; + for (int i = 0; i < m; i++) { + determinant *= lu[i][i]; + } + return determinant; + } + } + + /** + * Get a solver for finding the A × X = B solution in exact linear + * sense. + * @return a solver + */ + public DecompositionSolver getSolver() { + return new Solver(lu, pivot, singular); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + + /** Entries of LU decomposition. */ + private final double[][] lu; + + /** Pivot permutation associated with LU decomposition. */ + private final int[] pivot; + + /** Singularity indicator. */ + private final boolean singular; + + /** + * Build a solver from decomposed matrix. + * @param lu entries of LU decomposition + * @param pivot pivot permutation associated with LU decomposition + * @param singular singularity indicator + */ + private Solver(final double[][] lu, final int[] pivot, final boolean singular) { + this.lu = lu; + this.pivot = pivot; + this.singular = singular; + } + + /** {@inheritDoc} */ + public boolean isNonSingular() { + return !singular; + } + + /** {@inheritDoc} */ + public RealVector solve(RealVector b) { + final int m = pivot.length; + if (b.getDimension() != m) { + throw new DimensionMismatchException(b.getDimension(), m); + } + if (singular) { + throw new SingularMatrixException(); + } + + final double[] bp = new double[m]; + + // Apply permutations to b + for (int row = 0; row < m; row++) { + bp[row] = b.getEntry(pivot[row]); + } + + // Solve LY = b + for (int col = 0; col < m; col++) { + final double bpCol = bp[col]; + for (int i = col + 1; i < m; i++) { + bp[i] -= bpCol * lu[i][col]; + } + } + + // Solve UX = Y + for (int col = m - 1; col >= 0; col--) { + bp[col] /= lu[col][col]; + final double bpCol = bp[col]; + for (int i = 0; i < col; i++) { + bp[i] -= bpCol * lu[i][col]; + } + } + + return new ArrayRealVector(bp, false); + } + + /** {@inheritDoc} */ + public RealMatrix solve(RealMatrix b) { + + final int m = pivot.length; + if (b.getRowDimension() != m) { + throw new DimensionMismatchException(b.getRowDimension(), m); + } + if (singular) { + throw new SingularMatrixException(); + } + + final int nColB = b.getColumnDimension(); + + // Apply permutations to b + final double[][] bp = new double[m][nColB]; + for (int row = 0; row < m; row++) { + final double[] bpRow = bp[row]; + final int pRow = pivot[row]; + for (int col = 0; col < nColB; col++) { + bpRow[col] = b.getEntry(pRow, col); + } + } + + // Solve LY = b + for (int col = 0; col < m; col++) { + final double[] bpCol = bp[col]; + for (int i = col + 1; i < m; i++) { + final double[] bpI = bp[i]; + final double luICol = lu[i][col]; + for (int j = 0; j < nColB; j++) { + bpI[j] -= bpCol[j] * luICol; + } + } + } + + // Solve UX = Y + for (int col = m - 1; col >= 0; col--) { + final double[] bpCol = bp[col]; + final double luDiag = lu[col][col]; + for (int j = 0; j < nColB; j++) { + bpCol[j] /= luDiag; + } + for (int i = 0; i < col; i++) { + final double[] bpI = bp[i]; + final double luICol = lu[i][col]; + for (int j = 0; j < nColB; j++) { + bpI[j] -= bpCol[j] * luICol; + } + } + } + + return new Array2DRowRealMatrix(bp, false); + } + + /** + * Get the inverse of the decomposed matrix. + * + * @return the inverse matrix. + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public RealMatrix getInverse() { + return solve(MatrixUtils.createRealIdentityMatrix(pivot.length)); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixDimensionMismatchException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixDimensionMismatchException.java new file mode 100644 index 000000000..de9145171 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixDimensionMismatchException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MultiDimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when either the number of rows or the number of + * columns of a matrix do not match the expected values. + * + * @since 3.0 + */ +public class MatrixDimensionMismatchException extends MultiDimensionMismatchException { + /** Serializable version Id. */ + private static final long serialVersionUID = -8415396756375798143L; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrongRowDim Wrong row dimension. + * @param wrongColDim Wrong column dimension. + * @param expectedRowDim Expected row dimension. + * @param expectedColDim Expected column dimension. + */ + public MatrixDimensionMismatchException(int wrongRowDim, + int wrongColDim, + int expectedRowDim, + int expectedColDim) { + super(LocalizedFormats.DIMENSIONS_MISMATCH_2x2, + new Integer[] { wrongRowDim, wrongColDim }, + new Integer[] { expectedRowDim, expectedColDim }); + } + + /** + * @return the expected row dimension. + */ + public int getWrongRowDimension() { + return getWrongDimension(0); + } + /** + * @return the expected row dimension. + */ + public int getExpectedRowDimension() { + return getExpectedDimension(0); + } + /** + * @return the wrong column dimension. + */ + public int getWrongColumnDimension() { + return getWrongDimension(1); + } + /** + * @return the expected column dimension. + */ + public int getExpectedColumnDimension() { + return getExpectedDimension(1); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixUtils.java new file mode 100644 index 000000000..5e050cb5a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/MatrixUtils.java @@ -0,0 +1,1119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.fraction.Fraction; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * A collection of static methods that operate on or return matrices. + * + */ +public class MatrixUtils { + + /** + * The default format for {@link RealMatrix} objects. + * @since 3.1 + */ + public static final RealMatrixFormat DEFAULT_FORMAT = RealMatrixFormat.getInstance(); + + /** + * A format for {@link RealMatrix} objects compatible with octave. + * @since 3.1 + */ + public static final RealMatrixFormat OCTAVE_FORMAT = new RealMatrixFormat("[", "]", "", "", "; ", ", "); + + /** + * Private constructor. + */ + private MatrixUtils() { + super(); + } + + /** + * Returns a {@link RealMatrix} with specified dimensions. + *

    The type of matrix returned depends on the dimension. Below + * 212 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.

    + *

    The matrix elements are all set to 0.0.

    + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @return RealMatrix with specified dimensions + * @see #createRealMatrix(double[][]) + */ + public static RealMatrix createRealMatrix(final int rows, final int columns) { + return (rows * columns <= 4096) ? + new Array2DRowRealMatrix(rows, columns) : new BlockRealMatrix(rows, columns); + } + + /** + * Returns a {@link FieldMatrix} with specified dimensions. + *

    The type of matrix returned depends on the dimension. Below + * 212 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.

    + *

    The matrix elements are all set to field.getZero().

    + * @param 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 > FieldMatrix createFieldMatrix(final Field field, + final int rows, + final int columns) { + return (rows * columns <= 4096) ? + new Array2DRowFieldMatrix(field, rows, columns) : new BlockFieldMatrix(field, rows, columns); + } + + /** + * Returns a {@link RealMatrix} whose entries are the the values in the + * the input array. + *

    The type of matrix returned depends on the dimension. Below + * 212 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.

    + *

    The input array is copied, not referenced.

    + * + * @param data input array + * @return RealMatrix containing the values of the array + * @throws DimensionMismatchException + * if {@code data} is not rectangular (not all rows have the same length). + * @throws NoDataException if a row or column is empty. + * @throws NullArgumentException if either {@code data} or {@code data[0]} + * is {@code null}. + * @throws DimensionMismatchException if {@code data} is not rectangular. + * @see #createRealMatrix(int, int) + */ + public static RealMatrix createRealMatrix(double[][] data) + throws NullArgumentException, DimensionMismatchException, + NoDataException { + if (data == null || + data[0] == null) { + throw new NullArgumentException(); + } + return (data.length * data[0].length <= 4096) ? + new Array2DRowRealMatrix(data) : new BlockRealMatrix(data); + } + + /** + * Returns a {@link FieldMatrix} whose entries are the the values in the + * the input array. + *

    The type of matrix returned depends on the dimension. Below + * 212 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.

    + *

    The input array is copied, not referenced.

    + * @param the type of the field elements + * @param data input array + * @return a matrix containing the values of the array. + * @throws DimensionMismatchException + * if {@code data} is not rectangular (not all rows have the same length). + * @throws NoDataException if a row or column is empty. + * @throws NullArgumentException if either {@code data} or {@code data[0]} + * is {@code null}. + * @see #createFieldMatrix(Field, int, int) + * @since 2.0 + */ + public static > FieldMatrix createFieldMatrix(T[][] data) + throws DimensionMismatchException, NoDataException, NullArgumentException { + if (data == null || + data[0] == null) { + throw new NullArgumentException(); + } + return (data.length * data[0].length <= 4096) ? + new Array2DRowFieldMatrix(data) : new BlockFieldMatrix(data); + } + + /** + * Returns dimension x dimension 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 dimension x dimension identity matrix. + * + * @param 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 > FieldMatrix + createFieldIdentityMatrix(final Field field, final int dimension) { + final T zero = field.getZero(); + final T one = field.getOne(); + final T[][] d = MathArrays.buildArray(field, dimension, dimension); + for (int row = 0; row < dimension; row++) { + final T[] dRow = d[row]; + Arrays.fill(dRow, zero); + dRow[row] = one; + } + return new Array2DRowFieldMatrix(field, d, false); + } + + /** + * Returns a diagonal matrix with specified elements. + * + * @param diagonal diagonal elements of the matrix (the array elements + * will be copied) + * @return diagonal matrix + * @since 2.0 + */ + public static RealMatrix createRealDiagonalMatrix(final double[] diagonal) { + final RealMatrix m = createRealMatrix(diagonal.length, diagonal.length); + for (int i = 0; i < diagonal.length; ++i) { + m.setEntry(i, i, diagonal[i]); + } + return m; + } + + /** + * Returns a diagonal matrix with specified elements. + * + * @param 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 > FieldMatrix + createFieldDiagonalMatrix(final T[] diagonal) { + final FieldMatrix m = + createFieldMatrix(diagonal[0].getField(), diagonal.length, diagonal.length); + for (int i = 0; i < diagonal.length; ++i) { + m.setEntry(i, i, diagonal[i]); + } + return m; + } + + /** + * Creates a {@link RealVector} using the data from the input array. + * + * @param data the input data + * @return a data.length RealVector + * @throws NoDataException if {@code data} is empty. + * @throws NullArgumentException if {@code data} is {@code null}. + */ + public static RealVector createRealVector(double[] data) + throws NoDataException, NullArgumentException { + if (data == null) { + throw new NullArgumentException(); + } + return new ArrayRealVector(data, true); + } + + /** + * Creates a {@link FieldVector} using the data from the input array. + * + * @param the type of the field elements + * @param data the input data + * @return a data.length FieldVector + * @throws NoDataException if {@code data} is empty. + * @throws NullArgumentException if {@code data} is {@code null}. + * @throws ZeroException if {@code data} has 0 elements + */ + public static > FieldVector createFieldVector(final T[] data) + throws NoDataException, NullArgumentException, ZeroException { + if (data == null) { + throw new NullArgumentException(); + } + if (data.length == 0) { + throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT); + } + return new ArrayFieldVector(data[0].getField(), data, true); + } + + /** + * Create a row {@link RealMatrix} using the data from the input + * array. + * + * @param rowData the input row data + * @return a 1 x rowData.length RealMatrix + * @throws NoDataException if {@code rowData} is empty. + * @throws NullArgumentException if {@code rowData} is {@code null}. + */ + public static RealMatrix createRowRealMatrix(double[] rowData) + throws NoDataException, NullArgumentException { + if (rowData == null) { + throw new NullArgumentException(); + } + final int nCols = rowData.length; + final RealMatrix m = createRealMatrix(1, nCols); + for (int i = 0; i < nCols; ++i) { + m.setEntry(0, i, rowData[i]); + } + return m; + } + + /** + * Create a row {@link FieldMatrix} using the data from the input + * array. + * + * @param the type of the field elements + * @param rowData the input row data + * @return a 1 x rowData.length FieldMatrix + * @throws NoDataException if {@code rowData} is empty. + * @throws NullArgumentException if {@code rowData} is {@code null}. + */ + public static > FieldMatrix + createRowFieldMatrix(final T[] rowData) + throws NoDataException, NullArgumentException { + if (rowData == null) { + throw new NullArgumentException(); + } + final int nCols = rowData.length; + if (nCols == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN); + } + final FieldMatrix m = createFieldMatrix(rowData[0].getField(), 1, nCols); + for (int i = 0; i < nCols; ++i) { + m.setEntry(0, i, rowData[i]); + } + return m; + } + + /** + * Creates a column {@link RealMatrix} using the data from the input + * array. + * + * @param columnData the input column data + * @return a columnData x 1 RealMatrix + * @throws NoDataException if {@code columnData} is empty. + * @throws NullArgumentException if {@code columnData} is {@code null}. + */ + public static RealMatrix createColumnRealMatrix(double[] columnData) + throws NoDataException, NullArgumentException { + if (columnData == null) { + throw new NullArgumentException(); + } + final int nRows = columnData.length; + final RealMatrix m = createRealMatrix(nRows, 1); + for (int i = 0; i < nRows; ++i) { + m.setEntry(i, 0, columnData[i]); + } + return m; + } + + /** + * Creates a column {@link FieldMatrix} using the data from the input + * array. + * + * @param the type of the field elements + * @param columnData the input column data + * @return a columnData x 1 FieldMatrix + * @throws NoDataException if {@code data} is empty. + * @throws NullArgumentException if {@code columnData} is {@code null}. + */ + public static > FieldMatrix + createColumnFieldMatrix(final T[] columnData) + throws NoDataException, NullArgumentException { + if (columnData == null) { + throw new NullArgumentException(); + } + final int nRows = columnData.length; + if (nRows == 0) { + throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW); + } + final FieldMatrix m = createFieldMatrix(columnData[0].getField(), nRows, 1); + for (int i = 0; i < nRows; ++i) { + m.setEntry(i, 0, columnData[i]); + } + return m; + } + + /** + * Checks whether a matrix is symmetric, within a given relative tolerance. + * + * @param matrix Matrix to check. + * @param relativeTolerance Tolerance of the symmetry check. + * @param raiseException If {@code true}, an exception will be raised if + * the matrix is not symmetric. + * @return {@code true} if {@code matrix} is symmetric. + * @throws NonSquareMatrixException if the matrix is not square. + * @throws NonSymmetricMatrixException if the matrix is not symmetric. + */ + private static boolean isSymmetricInternal(RealMatrix matrix, + double relativeTolerance, + boolean raiseException) { + final int rows = matrix.getRowDimension(); + if (rows != matrix.getColumnDimension()) { + if (raiseException) { + throw new NonSquareMatrixException(rows, matrix.getColumnDimension()); + } else { + return false; + } + } + for (int i = 0; i < rows; i++) { + for (int j = i + 1; j < rows; j++) { + final double mij = matrix.getEntry(i, j); + final double mji = matrix.getEntry(j, i); + if (FastMath.abs(mij - mji) > + FastMath.max(FastMath.abs(mij), FastMath.abs(mji)) * relativeTolerance) { + if (raiseException) { + throw new NonSymmetricMatrixException(i, j, relativeTolerance); + } else { + return false; + } + } + } + } + return true; + } + + /** + * Checks whether a matrix is symmetric. + * + * @param matrix Matrix to check. + * @param eps Relative tolerance. + * @throws NonSquareMatrixException if the matrix is not square. + * @throws NonSymmetricMatrixException if the matrix is not symmetric. + * @since 3.1 + */ + public static void checkSymmetric(RealMatrix matrix, + double eps) { + isSymmetricInternal(matrix, eps, true); + } + + /** + * Checks whether a matrix is symmetric. + * + * @param matrix Matrix to check. + * @param eps Relative tolerance. + * @return {@code true} if {@code matrix} is symmetric. + * @since 3.1 + */ + public static boolean isSymmetric(RealMatrix matrix, + double eps) { + return isSymmetricInternal(matrix, eps, false); + } + + /** + * Check if matrix indices are valid. + * + * @param m Matrix. + * @param row Row index to check. + * @param column Column index to check. + * @throws OutOfRangeException if {@code row} or {@code column} is not + * a valid index. + */ + public static void checkMatrixIndex(final AnyMatrix m, + final int row, final int column) + throws OutOfRangeException { + checkRowIndex(m, row); + checkColumnIndex(m, column); + } + + /** + * Check if a row index is valid. + * + * @param m Matrix. + * @param row Row index to check. + * @throws OutOfRangeException if {@code row} is not a valid index. + */ + public static void checkRowIndex(final AnyMatrix m, final int row) + throws OutOfRangeException { + if (row < 0 || + row >= m.getRowDimension()) { + throw new OutOfRangeException(LocalizedFormats.ROW_INDEX, + row, 0, m.getRowDimension() - 1); + } + } + + /** + * Check if a column index is valid. + * + * @param m Matrix. + * @param column Column index to check. + * @throws OutOfRangeException if {@code column} is not a valid index. + */ + public static void checkColumnIndex(final AnyMatrix m, final int column) + throws OutOfRangeException { + if (column < 0 || column >= m.getColumnDimension()) { + throw new OutOfRangeException(LocalizedFormats.COLUMN_INDEX, + column, 0, m.getColumnDimension() - 1); + } + } + + /** + * Check if submatrix ranges indices are valid. + * Rows and columns are indicated counting from 0 to {@code n - 1}. + * + * @param m Matrix. + * @param startRow Initial row index. + * @param endRow Final row index. + * @param startColumn Initial column index. + * @param endColumn Final column index. + * @throws OutOfRangeException if the indices are invalid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + */ + public static void checkSubMatrixIndex(final AnyMatrix m, + final int startRow, final int endRow, + final int startColumn, final int endColumn) + throws NumberIsTooSmallException, OutOfRangeException { + checkRowIndex(m, startRow); + checkRowIndex(m, endRow); + if (endRow < startRow) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, + endRow, startRow, false); + } + + checkColumnIndex(m, startColumn); + checkColumnIndex(m, endColumn); + if (endColumn < startColumn) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN, + endColumn, startColumn, false); + } + + + } + + /** + * Check if submatrix ranges indices are valid. + * Rows and columns are indicated counting from 0 to n-1. + * + * @param m Matrix. + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @throws NullArgumentException if {@code selectedRows} or + * {@code selectedColumns} are {@code null}. + * @throws NoDataException if the row or column selections are empty (zero + * length). + * @throws OutOfRangeException if row or column selections are not valid. + */ + public static void checkSubMatrixIndex(final AnyMatrix m, + final int[] selectedRows, + final int[] selectedColumns) + throws NoDataException, NullArgumentException, OutOfRangeException { + if (selectedRows == null) { + throw new NullArgumentException(); + } + if (selectedColumns == null) { + throw new NullArgumentException(); + } + if (selectedRows.length == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_SELECTED_ROW_INDEX_ARRAY); + } + if (selectedColumns.length == 0) { + throw new NoDataException(LocalizedFormats.EMPTY_SELECTED_COLUMN_INDEX_ARRAY); + } + + for (final int row : selectedRows) { + checkRowIndex(m, row); + } + for (final int column : selectedColumns) { + checkColumnIndex(m, column); + } + } + + /** + * Check if matrices are addition compatible. + * + * @param left Left hand side matrix. + * @param right Right hand side matrix. + * @throws MatrixDimensionMismatchException if the matrices are not addition + * compatible. + */ + public static void checkAdditionCompatible(final AnyMatrix left, final AnyMatrix right) + throws MatrixDimensionMismatchException { + if ((left.getRowDimension() != right.getRowDimension()) || + (left.getColumnDimension() != right.getColumnDimension())) { + throw new MatrixDimensionMismatchException(left.getRowDimension(), left.getColumnDimension(), + right.getRowDimension(), right.getColumnDimension()); + } + } + + /** + * Check if matrices are subtraction compatible + * + * @param left Left hand side matrix. + * @param right Right hand side matrix. + * @throws MatrixDimensionMismatchException if the matrices are not addition + * compatible. + */ + public static void checkSubtractionCompatible(final AnyMatrix left, final AnyMatrix right) + throws MatrixDimensionMismatchException { + if ((left.getRowDimension() != right.getRowDimension()) || + (left.getColumnDimension() != right.getColumnDimension())) { + throw new MatrixDimensionMismatchException(left.getRowDimension(), left.getColumnDimension(), + right.getRowDimension(), right.getColumnDimension()); + } + } + + /** + * Check if matrices are multiplication compatible + * + * @param left Left hand side matrix. + * @param right Right hand side matrix. + * @throws DimensionMismatchException if matrices are not multiplication + * compatible. + */ + public static void checkMultiplicationCompatible(final AnyMatrix left, final AnyMatrix right) + throws DimensionMismatchException { + + if (left.getColumnDimension() != right.getRowDimension()) { + throw new DimensionMismatchException(left.getColumnDimension(), + right.getRowDimension()); + } + } + + /** + * Convert a {@link FieldMatrix}/{@link Fraction} matrix to a {@link RealMatrix}. + * @param m Matrix to convert. + * @return the converted matrix. + */ + public static Array2DRowRealMatrix fractionMatrixToRealMatrix(final FieldMatrix m) { + final FractionMatrixConverter converter = new FractionMatrixConverter(); + m.walkInOptimizedOrder(converter); + return converter.getConvertedMatrix(); + } + + /** Converter for {@link FieldMatrix}/{@link Fraction}. */ + private static class FractionMatrixConverter extends DefaultFieldMatrixPreservingVisitor { + /** Converted array. */ + private double[][] data; + /** Simple constructor. */ + FractionMatrixConverter() { + super(Fraction.ZERO); + } + + /** {@inheritDoc} */ + @Override + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + data = new double[rows][columns]; + } + + /** {@inheritDoc} */ + @Override + public void visit(int row, int column, Fraction value) { + data[row][column] = value.doubleValue(); + } + + /** + * Get the converted matrix. + * + * @return the converted matrix. + */ + Array2DRowRealMatrix getConvertedMatrix() { + return new Array2DRowRealMatrix(data, false); + } + + } + + /** + * Convert a {@link FieldMatrix}/{@link BigFraction} matrix to a {@link RealMatrix}. + * + * @param m Matrix to convert. + * @return the converted matrix. + */ + public static Array2DRowRealMatrix bigFractionMatrixToRealMatrix(final FieldMatrix m) { + final BigFractionMatrixConverter converter = new BigFractionMatrixConverter(); + m.walkInOptimizedOrder(converter); + return converter.getConvertedMatrix(); + } + + /** Converter for {@link FieldMatrix}/{@link BigFraction}. */ + private static class BigFractionMatrixConverter extends DefaultFieldMatrixPreservingVisitor { + /** Converted array. */ + private double[][] data; + /** Simple constructor. */ + BigFractionMatrixConverter() { + super(BigFraction.ZERO); + } + + /** {@inheritDoc} */ + @Override + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + data = new double[rows][columns]; + } + + /** {@inheritDoc} */ + @Override + public void visit(int row, int column, BigFraction value) { + data[row][column] = value.doubleValue(); + } + + /** + * Get the converted matrix. + * + * @return the converted matrix. + */ + Array2DRowRealMatrix getConvertedMatrix() { + return new Array2DRowRealMatrix(data, false); + } + } + + /** Serialize a {@link RealVector}. + *

    + * This method is intended to be called from within a private + * writeObject method (after a call to + * oos.defaultWriteObject()) in a class that has a + * {@link RealVector} field, which should be declared transient. + * 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. + *

    + *

    + * The following example shows how a simple class with a name and a real vector + * should be written: + *

    
    +     * 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);
    +     *     }
    +     *
    +     * }
    +     * 
    + *

    + * + * @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. + *

    + * This method is intended to be called from within a private + * readObject method (after a call to + * ois.defaultReadObject()) in a class that has a + * {@link RealVector} field, which should be declared transient. + * This way, the default handling does not deserialize the vector (the {@link + * RealVector} interface is not serializable by default) but this method does + * deserialize it specifically. + *

    + * @param instance instance in which the field must be set up + * @param fieldName name of the field within the class (may be private and final) + * @param ois stream from which the real vector should be read + * @exception ClassNotFoundException if a class in the stream cannot be found + * @exception IOException if object cannot be read from the stream + * @see #serializeRealVector(RealVector, ObjectOutputStream) + */ + public static void deserializeRealVector(final Object instance, + final String fieldName, + final ObjectInputStream ois) + throws ClassNotFoundException, IOException { + try { + + // read the vector data + final int n = ois.readInt(); + final double[] data = new double[n]; + for (int i = 0; i < n; ++i) { + data[i] = ois.readDouble(); + } + + // create the instance + final RealVector vector = new ArrayRealVector(data, false); + + // set up the field + final java.lang.reflect.Field f = + instance.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(instance, vector); + + } catch (NoSuchFieldException nsfe) { + IOException ioe = new IOException(); + ioe.initCause(nsfe); + throw ioe; + } catch (IllegalAccessException iae) { + IOException ioe = new IOException(); + ioe.initCause(iae); + throw ioe; + } + + } + + /** Serialize a {@link RealMatrix}. + *

    + * This method is intended to be called from within a private + * writeObject method (after a call to + * oos.defaultWriteObject()) in a class that has a + * {@link RealMatrix} field, which should be declared transient. + * 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. + *

    + *

    + * The following example shows how a simple class with a name and a real matrix + * should be written: + *

    
    +     * 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);
    +     *     }
    +     *
    +     * }
    +     * 
    + *

    + * + * @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. + *

    + * This method is intended to be called from within a private + * readObject method (after a call to + * ois.defaultReadObject()) in a class that has a + * {@link RealMatrix} field, which should be declared transient. + * This way, the default handling does not deserialize the matrix (the {@link + * RealMatrix} interface is not serializable by default) but this method does + * deserialize it specifically. + *

    + * @param instance instance in which the field must be set up + * @param fieldName name of the field within the class (may be private and final) + * @param ois stream from which the real matrix should be read + * @exception ClassNotFoundException if a class in the stream cannot be found + * @exception IOException if object cannot be read from the stream + * @see #serializeRealMatrix(RealMatrix, ObjectOutputStream) + */ + public static void deserializeRealMatrix(final Object instance, + final String fieldName, + final ObjectInputStream ois) + throws ClassNotFoundException, IOException { + try { + + // read the matrix data + final int n = ois.readInt(); + final int m = ois.readInt(); + final double[][] data = new double[n][m]; + for (int i = 0; i < n; ++i) { + final double[] dataI = data[i]; + for (int j = 0; j < m; ++j) { + dataI[j] = ois.readDouble(); + } + } + + // create the instance + final RealMatrix matrix = new Array2DRowRealMatrix(data, false); + + // set up the field + final java.lang.reflect.Field f = + instance.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(instance, matrix); + + } catch (NoSuchFieldException nsfe) { + IOException ioe = new IOException(); + ioe.initCause(nsfe); + throw ioe; + } catch (IllegalAccessException iae) { + IOException ioe = new IOException(); + ioe.initCause(iae); + throw ioe; + } + } + + /**Solve a system of composed of a Lower Triangular Matrix + * {@link RealMatrix}. + *

    + * This method is called to solve systems of equations which are + * of the lower triangular form. The matrix {@link RealMatrix} + * is assumed, though not checked, to be in lower triangular form. + * The vector {@link RealVector} is overwritten with the solution. + * The matrix is checked that it is square and its dimensions match + * the length of the vector. + *

    + * @param rm RealMatrix which is lower triangular + * @param b RealVector this is overwritten + * @throws DimensionMismatchException if the matrix and vector are not + * conformable + * @throws NonSquareMatrixException if the matrix {@code rm} is not square + * @throws MathArithmeticException if the absolute value of one of the diagonal + * coefficient of {@code rm} is lower than {@link Precision#SAFE_MIN} + */ + public static void solveLowerTriangularSystem(RealMatrix rm, RealVector b) + throws DimensionMismatchException, MathArithmeticException, + NonSquareMatrixException { + if ((rm == null) || (b == null) || ( rm.getRowDimension() != b.getDimension())) { + throw new DimensionMismatchException( + (rm == null) ? 0 : rm.getRowDimension(), + (b == null) ? 0 : b.getDimension()); + } + if( rm.getColumnDimension() != rm.getRowDimension() ){ + throw new NonSquareMatrixException(rm.getRowDimension(), + rm.getColumnDimension()); + } + int rows = rm.getRowDimension(); + for( int i = 0 ; i < rows ; i++ ){ + double diag = rm.getEntry(i, i); + if( FastMath.abs(diag) < Precision.SAFE_MIN ){ + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + double bi = b.getEntry(i)/diag; + b.setEntry(i, bi ); + for( int j = i+1; j< rows; j++ ){ + b.setEntry(j, b.getEntry(j)-bi*rm.getEntry(j,i) ); + } + } + } + + /** Solver a system composed of an Upper Triangular Matrix + * {@link RealMatrix}. + *

    + * This method is called to solve systems of equations which are + * of the lower triangular form. The matrix {@link RealMatrix} + * is assumed, though not checked, to be in upper triangular form. + * The vector {@link RealVector} is overwritten with the solution. + * The matrix is checked that it is square and its dimensions match + * the length of the vector. + *

    + * @param rm RealMatrix which is upper triangular + * @param b RealVector this is overwritten + * @throws DimensionMismatchException if the matrix and vector are not + * conformable + * @throws NonSquareMatrixException if the matrix {@code rm} is not + * square + * @throws MathArithmeticException if the absolute value of one of the diagonal + * coefficient of {@code rm} is lower than {@link Precision#SAFE_MIN} + */ + public static void solveUpperTriangularSystem(RealMatrix rm, RealVector b) + throws DimensionMismatchException, MathArithmeticException, + NonSquareMatrixException { + if ((rm == null) || (b == null) || ( rm.getRowDimension() != b.getDimension())) { + throw new DimensionMismatchException( + (rm == null) ? 0 : rm.getRowDimension(), + (b == null) ? 0 : b.getDimension()); + } + if( rm.getColumnDimension() != rm.getRowDimension() ){ + throw new NonSquareMatrixException(rm.getRowDimension(), + rm.getColumnDimension()); + } + int rows = rm.getRowDimension(); + for( int i = rows-1 ; i >-1 ; i-- ){ + double diag = rm.getEntry(i, i); + if( FastMath.abs(diag) < Precision.SAFE_MIN ){ + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + double bi = b.getEntry(i)/diag; + b.setEntry(i, bi ); + for( int j = i-1; j>-1; j-- ){ + b.setEntry(j, b.getEntry(j)-bi*rm.getEntry(j,i) ); + } + } + } + + /** + * Computes the inverse of the given matrix by splitting it into + * 4 sub-matrices. + * + * @param m Matrix whose inverse must be computed. + * @param splitIndex Index that determines the "split" line and + * column. + * The element corresponding to this index will part of the + * upper-left sub-matrix. + * @return the inverse of {@code m}. + * @throws NonSquareMatrixException if {@code m} is not square. + */ + public static RealMatrix blockInverse(RealMatrix m, + int splitIndex) { + final int n = m.getRowDimension(); + if (m.getColumnDimension() != n) { + throw new NonSquareMatrixException(m.getRowDimension(), + m.getColumnDimension()); + } + + final int splitIndex1 = splitIndex + 1; + + final RealMatrix a = m.getSubMatrix(0, splitIndex, 0, splitIndex); + final RealMatrix b = m.getSubMatrix(0, splitIndex, splitIndex1, n - 1); + final RealMatrix c = m.getSubMatrix(splitIndex1, n - 1, 0, splitIndex); + final RealMatrix d = m.getSubMatrix(splitIndex1, n - 1, splitIndex1, n - 1); + + final SingularValueDecomposition aDec = new SingularValueDecomposition(a); + final DecompositionSolver aSolver = aDec.getSolver(); + if (!aSolver.isNonSingular()) { + throw new SingularMatrixException(); + } + final RealMatrix aInv = aSolver.getInverse(); + + final SingularValueDecomposition dDec = new SingularValueDecomposition(d); + final DecompositionSolver dSolver = dDec.getSolver(); + if (!dSolver.isNonSingular()) { + throw new SingularMatrixException(); + } + final RealMatrix dInv = dSolver.getInverse(); + + final RealMatrix tmp1 = a.subtract(b.multiply(dInv).multiply(c)); + final SingularValueDecomposition tmp1Dec = new SingularValueDecomposition(tmp1); + final DecompositionSolver tmp1Solver = tmp1Dec.getSolver(); + if (!tmp1Solver.isNonSingular()) { + throw new SingularMatrixException(); + } + final RealMatrix result00 = tmp1Solver.getInverse(); + + final RealMatrix tmp2 = d.subtract(c.multiply(aInv).multiply(b)); + final SingularValueDecomposition tmp2Dec = new SingularValueDecomposition(tmp2); + final DecompositionSolver tmp2Solver = tmp2Dec.getSolver(); + if (!tmp2Solver.isNonSingular()) { + throw new SingularMatrixException(); + } + final RealMatrix result11 = tmp2Solver.getInverse(); + + final RealMatrix result01 = aInv.multiply(b).multiply(result11).scalarMultiply(-1); + final RealMatrix result10 = dInv.multiply(c).multiply(result00).scalarMultiply(-1); + + final RealMatrix result = new Array2DRowRealMatrix(n, n); + result.setSubMatrix(result00.getData(), 0, 0); + result.setSubMatrix(result01.getData(), 0, splitIndex1); + result.setSubMatrix(result10.getData(), splitIndex1, 0); + result.setSubMatrix(result11.getData(), splitIndex1, splitIndex1); + + return result; + } + + /** + * Computes the inverse of the given matrix. + *

    + * By default, the inverse of the matrix is computed using the QR-decomposition, + * unless a more efficient method can be determined for the input matrix. + *

    + * Note: this method will use a singularity threshold of 0, + * use {@link #inverse(RealMatrix, double)} if a different threshold is needed. + * + * @param matrix Matrix whose inverse shall be computed + * @return the inverse of {@code matrix} + * @throws NullArgumentException if {@code matrix} is {@code null} + * @throws SingularMatrixException if m is singular + * @throws NonSquareMatrixException if matrix is not square + * @since 3.3 + */ + public static RealMatrix inverse(RealMatrix matrix) + throws NullArgumentException, SingularMatrixException, NonSquareMatrixException { + return inverse(matrix, 0); + } + + /** + * Computes the inverse of the given matrix. + *

    + * By default, the inverse of the matrix is computed using the QR-decomposition, + * unless a more efficient method can be determined for the input matrix. + * + * @param matrix Matrix whose inverse shall be computed + * @param threshold Singularity threshold + * @return the inverse of {@code m} + * @throws NullArgumentException if {@code matrix} is {@code null} + * @throws SingularMatrixException if matrix is singular + * @throws NonSquareMatrixException if matrix is not square + * @since 3.3 + */ + public static RealMatrix inverse(RealMatrix matrix, double threshold) + throws NullArgumentException, SingularMatrixException, NonSquareMatrixException { + + MathUtils.checkNotNull(matrix); + + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + if (matrix instanceof DiagonalMatrix) { + return ((DiagonalMatrix) matrix).inverse(threshold); + } else { + QRDecomposition decomposition = new QRDecomposition(matrix, threshold); + return decomposition.getSolver().getInverse(); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.java new file mode 100644 index 000000000..1ea764765 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a positive definite matrix is expected. + * + * @since 3.0 + */ +public class NonPositiveDefiniteMatrixException extends NumberIsTooSmallException { + /** Serializable version Id. */ + private static final long serialVersionUID = 1641613838113738061L; + /** Index (diagonal element). */ + private final int index; + /** Threshold. */ + private final double threshold; + + /** + * Construct an exception. + * + * @param wrong Value that fails the positivity check. + * @param index Row (and column) index. + * @param threshold Absolute positivity threshold. + */ + public NonPositiveDefiniteMatrixException(double wrong, + int index, + double threshold) { + super(wrong, threshold, false); + this.index = index; + this.threshold = threshold; + + final ExceptionContext context = getContext(); + context.addMessage(LocalizedFormats.NOT_POSITIVE_DEFINITE_MATRIX); + context.addMessage(LocalizedFormats.ARRAY_ELEMENT, wrong, index); + } + + /** + * @return the row index. + */ + public int getRow() { + return index; + } + /** + * @return the column index. + */ + public int getColumn() { + return index; + } + /** + * @return the absolute positivity threshold. + */ + public double getThreshold() { + return threshold; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.java new file mode 100644 index 000000000..4e3039aee --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a symmetric, definite positive + * {@link RealLinearOperator} is expected. + * Since the coefficients of the matrix are not accessible, the most + * general definition is used to check that {@code A} is not positive + * definite, i.e. there exists {@code x} such that {@code x' A x <= 0}. + * In the terminology of this exception, {@code A} is the "offending" + * linear operator and {@code x} the "offending" vector. + * + * @since 3.0 + */ +public class NonPositiveDefiniteOperatorException + extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = 917034489420549847L; + + /** Creates a new instance of this class. */ + public NonPositiveDefiniteOperatorException() { + super(LocalizedFormats.NON_POSITIVE_DEFINITE_OPERATOR); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.java new file mode 100644 index 000000000..96b273ca7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a self-adjoint {@link RealLinearOperator} + * is expected. + * Since the coefficients of the matrix are not accessible, the most + * general definition is used to check that A is not self-adjoint, i.e. + * there exist x and y such as {@code | x' A y - y' A x | >= eps}, + * where {@code eps} is a user-specified tolerance, and {@code x'} + * denotes the transpose of {@code x}. + * In the terminology of this exception, {@code A} is the "offending" + * linear operator, {@code x} and {@code y} are the first and second + * "offending" vectors, respectively. + * + * @since 3.0 + */ +public class NonSelfAdjointOperatorException + extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = 1784999305030258247L; + + /** Creates a new instance of this class. */ + public NonSelfAdjointOperatorException() { + super(LocalizedFormats.NON_SELF_ADJOINT_OPERATOR); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareMatrixException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareMatrixException.java new file mode 100644 index 000000000..9210d4fde --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareMatrixException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a square matrix is expected. + * + * @since 3.0 + */ +public class NonSquareMatrixException extends DimensionMismatchException { + /** Serializable version Id. */ + private static final long serialVersionUID = -660069396594485772L; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrong Row dimension. + * @param expected Column dimension. + */ + public NonSquareMatrixException(int wrong, + int expected) { + super(LocalizedFormats.NON_SQUARE_MATRIX, wrong, expected); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareOperatorException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareOperatorException.java new file mode 100644 index 000000000..a6269b3cb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSquareOperatorException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a square linear operator is expected. + * + * @since 3.0 + */ +public class NonSquareOperatorException extends DimensionMismatchException { + /** Serializable version Id. */ + private static final long serialVersionUID = -4145007524150846242L; + + /** + * Construct an exception from the mismatched dimensions. + * + * @param wrong Row dimension. + * @param expected Column dimension. + */ + public NonSquareOperatorException(int wrong, int expected) { + super(LocalizedFormats.NON_SQUARE_OPERATOR, wrong, expected); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSymmetricMatrixException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSymmetricMatrixException.java new file mode 100644 index 000000000..623524226 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/NonSymmetricMatrixException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a symmetric matrix is expected. + * + * @since 3.0 + */ +public class NonSymmetricMatrixException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -7518495577824189882L; + /** Row. */ + private final int row; + /** Column. */ + private final int column; + /** Threshold. */ + private final double threshold; + + /** + * Construct an exception. + * + * @param row Row index. + * @param column Column index. + * @param threshold Relative symmetry threshold. + */ + public NonSymmetricMatrixException(int row, + int column, + double threshold) { + super(LocalizedFormats.NON_SYMMETRIC_MATRIX, row, column, threshold); + this.row = row; + this.column = column; + this.threshold = threshold; + } + + /** + * @return the row index of the entry. + */ + public int getRow() { + return row; + } + /** + * @return the column index of the entry. + */ + public int getColumn() { + return column; + } + /** + * @return the relative symmetry threshold. + */ + public double getThreshold() { + return threshold; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealMatrix.java new file mode 100644 index 000000000..53c82f4ae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealMatrix.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.OpenIntToDoubleHashMap; + +/** + * Sparse matrix implementation based on an open addressed map. + * + *

    + * Caveat: This implementation assumes that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results. + *

    + * @since 2.0 + */ +public class OpenMapRealMatrix extends AbstractRealMatrix + implements SparseRealMatrix, Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = -5962461716457143437L; + /** Number of rows of the matrix. */ + private final int rows; + /** Number of columns of the matrix. */ + private final int columns; + /** Storage for (sparse) matrix elements. */ + private final OpenIntToDoubleHashMap entries; + + /** + * Build a sparse matrix with the supplied row and column dimensions. + * + * @param rowDimension Number of rows of the matrix. + * @param columnDimension Number of columns of the matrix. + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @throws NumberIsTooLargeException if the total number of entries of the + * matrix is larger than {@code Integer.MAX_VALUE}. + */ + public OpenMapRealMatrix(int rowDimension, int columnDimension) + throws NotStrictlyPositiveException, NumberIsTooLargeException { + super(rowDimension, columnDimension); + long lRow = rowDimension; + long lCol = columnDimension; + if (lRow * lCol >= Integer.MAX_VALUE) { + throw new NumberIsTooLargeException(lRow * lCol, Integer.MAX_VALUE, false); + } + this.rows = rowDimension; + this.columns = columnDimension; + this.entries = new OpenIntToDoubleHashMap(0.0); + } + + /** + * Build a matrix by copying another one. + * + * @param matrix matrix to copy. + */ + public OpenMapRealMatrix(OpenMapRealMatrix matrix) { + this.rows = matrix.rows; + this.columns = matrix.columns; + this.entries = new OpenIntToDoubleHashMap(matrix.entries); + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealMatrix copy() { + return new OpenMapRealMatrix(this); + } + + /** + * {@inheritDoc} + * + * @throws NumberIsTooLargeException if the total number of entries of the + * matrix is larger than {@code Integer.MAX_VALUE}. + */ + @Override + public OpenMapRealMatrix createMatrix(int rowDimension, int columnDimension) + throws NotStrictlyPositiveException, NumberIsTooLargeException { + return new OpenMapRealMatrix(rowDimension, columnDimension); + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return columns; + } + + /** + * Compute the sum of this matrix and {@code m}. + * + * @param m Matrix to be added. + * @return {@code this} + {@code m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public OpenMapRealMatrix add(OpenMapRealMatrix m) + throws MatrixDimensionMismatchException { + + MatrixUtils.checkAdditionCompatible(this, m); + + final OpenMapRealMatrix out = new OpenMapRealMatrix(this); + for (OpenIntToDoubleHashMap.Iterator iterator = m.entries.iterator(); iterator.hasNext();) { + iterator.advance(); + final int row = iterator.key() / columns; + final int col = iterator.key() - row * columns; + out.setEntry(row, col, getEntry(row, col) + iterator.value()); + } + + return out; + + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealMatrix subtract(final RealMatrix m) + throws MatrixDimensionMismatchException { + try { + return subtract((OpenMapRealMatrix) m); + } catch (ClassCastException cce) { + return (OpenMapRealMatrix) super.subtract(m); + } + } + + /** + * Subtract {@code m} from this matrix. + * + * @param m Matrix to be subtracted. + * @return {@code this} - {@code m}. + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + public OpenMapRealMatrix subtract(OpenMapRealMatrix m) + throws MatrixDimensionMismatchException { + MatrixUtils.checkAdditionCompatible(this, m); + + final OpenMapRealMatrix out = new OpenMapRealMatrix(this); + for (OpenIntToDoubleHashMap.Iterator iterator = m.entries.iterator(); iterator.hasNext();) { + iterator.advance(); + final int row = iterator.key() / columns; + final int col = iterator.key() - row * columns; + out.setEntry(row, col, getEntry(row, col) - iterator.value()); + } + + return out; + } + + /** + * {@inheritDoc} + * + * @throws NumberIsTooLargeException if {@code m} is an + * {@code OpenMapRealMatrix}, and the total number of entries of the product + * is larger than {@code Integer.MAX_VALUE}. + */ + @Override + public RealMatrix multiply(final RealMatrix m) + throws DimensionMismatchException, NumberIsTooLargeException { + try { + return multiply((OpenMapRealMatrix) m); + } catch (ClassCastException cce) { + + MatrixUtils.checkMultiplicationCompatible(this, m); + + final int outCols = m.getColumnDimension(); + final BlockRealMatrix out = new BlockRealMatrix(rows, outCols); + for (OpenIntToDoubleHashMap.Iterator iterator = entries.iterator(); iterator.hasNext();) { + iterator.advance(); + final double value = iterator.value(); + final int key = iterator.key(); + final int i = key / columns; + final int k = key % columns; + for (int j = 0; j < outCols; ++j) { + out.addToEntry(i, j, value * m.getEntry(k, j)); + } + } + + return out; + } + } + + /** + * Postmultiply this matrix by {@code m}. + * + * @param m Matrix to postmultiply by. + * @return {@code this} * {@code m}. + * @throws DimensionMismatchException if the number of rows of {@code m} + * differ from the number of columns of {@code this} matrix. + * @throws NumberIsTooLargeException if the total number of entries of the + * product is larger than {@code Integer.MAX_VALUE}. + */ + public OpenMapRealMatrix multiply(OpenMapRealMatrix m) + throws DimensionMismatchException, NumberIsTooLargeException { + // Safety check. + MatrixUtils.checkMultiplicationCompatible(this, m); + + final int outCols = m.getColumnDimension(); + OpenMapRealMatrix out = new OpenMapRealMatrix(rows, outCols); + for (OpenIntToDoubleHashMap.Iterator iterator = entries.iterator(); iterator.hasNext();) { + iterator.advance(); + final double value = iterator.value(); + final int key = iterator.key(); + final int i = key / columns; + final int k = key % columns; + for (int j = 0; j < outCols; ++j) { + final int rightKey = m.computeKey(k, j); + if (m.entries.containsKey(rightKey)) { + final int outKey = out.computeKey(i, j); + final double outValue = + out.entries.get(outKey) + value * m.entries.get(rightKey); + if (outValue == 0.0) { + out.entries.remove(outKey); + } else { + out.entries.put(outKey, outValue); + } + } + } + } + + return out; + } + + /** {@inheritDoc} */ + @Override + public double getEntry(int row, int column) throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + MatrixUtils.checkColumnIndex(this, column); + return entries.get(computeKey(row, column)); + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return rows; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(int row, int column, double value) + throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + MatrixUtils.checkColumnIndex(this, column); + if (value == 0.0) { + entries.remove(computeKey(row, column)); + } else { + entries.put(computeKey(row, column), value); + } + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(int row, int column, double increment) + throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + MatrixUtils.checkColumnIndex(this, column); + final int key = computeKey(row, column); + final double value = entries.get(key) + increment; + if (value == 0.0) { + entries.remove(key); + } else { + entries.put(key, value); + } + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(int row, int column, double factor) + throws OutOfRangeException { + MatrixUtils.checkRowIndex(this, row); + MatrixUtils.checkColumnIndex(this, column); + final int key = computeKey(row, column); + final double value = entries.get(key) * factor; + if (value == 0.0) { + entries.remove(key); + } else { + entries.put(key, value); + } + } + + /** + * Compute the key to access a matrix element + * @param row row index of the matrix element + * @param column column index of the matrix element + * @return key within the map to access the matrix element + */ + private int computeKey(int row, int column) { + return row * columns + column; + } + + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealVector.java new file mode 100644 index 000000000..2faf1f16e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/OpenMapRealVector.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 com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.OpenIntToDoubleHashMap; +import com.fr.third.org.apache.commons.math3.util.OpenIntToDoubleHashMap.Iterator; + +/** + * This class implements the {@link RealVector} interface with a + * {@link OpenIntToDoubleHashMap} backing store. + *

    + * Caveat: This implementation assumes that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results, like multiplications, divisions or + * functions mapping. + *

    + * @since 2.0 + */ +public class OpenMapRealVector extends SparseRealVector + implements Serializable { + /** Default Tolerance for having a value considered zero. */ + public static final double DEFAULT_ZERO_TOLERANCE = 1.0e-12; + /** Serializable version identifier. */ + private static final long serialVersionUID = 8772222695580707260L; + /** Entries of the vector. */ + private final OpenIntToDoubleHashMap entries; + /** Dimension of the vector. */ + private final int virtualSize; + /** Tolerance for having a value considered zero. */ + private final double epsilon; + + /** + * Build a 0-length vector. + * Zero-length vectors may be used to initialized construction of vectors + * by data gathering. We start with zero-length and use either the {@link + * #OpenMapRealVector(OpenMapRealVector, int)} constructor + * or one of the {@code append} method ({@link #append(double)}, + * {@link #append(RealVector)}) to gather data into this vector. + */ + public OpenMapRealVector() { + this(0, DEFAULT_ZERO_TOLERANCE); + } + + /** + * Construct a vector of zeroes. + * + * @param dimension Size of the vector. + */ + public OpenMapRealVector(int dimension) { + this(dimension, DEFAULT_ZERO_TOLERANCE); + } + + /** + * Construct a vector of zeroes, specifying zero tolerance. + * + * @param dimension Size of the vector. + * @param epsilon Tolerance below which a value considered zero. + */ + public OpenMapRealVector(int dimension, double epsilon) { + virtualSize = dimension; + entries = new OpenIntToDoubleHashMap(0.0); + this.epsilon = epsilon; + } + + /** + * Build a resized vector, for use with append. + * + * @param v Original vector. + * @param resize Amount to add. + */ + protected OpenMapRealVector(OpenMapRealVector v, int resize) { + virtualSize = v.getDimension() + resize; + entries = new OpenIntToDoubleHashMap(v.entries); + epsilon = v.epsilon; + } + + /** + * Build a vector with known the sparseness (for advanced use only). + * + * @param dimension Size of the vector. + * @param expectedSize The expected number of non-zero entries. + */ + public OpenMapRealVector(int dimension, int expectedSize) { + this(dimension, expectedSize, DEFAULT_ZERO_TOLERANCE); + } + + /** + * Build a vector with known the sparseness and zero tolerance + * setting (for advanced use only). + * + * @param dimension Size of the vector. + * @param expectedSize Expected number of non-zero entries. + * @param epsilon Tolerance below which a value is considered zero. + */ + public OpenMapRealVector(int dimension, int expectedSize, double epsilon) { + virtualSize = dimension; + entries = new OpenIntToDoubleHashMap(expectedSize, 0.0); + this.epsilon = epsilon; + } + + /** + * Create from an array. + * Only non-zero entries will be stored. + * + * @param values Set of values to create from. + */ + public OpenMapRealVector(double[] values) { + this(values, DEFAULT_ZERO_TOLERANCE); + } + + /** + * Create from an array, specifying zero tolerance. + * Only non-zero entries will be stored. + * + * @param values Set of values to create from. + * @param epsilon Tolerance below which a value is considered zero. + */ + public OpenMapRealVector(double[] values, double epsilon) { + virtualSize = values.length; + entries = new OpenIntToDoubleHashMap(0.0); + this.epsilon = epsilon; + for (int key = 0; key < values.length; key++) { + double value = values[key]; + if (!isDefaultValue(value)) { + entries.put(key, value); + } + } + } + + /** + * Create from an array. + * Only non-zero entries will be stored. + * + * @param values The set of values to create from + */ + public OpenMapRealVector(Double[] values) { + this(values, DEFAULT_ZERO_TOLERANCE); + } + + /** + * Create from an array. + * Only non-zero entries will be stored. + * + * @param values Set of values to create from. + * @param epsilon Tolerance below which a value is considered zero. + */ + public OpenMapRealVector(Double[] values, double epsilon) { + virtualSize = values.length; + entries = new OpenIntToDoubleHashMap(0.0); + this.epsilon = epsilon; + for (int key = 0; key < values.length; key++) { + double value = values[key].doubleValue(); + if (!isDefaultValue(value)) { + entries.put(key, value); + } + } + } + + /** + * Copy constructor. + * + * @param v Instance to copy from. + */ + public OpenMapRealVector(OpenMapRealVector v) { + virtualSize = v.getDimension(); + entries = new OpenIntToDoubleHashMap(v.getEntries()); + epsilon = v.epsilon; + } + + /** + * Generic copy constructor. + * + * @param v Instance to copy from. + */ + public OpenMapRealVector(RealVector v) { + virtualSize = v.getDimension(); + entries = new OpenIntToDoubleHashMap(0.0); + epsilon = DEFAULT_ZERO_TOLERANCE; + for (int key = 0; key < virtualSize; key++) { + double value = v.getEntry(key); + if (!isDefaultValue(value)) { + entries.put(key, value); + } + } + } + + /** + * Get the entries of this instance. + * + * @return the entries of this instance. + */ + private OpenIntToDoubleHashMap getEntries() { + return entries; + } + + /** + * Determine if this value is within epsilon of zero. + * + * @param value Value to test + * @return {@code true} if this value is within epsilon to zero, + * {@code false} otherwise. + * @since 2.1 + */ + protected boolean isDefaultValue(double value) { + return FastMath.abs(value) < epsilon; + } + + /** {@inheritDoc} */ + @Override + public RealVector add(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + if (v instanceof OpenMapRealVector) { + return add((OpenMapRealVector) v); + } else { + return super.add(v); + } + } + + /** + * Optimized method to add two OpenMapRealVectors. + * It copies the larger vector, then iterates over the smaller. + * + * @param v Vector to add. + * @return the sum of {@code this} and {@code v}. + * @throws DimensionMismatchException if the dimensions do not match. + */ + public OpenMapRealVector add(OpenMapRealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + boolean copyThis = entries.size() > v.entries.size(); + OpenMapRealVector res = copyThis ? this.copy() : v.copy(); + Iterator iter = copyThis ? v.entries.iterator() : entries.iterator(); + OpenIntToDoubleHashMap randomAccess = copyThis ? entries : v.entries; + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (randomAccess.containsKey(key)) { + res.setEntry(key, randomAccess.get(key) + iter.value()); + } else { + res.setEntry(key, iter.value()); + } + } + return res; + } + + /** + * Optimized method to append a OpenMapRealVector. + * @param v vector to append + * @return The result of appending {@code v} to self + */ + public OpenMapRealVector append(OpenMapRealVector v) { + OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension()); + Iterator iter = v.entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res.setEntry(iter.key() + virtualSize, iter.value()); + } + return res; + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector append(RealVector v) { + if (v instanceof OpenMapRealVector) { + return append((OpenMapRealVector) v); + } else { + final OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension()); + for (int i = 0; i < v.getDimension(); i++) { + res.setEntry(i + virtualSize, v.getEntry(i)); + } + return res; + } + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector append(double d) { + OpenMapRealVector res = new OpenMapRealVector(this, 1); + res.setEntry(virtualSize, d); + return res; + } + + /** + * {@inheritDoc} + * @since 2.1 + */ + @Override + public OpenMapRealVector copy() { + return new OpenMapRealVector(this); + } + + /** + * Computes the dot product. + * Note that the computation is now performed in the parent class: no + * performance improvement is to be expected from this overloaded + * method. + * The previous implementation was buggy and cannot be easily fixed + * (see MATH-795). + * + * @param v Vector. + * @return the dot product of this vector with {@code v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + * + * @deprecated as of 3.1 (to be removed in 4.0). The computation is + * performed by the parent class. The method must be kept to maintain + * backwards compatibility. + */ + @Deprecated + public double dotProduct(OpenMapRealVector v) + throws DimensionMismatchException { + return dotProduct((RealVector) v); + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector ebeDivide(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + OpenMapRealVector res = new OpenMapRealVector(this); + /* + * MATH-803: it is not sufficient to loop through non zero entries of + * this only. Indeed, if this[i] = 0d and v[i] = 0d, then + * this[i] / v[i] = NaN, and not 0d. + */ + final int n = getDimension(); + for (int i = 0; i < n; i++) { + res.setEntry(i, this.getEntry(i) / v.getEntry(i)); + } + return res; + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector ebeMultiply(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + OpenMapRealVector res = new OpenMapRealVector(this); + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key())); + } + return res; + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector getSubVector(int index, int n) + throws NotPositiveException, OutOfRangeException { + checkIndex(index); + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n); + } + checkIndex(index + n - 1); + OpenMapRealVector res = new OpenMapRealVector(n); + int end = index + n; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (key >= index && key < end) { + res.setEntry(key - index, iter.value()); + } + } + return res; + } + + /** {@inheritDoc} */ + @Override + public int getDimension() { + return virtualSize; + } + + /** + * Optimized method to compute distance. + * + * @param v Vector to compute distance to. + * @return the distance from {@code this} and {@code v}. + * @throws DimensionMismatchException if the dimensions do not match. + */ + public double getDistance(OpenMapRealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + Iterator iter = entries.iterator(); + double res = 0; + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + double delta; + delta = iter.value() - v.getEntry(key); + res += delta * delta; + } + iter = v.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (!entries.containsKey(key)) { + final double value = iter.value(); + res += value * value; + } + } + return FastMath.sqrt(res); + } + + /** {@inheritDoc} */ + @Override + public double getDistance(RealVector v) throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + if (v instanceof OpenMapRealVector) { + return getDistance((OpenMapRealVector) v); + } else { + return super.getDistance(v); + } + } + + /** {@inheritDoc} */ + @Override + public double getEntry(int index) throws OutOfRangeException { + checkIndex(index); + return entries.get(index); + } + + /** + * Distance between two vectors. + * This method computes the distance consistent with + * L1 norm, i.e. the sum of the absolute values of + * elements differences. + * + * @param v Vector to which distance is requested. + * @return distance between this vector and {@code v}. + * @throws DimensionMismatchException if the dimensions do not match. + */ + public double getL1Distance(OpenMapRealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + double max = 0; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + double delta = FastMath.abs(iter.value() - v.getEntry(iter.key())); + max += delta; + } + iter = v.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (!entries.containsKey(key)) { + double delta = FastMath.abs(iter.value()); + max += FastMath.abs(delta); + } + } + return max; + } + + /** {@inheritDoc} */ + @Override + public double getL1Distance(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + if (v instanceof OpenMapRealVector) { + return getL1Distance((OpenMapRealVector) v); + } else { + return super.getL1Distance(v); + } + } + + /** + * Optimized method to compute LInfDistance. + * + * @param v Vector to compute distance from. + * @return the LInfDistance. + * @throws DimensionMismatchException if the dimensions do not match. + */ + private double getLInfDistance(OpenMapRealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + double max = 0; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + double delta = FastMath.abs(iter.value() - v.getEntry(iter.key())); + if (delta > max) { + max = delta; + } + } + iter = v.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (!entries.containsKey(key) && iter.value() > max) { + max = iter.value(); + } + } + return max; + } + + /** {@inheritDoc} */ + @Override + public double getLInfDistance(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + if (v instanceof OpenMapRealVector) { + return getLInfDistance((OpenMapRealVector) v); + } else { + return super.getLInfDistance(v); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isInfinite() { + boolean infiniteFound = false; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + final double value = iter.value(); + if (Double.isNaN(value)) { + return false; + } + if (Double.isInfinite(value)) { + infiniteFound = true; + } + } + return infiniteFound; + } + + /** {@inheritDoc} */ + @Override + public boolean isNaN() { + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + if (Double.isNaN(iter.value())) { + return true; + } + } + return false; + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector mapAdd(double d) { + return copy().mapAddToSelf(d); + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector mapAddToSelf(double d) { + for (int i = 0; i < virtualSize; i++) { + setEntry(i, getEntry(i) + d); + } + return this; + } + + /** {@inheritDoc} */ + @Override + public void setEntry(int index, double value) + throws OutOfRangeException { + checkIndex(index); + if (!isDefaultValue(value)) { + entries.put(index, value); + } else if (entries.containsKey(index)) { + entries.remove(index); + } + } + + /** {@inheritDoc} */ + @Override + public void setSubVector(int index, RealVector v) + throws OutOfRangeException { + checkIndex(index); + checkIndex(index + v.getDimension() - 1); + for (int i = 0; i < v.getDimension(); i++) { + setEntry(i + index, v.getEntry(i)); + } + } + + /** {@inheritDoc} */ + @Override + public void set(double value) { + for (int i = 0; i < virtualSize; i++) { + setEntry(i, value); + } + } + + /** + * Optimized method to subtract OpenMapRealVectors. + * + * @param v Vector to subtract from {@code this}. + * @return the difference of {@code this} and {@code v}. + * @throws DimensionMismatchException if the dimensions do not match. + */ + public OpenMapRealVector subtract(OpenMapRealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + OpenMapRealVector res = copy(); + Iterator iter = v.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + if (entries.containsKey(key)) { + res.setEntry(key, entries.get(key) - iter.value()); + } else { + res.setEntry(key, -iter.value()); + } + } + return res; + } + + /** {@inheritDoc} */ + @Override + public RealVector subtract(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + if (v instanceof OpenMapRealVector) { + return subtract((OpenMapRealVector) v); + } else { + return super.subtract(v); + } + } + + /** {@inheritDoc} */ + @Override + public OpenMapRealVector unitVector() throws MathArithmeticException { + OpenMapRealVector res = copy(); + res.unitize(); + return res; + } + + /** {@inheritDoc} */ + @Override + public void unitize() throws MathArithmeticException { + double norm = getNorm(); + if (isDefaultValue(norm)) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + entries.put(iter.key(), iter.value() / norm); + } + } + + /** {@inheritDoc} */ + @Override + public double[] toArray() { + double[] res = new double[virtualSize]; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res[iter.key()] = iter.value(); + } + return res; + } + + /** + * {@inheritDoc} + * Implementation Note: This works on exact values, and as a result + * it is possible for {@code a.subtract(b)} to be the zero vector, while + * {@code a.hashCode() != b.hashCode()}. + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(epsilon); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + virtualSize; + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + temp = Double.doubleToLongBits(iter.value()); + result = prime * result + (int) (temp ^ (temp >>32)); + } + return result; + } + + /** + * {@inheritDoc} + * Implementation Note: This performs an exact comparison, and as a result + * it is possible for {@code a.subtract(b}} to be the zero vector, while + * {@code a.equals(b) == false}. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof OpenMapRealVector)) { + return false; + } + OpenMapRealVector other = (OpenMapRealVector) obj; + if (virtualSize != other.virtualSize) { + return false; + } + if (Double.doubleToLongBits(epsilon) != + Double.doubleToLongBits(other.epsilon)) { + return false; + } + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + double test = other.getEntry(iter.key()); + if (Double.doubleToLongBits(test) != Double.doubleToLongBits(iter.value())) { + return false; + } + } + iter = other.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + double test = iter.value(); + if (Double.doubleToLongBits(test) != Double.doubleToLongBits(getEntry(iter.key()))) { + return false; + } + } + return true; + } + + /** + * + * @return the percentage of none zero elements as a decimal percent. + * @since 2.2 + */ + public double getSparsity() { + return (double)entries.size()/(double)getDimension(); + } + + /** {@inheritDoc} */ + @Override + public java.util.Iterator sparseIterator() { + return new OpenMapSparseIterator(); + } + + /** + * Implementation of {@code Entry} optimized for OpenMap. + * This implementation does not allow arbitrary calls to {@code setIndex} + * since the order in which entries are returned is undefined. + */ + protected class OpenMapEntry extends Entry { + /** Iterator pointing to the entry. */ + private final Iterator iter; + + /** + * Build an entry from an iterator point to an element. + * + * @param iter Iterator pointing to the entry. + */ + protected OpenMapEntry(Iterator iter) { + this.iter = iter; + } + + /** {@inheritDoc} */ + @Override + public double getValue() { + return iter.value(); + } + + /** {@inheritDoc} */ + @Override + public void setValue(double value) { + entries.put(iter.key(), value); + } + + /** {@inheritDoc} */ + @Override + public int getIndex() { + return iter.key(); + } + + } + + /** + * Iterator class to do iteration over just the non-zero elements. + * This implementation is fail-fast, so cannot be used to modify + * any zero element. + */ + protected class OpenMapSparseIterator implements java.util.Iterator { + /** 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java new file mode 100644 index 000000000..2bfc70100 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.IterationManager; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

    + * This abstract class defines preconditioned iterative solvers. When A is + * ill-conditioned, instead of solving system A · x = b directly, it is + * preferable to solve either + *

    + * (M · A) · x = M · b + *
    + * (left preconditioning), or + *
    + * (A · M) · y = b,     followed by + * M · y = x + *
    + * (right preconditioning), where M approximates in some way A-1, + * while matrix-vector products of the type M · y remain comparatively + * easy to compute. In this library, M (not M-1!) is called the + * preconditionner. + *

    + *

    + * Concrete implementations of this abstract class must be provided with the + * preconditioner M, as a {@link RealLinearOperator}. + *

    + * + * @since 3.0 + */ +public abstract class PreconditionedIterativeLinearSolver + extends IterativeLinearSolver { + + /** + * Creates a new instance of this class, with default iteration manager. + * + * @param maxIterations the maximum number of iterations + */ + public PreconditionedIterativeLinearSolver(final int maxIterations) { + super(maxIterations); + } + + /** + * Creates a new instance of this class, with custom iteration manager. + * + * @param manager the custom iteration manager + * @throws NullArgumentException if {@code manager} is {@code null} + */ + public PreconditionedIterativeLinearSolver(final IterationManager manager) + throws NullArgumentException { + super(manager); + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param x0 the initial guess of the solution + * @return a new vector containing the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not + * square + * @throws DimensionMismatchException if {@code m}, {@code b} or + * {@code x0} have dimensions inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public RealVector solve(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, final RealVector x0) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(x0); + return solveInPlace(a, m, b, x0.copy()); + } + + /** {@inheritDoc} */ + @Override + public RealVector solve(final RealLinearOperator a, final RealVector b) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + x.set(0.); + return solveInPlace(a, null, b, x); + } + + /** {@inheritDoc} */ + @Override + public RealVector solve(final RealLinearOperator a, final RealVector b, + final RealVector x0) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(x0); + return solveInPlace(a, null, b, x0.copy()); + } + + /** + * Performs all dimension checks on the parameters of + * {@link #solve(RealLinearOperator, RealLinearOperator, RealVector, RealVector) solve} + * and + * {@link #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector) solveInPlace}, + * and throws an exception if one of the checks fails. + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param x0 the initial guess of the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not + * square + * @throws DimensionMismatchException if {@code m}, {@code b} or + * {@code x0} have dimensions inconsistent with {@code a} + */ + protected static void checkParameters(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, final RealVector x0) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException { + checkParameters(a, b, x0); + if (m != null) { + if (m.getColumnDimension() != m.getRowDimension()) { + throw new NonSquareOperatorException(m.getColumnDimension(), + m.getRowDimension()); + } + if (m.getRowDimension() != a.getRowDimension()) { + throw new DimensionMismatchException(m.getRowDimension(), + a.getRowDimension()); + } + } + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @return a new vector containing the solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not + * square + * @throws DimensionMismatchException if {@code m} or {@code b} have + * dimensions inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public RealVector solve(RealLinearOperator a, RealLinearOperator m, + RealVector b) throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + return solveInPlace(a, m, b, x); + } + + /** + * Returns an estimate of the solution to the linear system A · x = + * b. The solution is computed in-place (initial guess is modified). + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param x0 the initial guess of the solution + * @return a reference to {@code x0} (shallow copy) updated with the + * solution + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not + * square + * @throws DimensionMismatchException if {@code m}, {@code b} or + * {@code x0} have dimensions inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + */ + public abstract RealVector solveInPlace(RealLinearOperator a, + RealLinearOperator m, RealVector b, RealVector x0) throws + NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException; + + /** {@inheritDoc} */ + @Override + public RealVector solveInPlace(final RealLinearOperator a, + final RealVector b, final RealVector x0) throws + NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException { + return solveInPlace(a, null, b, x0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/QRDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/QRDecomposition.java new file mode 100644 index 000000000..e69e8722b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/QRDecomposition.java @@ -0,0 +1,476 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Calculates the QR-decomposition of a matrix. + *

    The QR-decomposition of a matrix A consists of two matrices Q and R + * that satisfy: A = QR, Q is orthogonal (QTQ = I), and R is + * upper triangular. If A is m×n, Q is m×m and R m×n.

    + *

    This class compute the decomposition using Householder reflectors.

    + *

    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.

    + *

    This class is based on the class with similar name from the + * JAMA library, with the + * following changes:

    + *
      + *
    • a {@link #getQT() getQT} method has been added,
    • + *
    • the {@code solve} and {@code isFullRank} methods have been replaced + * by a {@link #getSolver() getSolver} method and the equivalent methods + * provided by the returned {@link DecompositionSolver}.
    • + *
    + * + * @see MathWorld + * @see Wikipedia + * + * @since 1.2 (changed to concrete class in 3.0) + */ +public class QRDecomposition { + /** + * A packed TRANSPOSED representation of the QR decomposition. + *

    The elements BELOW the diagonal are the elements of the UPPER triangular + * matrix R, and the rows ABOVE the diagonal are the Householder reflector vectors + * from which an explicit form of Q can be recomputed if desired.

    + */ + private double[][] qrt; + /** The diagonal elements of R. */ + private double[] rDiag; + /** Cached value of Q. */ + private RealMatrix cachedQ; + /** Cached value of QT. */ + private RealMatrix cachedQT; + /** Cached value of R. */ + private RealMatrix cachedR; + /** Cached value of H. */ + private RealMatrix cachedH; + /** Singularity threshold. */ + private final double threshold; + + /** + * Calculates the QR-decomposition of the given matrix. + * The singularity threshold defaults to zero. + * + * @param matrix The matrix to decompose. + * + * @see #QRDecomposition(RealMatrix,double) + */ + public QRDecomposition(RealMatrix matrix) { + this(matrix, 0d); + } + + /** + * Calculates the QR-decomposition of the given matrix. + * + * @param matrix The matrix to decompose. + * @param threshold Singularity threshold. + */ + public QRDecomposition(RealMatrix matrix, + double threshold) { + this.threshold = threshold; + + final int m = matrix.getRowDimension(); + final int n = matrix.getColumnDimension(); + qrt = matrix.transpose().getData(); + rDiag = new double[FastMath.min(m, n)]; + cachedQ = null; + cachedQT = null; + cachedR = null; + cachedH = null; + + decompose(qrt); + + } + + /** Decompose matrix. + * @param matrix transposed matrix + * @since 3.2 + */ + protected void decompose(double[][] matrix) { + for (int minor = 0; minor < FastMath.min(matrix.length, matrix[0].length); minor++) { + performHouseholderReflection(minor, matrix); + } + } + + /** Perform Householder reflection for a minor A(minor, minor) of A. + * @param minor minor index + * @param matrix transposed matrix + * @since 3.2 + */ + protected void performHouseholderReflection(int minor, double[][] matrix) { + + final double[] qrtMinor = matrix[minor]; + + /* + * Let x be the first column of the minor, and a^2 = |x|^2. + * x will be in the positions qr[minor][minor] through qr[m][minor]. + * The first column of the transformed minor will be (a,0,0,..)' + * The sign of a is chosen to be opposite to the sign of the first + * component of x. Let's find a: + */ + double xNormSqr = 0; + for (int row = minor; row < qrtMinor.length; row++) { + final double c = qrtMinor[row]; + xNormSqr += c * c; + } + final double a = (qrtMinor[minor] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + rDiag[minor] = a; + + if (a != 0.0) { + + /* + * Calculate the normalized reflection vector v and transform + * the first column. We know the norm of v beforehand: v = x-ae + * so |v|^2 = = -2a+a^2 = + * a^2+a^2-2a = 2a*(a - ). + * Here 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/|v|^2 v. + * Therefore the transformation is easily calculated by + * subtracting the column vector (2/|v|^2)v from x. + * + * Let 2/|v|^2 = alpha. From above we have + * |v|^2 = -2a*(qr[minor][minor]), so + * alpha = -/(a*qr[minor][minor]) + */ + for (int col = minor+1; col < matrix.length; col++) { + final double[] qrtCol = matrix[col]; + double alpha = 0; + for (int row = minor; row < qrtCol.length; row++) { + alpha -= qrtCol[row] * qrtMinor[row]; + } + alpha /= a * qrtMinor[minor]; + + // Subtract the column vector alpha*v from x. + for (int row = minor; row < qrtCol.length; row++) { + qrtCol[row] -= alpha * qrtMinor[row]; + } + } + } + } + + + /** + * Returns the matrix R of the decomposition. + *

    R is an upper-triangular matrix

    + * @return the R matrix + */ + public RealMatrix getR() { + + if (cachedR == null) { + + // R is supposed to be m x n + final int n = qrt.length; + final int m = qrt[0].length; + double[][] ra = new double[m][n]; + // copy the diagonal from rDiag and the upper triangle of qr + for (int row = FastMath.min(m, n) - 1; row >= 0; row--) { + ra[row][row] = rDiag[row]; + for (int col = row + 1; col < n; col++) { + ra[row][col] = qrt[col][row]; + } + } + cachedR = MatrixUtils.createRealMatrix(ra); + } + + // return the cached matrix + return cachedR; + } + + /** + * Returns the matrix Q of the decomposition. + *

    Q is an orthogonal matrix

    + * @return the Q matrix + */ + public RealMatrix getQ() { + if (cachedQ == null) { + cachedQ = getQT().transpose(); + } + return cachedQ; + } + + /** + * Returns the transpose of the matrix Q of the decomposition. + *

    Q is an orthogonal matrix

    + * @return the transpose of the Q matrix, QT + */ + public RealMatrix getQT() { + if (cachedQT == null) { + + // QT is supposed to be m x m + final int n = qrt.length; + final int m = qrt[0].length; + double[][] qta = new double[m][m]; + + /* + * Q = Q1 Q2 ... Q_m, so Q is formed by first constructing Q_m and then + * applying the Householder transformations Q_(m-1),Q_(m-2),...,Q1 in + * succession to the result + */ + for (int minor = m - 1; minor >= FastMath.min(m, n); minor--) { + qta[minor][minor] = 1.0d; + } + + for (int minor = FastMath.min(m, n)-1; minor >= 0; minor--){ + final double[] qrtMinor = qrt[minor]; + qta[minor][minor] = 1.0d; + if (qrtMinor[minor] != 0.0) { + for (int col = minor; col < m; col++) { + double alpha = 0; + for (int row = minor; row < m; row++) { + alpha -= qta[col][row] * qrtMinor[row]; + } + alpha /= rDiag[minor] * qrtMinor[minor]; + + for (int row = minor; row < m; row++) { + qta[col][row] += -alpha * qrtMinor[row]; + } + } + } + } + cachedQT = MatrixUtils.createRealMatrix(qta); + } + + // return the cached matrix + return cachedQT; + } + + /** + * Returns the Householder reflector vectors. + *

    H is a lower trapezoidal matrix whose columns represent + * each successive Householder reflector vector. This matrix is used + * to compute Q.

    + * @return a matrix containing the Householder reflector vectors + */ + public RealMatrix getH() { + if (cachedH == null) { + + final int n = qrt.length; + final int m = qrt[0].length; + double[][] ha = new double[m][n]; + for (int i = 0; i < m; ++i) { + for (int j = 0; j < FastMath.min(i + 1, n); ++j) { + ha[i][j] = qrt[j][i] / -rDiag[j]; + } + } + cachedH = MatrixUtils.createRealMatrix(ha); + } + + // return the cached matrix + return cachedH; + } + + /** + * Get a solver for finding the A × X = B solution in least square sense. + *

    + * Least Square sense means a solver can be computed for an overdetermined system, + * (i.e. a system with more equations than unknowns, which corresponds to a tall A + * matrix with more rows than columns). In any case, if the matrix is singular + * within the tolerance set at {@link QRDecomposition#QRDecomposition(RealMatrix, + * double) construction}, an error will be triggered when + * the {@link DecompositionSolver#solve(RealVector) solve} method will be called. + *

    + * @return a solver + */ + public DecompositionSolver getSolver() { + return new Solver(qrt, rDiag, threshold); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + /** + * A packed TRANSPOSED representation of the QR decomposition. + *

    The elements BELOW the diagonal are the elements of the UPPER triangular + * matrix R, and the rows ABOVE the diagonal are the Householder reflector vectors + * from which an explicit form of Q can be recomputed if desired.

    + */ + private final double[][] qrt; + /** The diagonal elements of R. */ + private final double[] rDiag; + /** Singularity threshold. */ + private final double threshold; + + /** + * Build a solver from decomposed matrix. + * + * @param qrt Packed TRANSPOSED representation of the QR decomposition. + * @param rDiag Diagonal elements of R. + * @param threshold Singularity threshold. + */ + private Solver(final double[][] qrt, + final double[] rDiag, + final double threshold) { + this.qrt = qrt; + this.rDiag = rDiag; + this.threshold = threshold; + } + + /** {@inheritDoc} */ + public boolean isNonSingular() { + for (double diag : rDiag) { + if (FastMath.abs(diag) <= threshold) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + public RealVector solve(RealVector b) { + final int n = qrt.length; + final int m = qrt[0].length; + if (b.getDimension() != m) { + throw new DimensionMismatchException(b.getDimension(), m); + } + if (!isNonSingular()) { + throw new SingularMatrixException(); + } + + final double[] x = new double[n]; + final double[] y = b.toArray(); + + // apply Householder transforms to solve Q.y = b + for (int minor = 0; minor < FastMath.min(m, n); minor++) { + + final double[] qrtMinor = qrt[minor]; + double dotProduct = 0; + for (int row = minor; row < m; row++) { + dotProduct += y[row] * qrtMinor[row]; + } + dotProduct /= rDiag[minor] * qrtMinor[minor]; + + for (int row = minor; row < m; row++) { + y[row] += dotProduct * qrtMinor[row]; + } + } + + // solve triangular system R.x = y + for (int row = rDiag.length - 1; row >= 0; --row) { + y[row] /= rDiag[row]; + final double yRow = y[row]; + final double[] qrtRow = qrt[row]; + x[row] = yRow; + for (int i = 0; i < row; i++) { + y[i] -= yRow * qrtRow[i]; + } + } + + return new ArrayRealVector(x, false); + } + + /** {@inheritDoc} */ + public RealMatrix solve(RealMatrix b) { + final int n = qrt.length; + final int m = qrt[0].length; + if (b.getRowDimension() != m) { + throw new DimensionMismatchException(b.getRowDimension(), m); + } + if (!isNonSingular()) { + throw new SingularMatrixException(); + } + + final int columns = b.getColumnDimension(); + final int blockSize = BlockRealMatrix.BLOCK_SIZE; + final int cBlocks = (columns + blockSize - 1) / blockSize; + final double[][] xBlocks = BlockRealMatrix.createBlocksLayout(n, columns); + final double[][] y = new double[b.getRowDimension()][blockSize]; + final double[] alpha = new double[blockSize]; + + for (int kBlock = 0; kBlock < cBlocks; ++kBlock) { + final int kStart = kBlock * blockSize; + final int kEnd = FastMath.min(kStart + blockSize, columns); + final int kWidth = kEnd - kStart; + + // get the right hand side vector + b.copySubMatrix(0, m - 1, kStart, kEnd - 1, y); + + // apply Householder transforms to solve Q.y = b + for (int minor = 0; minor < FastMath.min(m, n); minor++) { + final double[] qrtMinor = qrt[minor]; + final double factor = 1.0 / (rDiag[minor] * qrtMinor[minor]); + + Arrays.fill(alpha, 0, kWidth, 0.0); + for (int row = minor; row < m; ++row) { + final double d = qrtMinor[row]; + final double[] yRow = y[row]; + for (int k = 0; k < kWidth; ++k) { + alpha[k] += d * yRow[k]; + } + } + for (int k = 0; k < kWidth; ++k) { + alpha[k] *= factor; + } + + for (int row = minor; row < m; ++row) { + final double d = qrtMinor[row]; + final double[] yRow = y[row]; + for (int k = 0; k < kWidth; ++k) { + yRow[k] += alpha[k] * d; + } + } + } + + // solve triangular system R.x = y + for (int j = rDiag.length - 1; j >= 0; --j) { + final int jBlock = j / blockSize; + final int jStart = jBlock * blockSize; + final double factor = 1.0 / rDiag[j]; + final double[] yJ = y[j]; + final double[] xBlock = xBlocks[jBlock * cBlocks + kBlock]; + int index = (j - jStart) * kWidth; + for (int k = 0; k < kWidth; ++k) { + yJ[k] *= factor; + xBlock[index++] = yJ[k]; + } + + final double[] qrtJ = qrt[j]; + for (int i = 0; i < j; ++i) { + final double rIJ = qrtJ[i]; + final double[] yI = y[i]; + for (int k = 0; k < kWidth; ++k) { + yI[k] -= yJ[k] * rIJ; + } + } + } + } + + return new BlockRealMatrix(n, columns, xBlocks, false); + } + + /** + * {@inheritDoc} + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public RealMatrix getInverse() { + return solve(MatrixUtils.createRealIdentityMatrix(qrt[0].length)); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RRQRDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RRQRDecomposition.java new file mode 100644 index 000000000..cd3cf0e53 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RRQRDecomposition.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Calculates the rank-revealing QR-decomposition of a matrix, with column pivoting. + *

    The rank-revealing QR-decomposition of a matrix A consists of three matrices Q, + * R and P such that AP=QR. Q is orthogonal (QTQ = I), and R is upper triangular. + * If A is m×n, Q is m×m and R is m×n and P is n×n.

    + *

    QR decomposition with column pivoting produces a rank-revealing QR + * decomposition and the {@link #getRank(double)} method may be used to return the rank of the + * input matrix A.

    + *

    This class compute the decomposition using Householder reflectors.

    + *

    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.

    + *

    This class is based on the class with similar name from the + * JAMA library, with the + * following changes:

    + *
      + *
    • a {@link #getQT() getQT} method has been added,
    • + *
    • the {@code solve} and {@code isFullRank} methods have been replaced + * by a {@link #getSolver() getSolver} method and the equivalent methods + * provided by the returned {@link DecompositionSolver}.
    • + *
    + * + * @see MathWorld + * @see Wikipedia + * + * @since 3.2 + */ +public class RRQRDecomposition extends QRDecomposition { + + /** An array to record the column pivoting for later creation of P. */ + private int[] p; + + /** Cached value of P. */ + private RealMatrix cachedP; + + + /** + * Calculates the QR-decomposition of the given matrix. + * The singularity threshold defaults to zero. + * + * @param matrix The matrix to decompose. + * + * @see #RRQRDecomposition(RealMatrix, double) + */ + public RRQRDecomposition(RealMatrix matrix) { + this(matrix, 0d); + } + + /** + * Calculates the QR-decomposition of the given matrix. + * + * @param matrix The matrix to decompose. + * @param threshold Singularity threshold. + * @see #RRQRDecomposition(RealMatrix) + */ + public RRQRDecomposition(RealMatrix matrix, double threshold) { + super(matrix, threshold); + } + + /** Decompose matrix. + * @param qrt transposed matrix + */ + @Override + protected void decompose(double[][] qrt) { + p = new int[qrt.length]; + for (int i = 0; i < p.length; i++) { + p[i] = i; + } + super.decompose(qrt); + } + + /** Perform Householder reflection for a minor A(minor, minor) of A. + * @param minor minor index + * @param qrt transposed matrix + */ + @Override + protected void performHouseholderReflection(int minor, double[][] qrt) { + + double l2NormSquaredMax = 0; + // Find the unreduced column with the greatest L2-Norm + int l2NormSquaredMaxIndex = minor; + for (int i = minor; i < qrt.length; i++) { + double l2NormSquared = 0; + for (int j = 0; j < qrt[i].length; j++) { + l2NormSquared += qrt[i][j] * qrt[i][j]; + } + if (l2NormSquared > l2NormSquaredMax) { + l2NormSquaredMax = l2NormSquared; + l2NormSquaredMaxIndex = i; + } + } + // swap the current column with that with the greated L2-Norm and record in p + if (l2NormSquaredMaxIndex != minor) { + double[] tmp1 = qrt[minor]; + qrt[minor] = qrt[l2NormSquaredMaxIndex]; + qrt[l2NormSquaredMaxIndex] = tmp1; + int tmp2 = p[minor]; + p[minor] = p[l2NormSquaredMaxIndex]; + p[l2NormSquaredMaxIndex] = tmp2; + } + + super.performHouseholderReflection(minor, qrt); + + } + + + /** + * Returns the pivot matrix, P, used in the QR Decomposition of matrix A such that AP = QR. + * + * If no pivoting is used in this decomposition then P is equal to the identity matrix. + * + * @return a permutation matrix. + */ + public RealMatrix getP() { + if (cachedP == null) { + int n = p.length; + cachedP = MatrixUtils.createRealMatrix(n,n); + for (int i = 0; i < n; i++) { + cachedP.setEntry(p[i], i, 1); + } + } + return cachedP ; + } + + /** + * Return the effective numerical matrix rank. + *

    The effective numerical rank is the number of non-negligible + * singular values.

    + *

    This implementation looks at Frobenius norms of the sequence of + * bottom right submatrices. When a large fall in norm is seen, + * the rank is returned. The drop is computed as:

    + *
    +     *   (thisNorm/lastNorm) * rNorm < dropThreshold
    +     * 
    + *

    + * where thisNorm is the Frobenius norm of the current submatrix, + * lastNorm is the Frobenius norm of the previous submatrix, + * rNorm is is the Frobenius norm of the complete matrix + *

    + * + * @param dropThreshold threshold triggering rank computation + * @return effective numerical matrix rank + */ + public int getRank(final double dropThreshold) { + RealMatrix r = getR(); + int rows = r.getRowDimension(); + int columns = r.getColumnDimension(); + int rank = 1; + double lastNorm = r.getFrobeniusNorm(); + double rNorm = lastNorm; + while (rank < FastMath.min(rows, columns)) { + double thisNorm = r.getSubMatrix(rank, rows - 1, rank, columns - 1).getFrobeniusNorm(); + if (thisNorm == 0 || (thisNorm / lastNorm) * rNorm < dropThreshold) { + break; + } + lastNorm = thisNorm; + rank++; + } + return rank; + } + + /** + * Get a solver for finding the A × X = B solution in least square sense. + *

    + * Least Square sense means a solver can be computed for an overdetermined system, + * (i.e. a system with more equations than unknowns, which corresponds to a tall A + * matrix with more rows than columns). In any case, if the matrix is singular + * within the tolerance set at {@link RRQRDecomposition#RRQRDecomposition(RealMatrix, + * double) construction}, an error will be triggered when + * the {@link DecompositionSolver#solve(RealVector) solve} method will be called. + *

    + * @return a solver + */ + @Override + public DecompositionSolver getSolver() { + return new Solver(super.getSolver(), this.getP()); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + + /** Upper level solver. */ + private final DecompositionSolver upper; + + /** A permutation matrix for the pivots used in the QR decomposition */ + private RealMatrix p; + + /** + * Build a solver from decomposed matrix. + * + * @param upper upper level solver. + * @param p permutation matrix + */ + private Solver(final DecompositionSolver upper, final RealMatrix p) { + this.upper = upper; + this.p = p; + } + + /** {@inheritDoc} */ + public boolean isNonSingular() { + return upper.isNonSingular(); + } + + /** {@inheritDoc} */ + public RealVector solve(RealVector b) { + return p.operate(upper.solve(b)); + } + + /** {@inheritDoc} */ + public RealMatrix solve(RealMatrix b) { + return p.multiply(upper.solve(b)); + } + + /** + * {@inheritDoc} + * @throws SingularMatrixException if the decomposed matrix is singular. + */ + public RealMatrix getInverse() { + return solve(MatrixUtils.createRealIdentityMatrix(p.getRowDimension())); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealLinearOperator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealLinearOperator.java new file mode 100644 index 000000000..837e1f939 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealLinearOperator.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * This class defines a linear operator operating on real ({@code double}) + * vector spaces. No direct access to the coefficients of the underlying matrix + * is provided. + * + * The motivation for such an interface is well stated by + * Barrett et al. (1994): + *
    + * We restrict ourselves to iterative methods, which work by repeatedly + * improving an approximate solution until it is accurate enough. These + * methods access the coefficient matrix A of the linear system only via the + * matrix-vector product y = A · x + * (and perhaps z = AT · x). Thus the user need only + * supply a subroutine for computing y (and perhaps z) given x, which permits + * full exploitation of the sparsity or other special structure of A. + *
    + *
    + * + *
    + *
    Barret et al. (1994)
    + *
    + * R. Barrett, M. Berry, T. F. Chan, J. Demmel, J. M. Donato, J. Dongarra, + * V. Eijkhout, R. Pozo, C. Romine and H. Van der Vorst, + * Templates for the Solution of Linear Systems: Building Blocks for + * Iterative Methods, SIAM + *
    + *
    + * + * @since 3.0 + */ +public abstract class RealLinearOperator { + /** + * Returns the dimension of the codomain of this operator. + * + * @return the number of rows of the underlying matrix + */ + public abstract int getRowDimension(); + + /** + * Returns the dimension of the domain of this operator. + * + * @return the number of columns of the underlying matrix + */ + public abstract int getColumnDimension(); + + /** + * Returns the result of multiplying {@code this} by the vector {@code x}. + * + * @param x the vector to operate on + * @return the product of {@code this} instance with {@code x} + * @throws DimensionMismatchException if the column dimension does not match + * the size of {@code x} + */ + public abstract RealVector operate(final RealVector x) + throws DimensionMismatchException; + + /** + * Returns the result of multiplying the transpose of {@code this} operator + * by the vector {@code x} (optional operation). The default implementation + * throws an {@link UnsupportedOperationException}. Users overriding this + * method must also override {@link #isTransposable()}. + * + * @param x the vector to operate on + * @return the product of the transpose of {@code this} instance with + * {@code x} + * @throws DimensionMismatchException + * if the row dimension does not match the size of {@code x} + * @throws UnsupportedOperationException if this operation is not supported + * by {@code this} operator + */ + public RealVector operateTranspose(final RealVector x) + throws DimensionMismatchException, UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + /** + * Returns {@code true} if this operator supports + * {@link #operateTranspose(RealVector)}. If {@code true} is returned, + * {@link #operateTranspose(RealVector)} should not throw + * {@code UnsupportedOperationException}. The default implementation returns + * {@code false}. + * + * @return {@code false} + */ + public boolean isTransposable() { + return false; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrix.java new file mode 100644 index 000000000..7cce88fd2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrix.java @@ -0,0 +1,828 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Interface defining a real-valued matrix with basic algebraic operations. + *

    + * Matrix element indexing is 0-based -- e.g., getEntry(0, 0) + * returns the element in the first row, first column of the matrix.

    + * + */ +public interface RealMatrix extends AnyMatrix { + + /** + * Create a new RealMatrix of the same type as the instance with the + * supplied + * row and column dimensions. + * + * @param rowDimension the number of rows in the new matrix + * @param columnDimension the number of columns in the new matrix + * @return a new matrix of the same type as the instance + * @throws NotStrictlyPositiveException if row or column dimension is not + * positive. + * @since 2.0 + */ + RealMatrix createMatrix(int rowDimension, int columnDimension) + throws NotStrictlyPositiveException; + + /** + * Returns a (deep) copy of this. + * + * @return matrix copy + */ + RealMatrix copy(); + + /** + * Returns the sum of {@code this} and {@code m}. + * + * @param m matrix to be added + * @return {@code this + m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + RealMatrix add(RealMatrix m) + throws MatrixDimensionMismatchException; + + /** + * Returns {@code this} minus {@code m}. + * + * @param m matrix to be subtracted + * @return {@code this - m} + * @throws MatrixDimensionMismatchException if {@code m} is not the same + * size as {@code this}. + */ + RealMatrix subtract(RealMatrix m) + throws MatrixDimensionMismatchException; + + /** + * Returns the result of adding {@code d} to each entry of {@code this}. + * + * @param d value to be added to each entry + * @return {@code d + this} + */ + RealMatrix scalarAdd(double d); + + /** + * Returns the result of multiplying each entry of {@code this} by + * {@code d}. + * + * @param d value to multiply all entries by + * @return {@code d * this} + */ + RealMatrix scalarMultiply(double d); + + /** + * Returns the result of postmultiplying {@code this} by {@code m}. + * + * @param m matrix to postmultiply by + * @return {@code this * m} + * @throws DimensionMismatchException if + * {@code columnDimension(this) != rowDimension(m)} + */ + RealMatrix multiply(RealMatrix m) + throws DimensionMismatchException; + + /** + * Returns the result of premultiplying {@code this} by {@code m}. + * + * @param m matrix to premultiply by + * @return {@code m * this} + * @throws DimensionMismatchException if + * {@code rowDimension(this) != columnDimension(m)} + */ + RealMatrix preMultiply(RealMatrix m) + throws DimensionMismatchException; + + /** + * Returns the result of multiplying {@code this} with itself {@code p} + * times. Depending on the underlying storage, instability for high powers + * might occur. + * + * @param p raise {@code this} to power {@code p} + * @return {@code this^p} + * @throws NotPositiveException if {@code p < 0} + * @throws NonSquareMatrixException if the matrix is not square + */ + RealMatrix power(final int p) + throws NotPositiveException, NonSquareMatrixException; + + /** + * Returns matrix entries as a two-dimensional array. + * + * @return 2-dimensional array of entries + */ + double[][] getData(); + + /** + * Returns the + * maximum absolute row sum norm of the matrix. + * + * @return norm + */ + double getNorm(); + + /** + * Returns the + * Frobenius norm of the matrix. + * + * @return norm + */ + double getFrobeniusNorm(); + + /** + * Gets a submatrix. Rows and columns are indicated + * counting from 0 to n-1. + * + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @return The subMatrix containing the data of the + * specified rows and columns. + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + */ + RealMatrix getSubMatrix(int startRow, int endRow, int startColumn, + int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Gets a submatrix. Rows and columns are indicated counting from 0 to n-1. + * + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @return The subMatrix containing the data in the specified rows and + * columns + * @throws NullArgumentException if the row or column selections are + * {@code null} + * @throws NoDataException if the row or column selections are empty (zero + * length). + * @throws OutOfRangeException if the indices are not valid. + */ + RealMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns) + throws NullArgumentException, NoDataException, OutOfRangeException; + + /** + * Copy a submatrix. Rows and columns are indicated counting from 0 to n-1. + * + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @param destination The arrays where the submatrix data should be copied + * (if larger than rows/columns counts, only the upper-left part will be + * used) + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @throws MatrixDimensionMismatchException if the destination array is too + * small. + */ + void copySubMatrix(int startRow, int endRow, int startColumn, + int endColumn, double[][] destination) + throws OutOfRangeException, NumberIsTooSmallException, + MatrixDimensionMismatchException; + + /** + * Copy a submatrix. Rows and columns are indicated counting from 0 to n-1. + * + * @param selectedRows Array of row indices. + * @param selectedColumns Array of column indices. + * @param destination The arrays where the submatrix data should be copied + * (if larger than rows/columns counts, only the upper-left part will be + * used) + * @throws NullArgumentException if the row or column selections are + * {@code null} + * @throws NoDataException if the row or column selections are empty (zero + * length). + * @throws OutOfRangeException if the indices are not valid. + * @throws MatrixDimensionMismatchException if the destination array is too + * small. + */ + void copySubMatrix(int[] selectedRows, int[] selectedColumns, + double[][] destination) + throws OutOfRangeException, NullArgumentException, NoDataException, + MatrixDimensionMismatchException; + + /** + * Replace the submatrix starting at {@code row, column} using data in the + * input {@code subMatrix} array. Indexes are 0-based. + *

    + * Example:
    + * Starting with

    +    * 1  2  3  4
    +    * 5  6  7  8
    +    * 9  0  1  2
    +    * 
    + * and subMatrix = {{3, 4} {5,6}}, invoking + * {@code setSubMatrix(subMatrix,1,1))} will result in
    +    * 1  2  3  4
    +    * 5  3  4  8
    +    * 9  5  6  2
    +    * 

    + * + * @param subMatrix array containing the submatrix replacement data + * @param row row coordinate of the top, left element to be replaced + * @param column column coordinate of the top, left element to be replaced + * @throws NoDataException if {@code subMatrix} is empty. + * @throws OutOfRangeException if {@code subMatrix} does not fit into + * this matrix from element in {@code (row, column)}. + * @throws DimensionMismatchException if {@code subMatrix} is not rectangular + * (not all rows have the same length) or empty. + * @throws NullArgumentException if {@code subMatrix} is {@code null}. + * @since 2.0 + */ + void setSubMatrix(double[][] subMatrix, int row, int column) + throws NoDataException, OutOfRangeException, + DimensionMismatchException, NullArgumentException; + + /** + * Get the entries at the given row index as a row matrix. Row indices start + * at 0. + * + * @param row Row to be fetched. + * @return row Matrix. + * @throws OutOfRangeException if the specified row index is invalid. + */ + RealMatrix getRowMatrix(int row) throws OutOfRangeException; + + /** + * Sets the specified {@code row} of {@code this} matrix to the entries of + * the specified row {@code matrix}. Row indices start at 0. + * + * @param row Row to be set. + * @param matrix Row matrix to be copied (must have one row and the same + * number of columns as the instance). + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the row dimension of the + * {@code matrix} is not {@code 1}, or the column dimensions of {@code this} + * and {@code matrix} do not match. + */ + void setRowMatrix(int row, RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Get the entries at the given column index as a column matrix. Column + * indices start at 0. + * + * @param column Column to be fetched. + * @return column Matrix. + * @throws OutOfRangeException if the specified column index is invalid. + */ + RealMatrix getColumnMatrix(int column) + throws OutOfRangeException; + + /** + * Sets the specified {@code column} of {@code this} matrix to the entries + * of the specified column {@code matrix}. Column indices start at 0. + * + * @param column Column to be set. + * @param matrix Column matrix to be copied (must have one column and the + * same number of rows as the instance). + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the column dimension of the + * {@code matrix} is not {@code 1}, or the row dimensions of {@code this} + * and {@code matrix} do not match. + */ + void setColumnMatrix(int column, RealMatrix matrix) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Returns the entries in row number {@code row} as a vector. Row indices + * start at 0. + * + * @param row Row to be fetched. + * @return a row vector. + * @throws OutOfRangeException if the specified row index is invalid. + */ + RealVector getRowVector(int row) + throws OutOfRangeException; + + /** + * Sets the specified {@code row} of {@code this} matrix to the entries of + * the specified {@code vector}. Row indices start at 0. + * + * @param row Row to be set. + * @param vector row vector to be copied (must have the same number of + * column as the instance). + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the {@code vector} dimension + * does not match the column dimension of {@code this} matrix. + */ + void setRowVector(int row, RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Get the entries at the given column index as a vector. Column indices + * start at 0. + * + * @param column Column to be fetched. + * @return a column vector. + * @throws OutOfRangeException if the specified column index is invalid + */ + RealVector getColumnVector(int column) + throws OutOfRangeException; + + /** + * Sets the specified {@code column} of {@code this} matrix to the entries + * of the specified {@code vector}. Column indices start at 0. + * + * @param column Column to be set. + * @param vector column vector to be copied (must have the same number of + * rows as the instance). + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the {@code vector} dimension + * does not match the row dimension of {@code this} matrix. + */ + void setColumnVector(int column, RealVector vector) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Get the entries at the given row index. Row indices start at 0. + * + * @param row Row to be fetched. + * @return the array of entries in the row. + * @throws OutOfRangeException if the specified row index is not valid. + */ + double[] getRow(int row) throws OutOfRangeException; + + /** + * Sets the specified {@code row} of {@code this} matrix to the entries + * of the specified {@code array}. Row indices start at 0. + * + * @param row Row to be set. + * @param array Row matrix to be copied (must have the same number of + * columns as the instance) + * @throws OutOfRangeException if the specified row index is invalid. + * @throws MatrixDimensionMismatchException if the {@code array} length does + * not match the column dimension of {@code this} matrix. + */ + void setRow(int row, double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Get the entries at the given column index as an array. Column indices + * start at 0. + * + * @param column Column to be fetched. + * @return the array of entries in the column. + * @throws OutOfRangeException if the specified column index is not valid. + */ + double[] getColumn(int column) throws OutOfRangeException; + + /** + * Sets the specified {@code column} of {@code this} matrix to the entries + * of the specified {@code array}. Column indices start at 0. + * + * @param column Column to be set. + * @param array Column array to be copied (must have the same number of + * rows as the instance). + * @throws OutOfRangeException if the specified column index is invalid. + * @throws MatrixDimensionMismatchException if the {@code array} length does + * not match the row dimension of {@code this} matrix. + */ + void setColumn(int column, double[] array) + throws OutOfRangeException, MatrixDimensionMismatchException; + + /** + * Get the entry in the specified row and column. Row and column indices + * start at 0. + * + * @param row Row index of entry to be fetched. + * @param column Column index of entry to be fetched. + * @return the matrix entry at {@code (row, column)}. + * @throws OutOfRangeException if the row or column index is not valid. + */ + double getEntry(int row, int column) throws OutOfRangeException; + + /** + * Set the entry in the specified row and column. Row and column indices + * start at 0. + * + * @param row Row index of entry to be set. + * @param column Column index of entry to be set. + * @param value the new value of the entry. + * @throws OutOfRangeException if the row or column index is not valid + * @since 2.0 + */ + void setEntry(int row, int column, double value) throws OutOfRangeException; + + /** + * Adds (in place) the specified value to the specified entry of + * {@code this} matrix. Row and column indices start at 0. + * + * @param row Row index of the entry to be modified. + * @param column Column index of the entry to be modified. + * @param increment value to add to the matrix entry. + * @throws OutOfRangeException if the row or column index is not valid. + * @since 2.0 + */ + void addToEntry(int row, int column, double increment) throws OutOfRangeException; + + /** + * Multiplies (in place) the specified entry of {@code this} matrix by the + * specified value. Row and column indices start at 0. + * + * @param row Row index of the entry to be modified. + * @param column Column index of the entry to be modified. + * @param factor Multiplication factor for the matrix entry. + * @throws OutOfRangeException if the row or column index is not valid. + * @since 2.0 + */ + void multiplyEntry(int row, int column, double factor) throws OutOfRangeException; + + /** + * Returns the transpose of this matrix. + * + * @return transpose matrix + */ + RealMatrix transpose(); + + /** + * Returns the + * trace of the matrix (the sum of the elements on the main diagonal). + * + * @return the trace. + * @throws NonSquareMatrixException if the matrix is not square. + */ + double getTrace() throws NonSquareMatrixException; + + /** + * Returns the result of multiplying this by the vector {@code v}. + * + * @param v the vector to operate on + * @return {@code this * v} + * @throws DimensionMismatchException if the length of {@code v} does not + * match the column dimension of {@code this}. + */ + double[] operate(double[] v) throws DimensionMismatchException; + + /** + * Returns the result of multiplying this by the vector {@code v}. + * + * @param v the vector to operate on + * @return {@code this * v} + * @throws DimensionMismatchException if the dimension of {@code v} does not + * match the column dimension of {@code this}. + */ + RealVector operate(RealVector v) throws DimensionMismatchException; + + /** + * Returns the (row) vector result of premultiplying this by the vector {@code v}. + * + * @param v the row vector to premultiply by + * @return {@code v * this} + * @throws DimensionMismatchException if the length of {@code v} does not + * match the row dimension of {@code this}. + */ + double[] preMultiply(double[] v) throws DimensionMismatchException; + + /** + * Returns the (row) vector result of premultiplying this by the vector {@code v}. + * + * @param v the row vector to premultiply by + * @return {@code v * this} + * @throws DimensionMismatchException if the dimension of {@code v} does not + * match the row dimension of {@code this}. + */ + RealVector preMultiply(RealVector v) throws DimensionMismatchException; + + /** + * Visit (and possibly change) all matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInRowOrder(RealMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInRowOrder(RealMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInRowOrder(RealMatrixChangingVisitor visitor, int startRow, + int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (but don't change) some matrix entries in row order. + *

    Row order starts at upper left and iterating through all elements + * of a row from left to right before going to the leftmost element + * of the next row.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInRowOrder(RealMatrixPreservingVisitor visitor, int startRow, + int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (and possibly change) all matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInColumnOrder(RealMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInColumnOrder(RealMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInColumnOrder(RealMatrixChangingVisitor visitor, int startRow, + int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (but don't change) some matrix entries in column order. + *

    Column order starts at upper left and iterating through all elements + * of a column from top to bottom before going to the topmost element + * of the next column.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInColumnOrder(RealMatrixPreservingVisitor visitor, int startRow, + int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (and possibly change) all matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInOptimizedOrder(RealMatrixChangingVisitor visitor); + + /** + * Visit (but don't change) all matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInOptimizedOrder(RealMatrixPreservingVisitor visitor); + + /** + * Visit (and possibly change) some matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end + * of the walk + */ + double walkInOptimizedOrder(RealMatrixChangingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; + + /** + * Visit (but don't change) some matrix entries using the fastest possible order. + *

    The fastest walking order depends on the exact matrix class. It may be + * different from traditional row or column orders.

    + * @param visitor visitor used to process all matrix entries + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + * @throws OutOfRangeException if the indices are not valid. + * @throws NumberIsTooSmallException if {@code endRow < startRow} or + * {@code endColumn < startColumn}. + * @see #walkInRowOrder(RealMatrixChangingVisitor) + * @see #walkInRowOrder(RealMatrixPreservingVisitor) + * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixChangingVisitor) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor) + * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int) + * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor) + * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor) + * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int) + * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end + * of the walk + */ + double walkInOptimizedOrder(RealMatrixPreservingVisitor visitor, + int startRow, int endRow, int startColumn, int endColumn) + throws OutOfRangeException, NumberIsTooSmallException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixChangingVisitor.java new file mode 100644 index 000000000..bb3941349 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixChangingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Interface defining a visitor for matrix entries. + * + * @see DefaultRealMatrixChangingVisitor + * @since 2.0 + */ +public interface RealMatrixChangingVisitor { + /** + * Start visiting a matrix. + *

    This method is called once before any entry of the matrix is visited.

    + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + */ + void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn); + + /** + * Visit one matrix entry. + * @param row row index of the entry + * @param column column index of the entry + * @param value current value of the entry + * @return the new value to be set for the entry + */ + double visit(int row, int column, double value); + + /** + * End visiting a matrix. + *

    This method is called once after all entries of the matrix have been visited.

    + * @return the value that the walkInXxxOrder must return + */ + double end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixFormat.java new file mode 100644 index 000000000..73e66dda1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixFormat.java @@ -0,0 +1,394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a {@code nxm} matrix in components list format + * "{{a00,a01, ..., + * a0m-1},{a10, + * a11, ..., a1m-1},{...},{ + * an-10, an-11, ..., + * an-1m-1}}". + *

    The prefix and suffix "{" and "}", the row prefix and suffix "{" and "}", + * the row separator "," and the column separator "," can be replaced by any + * user-defined strings. The number format for components can be configured.

    + * + *

    White space is ignored at parse time, even if it is in the prefix, suffix + * or separator specifications. So even if the default separator does include a space + * character that is used at format time, both input string "{{1,1,1}}" and + * " { { 1 , 1 , 1 } } " will be parsed without error and the same matrix will be + * returned. In the second case, however, the parse position after parsing will be + * just after the closing curly brace, i.e. just before the trailing space.

    + * + *

    Note: the grouping functionality of the used {@link NumberFormat} is + * disabled to prevent problems when parsing (e.g. 1,345.34 would be a valid number + * but conflicts with the default column separator).

    + * + * @since 3.1 + */ +public class RealMatrixFormat { + + /** The default prefix: "{". */ + private static final String DEFAULT_PREFIX = "{"; + /** The default suffix: "}". */ + private static final String DEFAULT_SUFFIX = "}"; + /** The default row prefix: "{". */ + private static final String DEFAULT_ROW_PREFIX = "{"; + /** The default row suffix: "}". */ + private static final String DEFAULT_ROW_SUFFIX = "}"; + /** The default row separator: ",". */ + private static final String DEFAULT_ROW_SEPARATOR = ","; + /** The default column separator: ",". */ + private static final String DEFAULT_COLUMN_SEPARATOR = ","; + /** Prefix. */ + private final String prefix; + /** Suffix. */ + private final String suffix; + /** Row prefix. */ + private final String rowPrefix; + /** Row suffix. */ + private final String rowSuffix; + /** Row separator. */ + private final String rowSeparator; + /** Column separator. */ + private final String columnSeparator; + /** The format used for components. */ + private final NumberFormat format; + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and row/column separator: + * "[", "]", ";" and ", " and the default number format for components.

    + */ + public RealMatrixFormat() { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ROW_PREFIX, DEFAULT_ROW_SUFFIX, + DEFAULT_ROW_SEPARATOR, DEFAULT_COLUMN_SEPARATOR, CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + public RealMatrixFormat(final NumberFormat format) { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ROW_PREFIX, DEFAULT_ROW_SUFFIX, + DEFAULT_ROW_SEPARATOR, DEFAULT_COLUMN_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param rowPrefix row prefix to use instead of the default "{" + * @param rowSuffix row suffix to use instead of the default "}" + * @param rowSeparator tow separator to use instead of the default ";" + * @param columnSeparator column separator to use instead of the default ", " + */ + public RealMatrixFormat(final String prefix, final String suffix, + final String rowPrefix, final String rowSuffix, + final String rowSeparator, final String columnSeparator) { + this(prefix, suffix, rowPrefix, rowSuffix, rowSeparator, columnSeparator, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param rowPrefix row prefix to use instead of the default "{" + * @param rowSuffix row suffix to use instead of the default "}" + * @param rowSeparator tow separator to use instead of the default ";" + * @param columnSeparator column separator to use instead of the default ", " + * @param format the custom format for components. + */ + public RealMatrixFormat(final String prefix, final String suffix, + final String rowPrefix, final String rowSuffix, + final String rowSeparator, final String columnSeparator, + final NumberFormat format) { + this.prefix = prefix; + this.suffix = suffix; + this.rowPrefix = rowPrefix; + this.rowSuffix = rowSuffix; + this.rowSeparator = rowSeparator; + this.columnSeparator = columnSeparator; + this.format = format; + // disable grouping to prevent parsing problems + this.format.setGroupingUsed(false); + } + + /** + * Get the set of locales for which real vectors formats are available. + *

    This is the same set as the {@link NumberFormat} set.

    + * @return available real vector format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * Get the format prefix. + * @return format prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Get the format suffix. + * @return format suffix. + */ + public String getSuffix() { + return suffix; + } + + /** + * Get the format prefix. + * @return format prefix. + */ + public String getRowPrefix() { + return rowPrefix; + } + + /** + * Get the format suffix. + * @return format suffix. + */ + public String getRowSuffix() { + return rowSuffix; + } + + /** + * Get the format separator between rows of the matrix. + * @return format separator for rows. + */ + public String getRowSeparator() { + return rowSeparator; + } + + /** + * Get the format separator between components. + * @return format separator between components. + */ + public String getColumnSeparator() { + return columnSeparator; + } + + /** + * Get the components format. + * @return components format. + */ + public NumberFormat getFormat() { + return format; + } + + /** + * Returns the default real vector format for the current locale. + * @return the default real vector format. + */ + public static RealMatrixFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default real vector format for the given locale. + * @param locale the specific locale used by the format. + * @return the real vector format specific to the given locale. + */ + public static RealMatrixFormat getInstance(final Locale locale) { + return new RealMatrixFormat(CompositeFormat.getDefaultNumberFormat(locale)); + } + + /** + * This method calls {@link #format(RealMatrix,StringBuffer,FieldPosition)}. + * + * @param m RealMatrix object to format. + * @return a formatted matrix. + */ + public String format(RealMatrix m) { + return format(m, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats a {@link RealMatrix} object to produce a string. + * @param matrix the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(RealMatrix matrix, StringBuffer toAppendTo, + FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + // format prefix + toAppendTo.append(prefix); + + // format rows + final int rows = matrix.getRowDimension(); + for (int i = 0; i < rows; ++i) { + toAppendTo.append(rowPrefix); + for (int j = 0; j < matrix.getColumnDimension(); ++j) { + if (j > 0) { + toAppendTo.append(columnSeparator); + } + CompositeFormat.formatDouble(matrix.getEntry(i, j), format, toAppendTo, pos); + } + toAppendTo.append(rowSuffix); + if (i < rows - 1) { + toAppendTo.append(rowSeparator); + } + } + + // format suffix + toAppendTo.append(suffix); + + return toAppendTo; + } + + /** + * Parse a string to produce a {@link RealMatrix} object. + * + * @param source String to parse. + * @return the parsed {@link RealMatrix} object. + * @throws MathParseException if the beginning of the specified string + * cannot be parsed. + */ + public RealMatrix parse(String source) { + final ParsePosition parsePosition = new ParsePosition(0); + final RealMatrix result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + Array2DRowRealMatrix.class); + } + return result; + } + + /** + * Parse a string to produce a {@link RealMatrix} object. + * + * @param source String to parse. + * @param pos input/ouput parsing parameter. + * @return the parsed {@link RealMatrix} object. + */ + public RealMatrix parse(String source, ParsePosition pos) { + int initialIndex = pos.getIndex(); + + final String trimmedPrefix = prefix.trim(); + final String trimmedSuffix = suffix.trim(); + final String trimmedRowPrefix = rowPrefix.trim(); + final String trimmedRowSuffix = rowSuffix.trim(); + final String trimmedColumnSeparator = columnSeparator.trim(); + final String trimmedRowSeparator = rowSeparator.trim(); + + // parse prefix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) { + return null; + } + + // parse components + List> matrix = new ArrayList>(); + List rowComponents = new ArrayList(); + for (boolean loop = true; loop;){ + + if (!rowComponents.isEmpty()) { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedColumnSeparator, pos)) { + if (trimmedRowSuffix.length() != 0 && + !CompositeFormat.parseFixedstring(source, trimmedRowSuffix, pos)) { + return null; + } else { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (CompositeFormat.parseFixedstring(source, trimmedRowSeparator, pos)) { + matrix.add(rowComponents); + rowComponents = new ArrayList(); + continue; + } else { + loop = false; + } + } + } + } else { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (trimmedRowPrefix.length() != 0 && + !CompositeFormat.parseFixedstring(source, trimmedRowPrefix, pos)) { + return null; + } + } + + if (loop) { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + Number component = CompositeFormat.parseNumber(source, format, pos); + if (component != null) { + rowComponents.add(component); + } else { + if (rowComponents.isEmpty()) { + loop = false; + } else { + // invalid component + // set index back to initial, error index should already be set + pos.setIndex(initialIndex); + return null; + } + } + } + + } + + if (!rowComponents.isEmpty()) { + matrix.add(rowComponents); + } + + // parse suffix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) { + return null; + } + + // do not allow an empty matrix + if (matrix.isEmpty()) { + pos.setIndex(initialIndex); + return null; + } + + // build vector + double[][] data = new double[matrix.size()][]; + int row = 0; + for (List rowList : matrix) { + data[row] = new double[rowList.size()]; + for (int i = 0; i < rowList.size(); i++) { + data[row][i] = rowList.get(i).doubleValue(); + } + row++; + } + return MatrixUtils.createRealMatrix(data); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.java new file mode 100644 index 000000000..1102dea2a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Interface defining a visitor for matrix entries. + * + * @see DefaultRealMatrixPreservingVisitor + * @since 2.0 + */ +public interface RealMatrixPreservingVisitor { + /** + * Start visiting a matrix. + *

    This method is called once before any entry of the matrix is visited.

    + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param startRow Initial row index + * @param endRow Final row index (inclusive) + * @param startColumn Initial column index + * @param endColumn Final column index (inclusive) + */ + void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn); + + /** + * Visit one matrix entry. + * @param row row index of the entry + * @param column column index of the entry + * @param value current value of the entry + */ + void visit(int row, int column, double value); + + /** + * End visiting a matrix. + *

    This method is called once after all entries of the matrix have been visited.

    + * @return the value that the walkInXxxOrder must return + */ + double end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVector.java new file mode 100644 index 000000000..fead2aac2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVector.java @@ -0,0 +1,1617 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.function.Add; +import com.fr.third.org.apache.commons.math3.analysis.function.Divide; +import com.fr.third.org.apache.commons.math3.analysis.function.Multiply; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Class defining a real-valued vector with basic algebraic operations. + *

    + * vector element indexing is 0-based -- e.g., {@code getEntry(0)} + * returns the first element of the vector. + *

    + *

    + * The {@code code map} and {@code mapToSelf} methods operate + * on vectors element-wise, i.e. they perform the same operation (adding a scalar, + * applying a function ...) on each element in turn. The {@code map} + * versions create a new vector to hold the result and do not change the instance. + * The {@code mapToSelf} version uses the instance itself to store the + * results, so the instance is changed by this method. In all cases, the result + * vector is returned by the methods, allowing the fluent API + * style, like this: + *

    + *
    + *   RealVector result = v.mapAddToSelf(3.4).mapToSelf(new Tan()).mapToSelf(new Power(2.3));
    + * 
    + * + * @since 2.1 + */ +public abstract class RealVector { + /** + * Returns the size of the vector. + * + * @return the size of this vector. + */ + public abstract int getDimension(); + + /** + * Return the entry at the specified index. + * + * @param index Index location of entry to be fetched. + * @return the vector entry at {@code index}. + * @throws OutOfRangeException if the index is not valid. + * @see #setEntry(int, double) + */ + public abstract double getEntry(int index) throws OutOfRangeException; + + /** + * Set a single element. + * + * @param index element index. + * @param value new value for the element. + * @throws OutOfRangeException if the index is not valid. + * @see #getEntry(int) + */ + public abstract void setEntry(int index, double value) + throws OutOfRangeException; + + /** + * Change an entry at the specified index. + * + * @param index Index location of entry to be set. + * @param increment Value to add to the vector entry. + * @throws OutOfRangeException if the index is not valid. + * @since 3.0 + */ + public void addToEntry(int index, double increment) + throws OutOfRangeException { + setEntry(index, getEntry(index) + increment); + } + + /** + * Construct a new vector by appending a vector to this vector. + * + * @param v vector to append to this one. + * @return a new vector. + */ + public abstract RealVector append(RealVector v); + + /** + * Construct a new vector by appending a double to this vector. + * + * @param d double to append. + * @return a new vector. + */ + public abstract RealVector append(double d); + + /** + * Get a subvector from consecutive elements. + * + * @param index index of first element. + * @param n number of elements to be retrieved. + * @return a vector containing n elements. + * @throws OutOfRangeException if the index is not valid. + * @throws NotPositiveException if the number of elements is not positive. + */ + public abstract RealVector getSubVector(int index, int n) + throws NotPositiveException, OutOfRangeException; + + /** + * Set a sequence of consecutive elements. + * + * @param index index of first element to be set. + * @param v vector containing the values to set. + * @throws OutOfRangeException if the index is not valid. + */ + public abstract void setSubVector(int index, RealVector v) + throws OutOfRangeException; + + /** + * Check whether any coordinate of this vector is {@code NaN}. + * + * @return {@code true} if any coordinate of this vector is {@code NaN}, + * {@code false} otherwise. + */ + public abstract boolean isNaN(); + + /** + * Check whether any coordinate of this vector is infinite and none are {@code NaN}. + * + * @return {@code true} if any coordinate of this vector is infinite and + * none are {@code NaN}, {@code false} otherwise. + */ + public abstract boolean isInfinite(); + + /** + * Check if instance and specified vectors have the same dimension. + * + * @param v Vector to compare instance with. + * @throws DimensionMismatchException if the vectors do not + * have the same dimension. + */ + protected void checkVectorDimensions(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + } + + /** + * Check if instance dimension is equal to some expected value. + * + * @param n Expected dimension. + * @throws DimensionMismatchException if the dimension is + * inconsistent with the vector size. + */ + protected void checkVectorDimensions(int n) + throws DimensionMismatchException { + int d = getDimension(); + if (d != n) { + throw new DimensionMismatchException(d, n); + } + } + + /** + * Check if an index is valid. + * + * @param index Index to check. + * @exception OutOfRangeException if {@code index} is not valid. + */ + protected void checkIndex(final int index) throws OutOfRangeException { + if (index < 0 || + index >= getDimension()) { + throw new OutOfRangeException(LocalizedFormats.INDEX, + index, 0, getDimension() - 1); + } + } + + /** + * Checks that the indices of a subvector are valid. + * + * @param start the index of the first entry of the subvector + * @param end the index of the last entry of the subvector (inclusive) + * @throws OutOfRangeException if {@code start} of {@code end} are not valid + * @throws NumberIsTooSmallException if {@code end < start} + * @since 3.1 + */ + protected void checkIndices(final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + final int dim = getDimension(); + if ((start < 0) || (start >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, + dim - 1); + } + if ((end < 0) || (end >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, + dim - 1); + } + if (end < start) { + // TODO Use more specific error message + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, + end, start, false); + } + } + + /** + * Compute the sum of this vector and {@code v}. + * Returns a new vector. Does not change instance data. + * + * @param v Vector to be added. + * @return {@code this} + {@code v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public RealVector add(RealVector v) throws DimensionMismatchException { + checkVectorDimensions(v); + RealVector result = v.copy(); + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + final int index = e.getIndex(); + result.setEntry(index, e.getValue() + result.getEntry(index)); + } + return result; + } + + /** + * Subtract {@code v} from this vector. + * Returns a new vector. Does not change instance data. + * + * @param v Vector to be subtracted. + * @return {@code this} - {@code v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public RealVector subtract(RealVector v) throws DimensionMismatchException { + checkVectorDimensions(v); + RealVector result = v.mapMultiply(-1d); + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + final int index = e.getIndex(); + result.setEntry(index, e.getValue() + result.getEntry(index)); + } + return result; + } + + /** + * Add a value to each entry. + * Returns a new vector. Does not change instance data. + * + * @param d Value to be added to each entry. + * @return {@code this} + {@code d}. + */ + public RealVector mapAdd(double d) { + return copy().mapAddToSelf(d); + } + + /** + * Add a value to each entry. + * The instance is changed in-place. + * + * @param d Value to be added to each entry. + * @return {@code this}. + */ + public RealVector mapAddToSelf(double d) { + if (d != 0) { + return mapToSelf(FunctionUtils.fix2ndArgument(new Add(), d)); + } + return this; + } + + /** + * Returns a (deep) copy of this vector. + * + * @return a vector copy. + */ + public abstract RealVector copy(); + + /** + * Compute the dot product of this vector with {@code v}. + * + * @param v Vector with which dot product should be computed + * @return the scalar dot product between this instance and {@code v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public double dotProduct(RealVector v) throws DimensionMismatchException { + checkVectorDimensions(v); + double d = 0; + final int n = getDimension(); + for (int i = 0; i < n; i++) { + d += getEntry(i) * v.getEntry(i); + } + return d; + } + + /** + * Computes the cosine of the angle between this vector and the + * argument. + * + * @param v Vector. + * @return the cosine of the angle between this vector and {@code v}. + * @throws MathArithmeticException if {@code this} or {@code v} is the null + * vector + * @throws DimensionMismatchException if the dimensions of {@code this} and + * {@code v} do not match + */ + public double cosine(RealVector v) throws DimensionMismatchException, + MathArithmeticException { + final double norm = getNorm(); + final double vNorm = v.getNorm(); + + if (norm == 0 || + vNorm == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + return dotProduct(v) / (norm * vNorm); + } + + /** + * Element-by-element division. + * + * @param v Vector by which instance elements must be divided. + * @return a vector containing this[i] / v[i] for all i. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public abstract RealVector ebeDivide(RealVector v) + throws DimensionMismatchException; + + /** + * Element-by-element multiplication. + * + * @param v Vector by which instance elements must be multiplied + * @return a vector containing this[i] * v[i] for all i. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public abstract RealVector ebeMultiply(RealVector v) + throws DimensionMismatchException; + + /** + * Distance between two vectors. + *

    This method computes the distance consistent with the + * L2 norm, i.e. the square root of the sum of + * element differences, or Euclidean distance.

    + * + * @param v Vector to which distance is requested. + * @return the distance between two vectors. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + * @see #getL1Distance(RealVector) + * @see #getLInfDistance(RealVector) + * @see #getNorm() + */ + public double getDistance(RealVector v) throws DimensionMismatchException { + checkVectorDimensions(v); + double d = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + final double diff = e.getValue() - v.getEntry(e.getIndex()); + d += diff * diff; + } + return FastMath.sqrt(d); + } + + /** + * Returns the L2 norm of the vector. + *

    The L2 norm is the root of the sum of + * the squared elements.

    + * + * @return the norm. + * @see #getL1Norm() + * @see #getLInfNorm() + * @see #getDistance(RealVector) + */ + public double getNorm() { + double sum = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + final double value = e.getValue(); + sum += value * value; + } + return FastMath.sqrt(sum); + } + + /** + * Returns the L1 norm of the vector. + *

    The L1 norm is the sum of the absolute + * values of the elements.

    + * + * @return the norm. + * @see #getNorm() + * @see #getLInfNorm() + * @see #getL1Distance(RealVector) + */ + public double getL1Norm() { + double norm = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + norm += FastMath.abs(e.getValue()); + } + return norm; + } + + /** + * Returns the L norm of the vector. + *

    The L norm is the max of the absolute + * values of the elements.

    + * + * @return the norm. + * @see #getNorm() + * @see #getL1Norm() + * @see #getLInfDistance(RealVector) + */ + public double getLInfNorm() { + double norm = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + norm = FastMath.max(norm, FastMath.abs(e.getValue())); + } + return norm; + } + + /** + * Distance between two vectors. + *

    This method computes the distance consistent with + * L1 norm, i.e. the sum of the absolute values of + * the elements differences.

    + * + * @param v Vector to which distance is requested. + * @return the distance between two vectors. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + */ + public double getL1Distance(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v); + double d = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + d += FastMath.abs(e.getValue() - v.getEntry(e.getIndex())); + } + return d; + } + + /** + * Distance between two vectors. + *

    This method computes the distance consistent with + * L norm, i.e. the max of the absolute values of + * element differences.

    + * + * @param v Vector to which distance is requested. + * @return the distance between two vectors. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + * @see #getDistance(RealVector) + * @see #getL1Distance(RealVector) + * @see #getLInfNorm() + */ + public double getLInfDistance(RealVector v) + throws DimensionMismatchException { + checkVectorDimensions(v); + double d = 0; + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + d = FastMath.max(FastMath.abs(e.getValue() - v.getEntry(e.getIndex())), d); + } + return d; + } + + /** + * Get the index of the minimum entry. + * + * @return the index of the minimum entry or -1 if vector length is 0 + * or all entries are {@code NaN}. + */ + public int getMinIndex() { + int minIndex = -1; + double minValue = Double.POSITIVE_INFINITY; + Iterator iterator = iterator(); + while (iterator.hasNext()) { + final Entry entry = iterator.next(); + if (entry.getValue() <= minValue) { + minIndex = entry.getIndex(); + minValue = entry.getValue(); + } + } + return minIndex; + } + + /** + * Get the value of the minimum entry. + * + * @return the value of the minimum entry or {@code NaN} if all + * entries are {@code NaN}. + */ + public double getMinValue() { + final int minIndex = getMinIndex(); + return minIndex < 0 ? Double.NaN : getEntry(minIndex); + } + + /** + * Get the index of the maximum entry. + * + * @return the index of the maximum entry or -1 if vector length is 0 + * or all entries are {@code NaN} + */ + public int getMaxIndex() { + int maxIndex = -1; + double maxValue = Double.NEGATIVE_INFINITY; + Iterator iterator = iterator(); + while (iterator.hasNext()) { + final Entry entry = iterator.next(); + if (entry.getValue() >= maxValue) { + maxIndex = entry.getIndex(); + maxValue = entry.getValue(); + } + } + return maxIndex; + } + + /** + * Get the value of the maximum entry. + * + * @return the value of the maximum entry or {@code NaN} if all + * entries are {@code NaN}. + */ + public double getMaxValue() { + final int maxIndex = getMaxIndex(); + return maxIndex < 0 ? Double.NaN : getEntry(maxIndex); + } + + + /** + * Multiply each entry by the argument. Returns a new vector. + * Does not change instance data. + * + * @param d Multiplication factor. + * @return {@code this} * {@code d}. + */ + public RealVector mapMultiply(double d) { + return copy().mapMultiplyToSelf(d); + } + + /** + * Multiply each entry. + * The instance is changed in-place. + * + * @param d Multiplication factor. + * @return {@code this}. + */ + public RealVector mapMultiplyToSelf(double d){ + return mapToSelf(FunctionUtils.fix2ndArgument(new Multiply(), d)); + } + + /** + * Subtract a value from each entry. Returns a new vector. + * Does not change instance data. + * + * @param d Value to be subtracted. + * @return {@code this} - {@code d}. + */ + public RealVector mapSubtract(double d) { + return copy().mapSubtractToSelf(d); + } + + /** + * Subtract a value from each entry. + * The instance is changed in-place. + * + * @param d Value to be subtracted. + * @return {@code this}. + */ + public RealVector mapSubtractToSelf(double d){ + return mapAddToSelf(-d); + } + + /** + * Divide each entry by the argument. Returns a new vector. + * Does not change instance data. + * + * @param d Value to divide by. + * @return {@code this} / {@code d}. + */ + public RealVector mapDivide(double d) { + return copy().mapDivideToSelf(d); + } + + /** + * Divide each entry by the argument. + * The instance is changed in-place. + * + * @param d Value to divide by. + * @return {@code this}. + */ + public RealVector mapDivideToSelf(double d){ + return mapToSelf(FunctionUtils.fix2ndArgument(new Divide(), d)); + } + + /** + * Compute the outer product. + * + * @param v Vector with which outer product should be computed. + * @return the matrix outer product between this instance and {@code v}. + */ + public RealMatrix outerProduct(RealVector v) { + final int m = this.getDimension(); + final int n = v.getDimension(); + final RealMatrix product; + if (v instanceof SparseRealVector || this instanceof SparseRealVector) { + product = new OpenMapRealMatrix(m, n); + } else { + product = new Array2DRowRealMatrix(m, n); + } + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + product.setEntry(i, j, this.getEntry(i) * v.getEntry(j)); + } + } + return product; + } + + /** + * Find the orthogonal projection of this vector onto another vector. + * + * @param v vector onto which instance must be projected. + * @return projection of the instance onto {@code v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this} vector. + * @throws MathArithmeticException if {@code this} or {@code v} is the null + * vector + */ + public RealVector projection(final RealVector v) + throws DimensionMismatchException, MathArithmeticException { + final double norm2 = v.dotProduct(v); + if (norm2 == 0.0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + return v.mapMultiply(dotProduct(v) / v.dotProduct(v)); + } + + /** + * Set all elements to a single value. + * + * @param value Single value to set for all elements. + */ + public void set(double value) { + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + e.setValue(value); + } + } + + /** + * Convert the vector to an array of {@code double}s. + * The array is independent from this vector data: the elements + * are copied. + * + * @return an array containing a copy of the vector elements. + */ + public double[] toArray() { + int dim = getDimension(); + double[] values = new double[dim]; + for (int i = 0; i < dim; i++) { + values[i] = getEntry(i); + } + return values; + } + + /** + * Creates a unit vector pointing in the direction of this vector. + * The instance is not changed by this method. + * + * @return a unit vector pointing in direction of this vector. + * @throws MathArithmeticException if the norm is zero. + */ + public RealVector unitVector() throws MathArithmeticException { + final double norm = getNorm(); + if (norm == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + return mapDivide(norm); + } + + /** + * Converts this vector into a unit vector. + * The instance itself is changed by this method. + * + * @throws MathArithmeticException if the norm is zero. + */ + public void unitize() throws MathArithmeticException { + final double norm = getNorm(); + if (norm == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + mapDivideToSelf(getNorm()); + } + + /** + * Create a sparse iterator over the vector, which may omit some entries. + * The ommitted entries are either exact zeroes (for dense implementations) + * or are the entries which are not stored (for real sparse vectors). + * No guarantees are made about order of iteration. + * + *

    Note: derived classes are required to return an {@link Iterator} that + * returns non-null {@link Entry} objects as long as {@link Iterator#hasNext()} + * returns {@code true}.

    + * + * @return a sparse iterator. + */ + public Iterator sparseIterator() { + return new SparseEntryIterator(); + } + + /** + * Generic dense iterator. Iteration is in increasing order + * of the vector index. + * + *

    Note: derived classes are required to return an {@link Iterator} that + * returns non-null {@link Entry} objects as long as {@link Iterator#hasNext()} + * returns {@code true}.

    + * + * @return a dense iterator. + */ + public Iterator iterator() { + final int dim = getDimension(); + return new Iterator() { + + /** Current index. */ + private int i = 0; + + /** Current entry. */ + private Entry e = new Entry(); + + /** {@inheritDoc} */ + public boolean hasNext() { + return i < dim; + } + + /** {@inheritDoc} */ + public Entry next() { + if (i < dim) { + e.setIndex(i++); + return e; + } else { + throw new NoSuchElementException(); + } + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all circumstances. + */ + public void remove() throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + }; + } + + /** + * Acts as if implemented as: + *
    +     *  return copy().mapToSelf(function);
    +     * 
    + * Returns a new vector. Does not change instance data. + * + * @param function Function to apply to each entry. + * @return a new vector. + */ + public RealVector map(UnivariateFunction function) { + return copy().mapToSelf(function); + } + + /** + * Acts as if it is implemented as: + *
    +     *  Entry e = null;
    +     *  for(Iterator it = iterator(); it.hasNext(); e = it.next()) {
    +     *      e.setValue(function.value(e.getValue()));
    +     *  }
    +     * 
    + * Entries of this vector are modified in-place by this method. + * + * @param function Function to apply to each entry. + * @return a reference to this vector. + */ + public RealVector mapToSelf(UnivariateFunction function) { + Iterator it = iterator(); + while (it.hasNext()) { + final Entry e = it.next(); + e.setValue(function.value(e.getValue())); + } + return this; + } + + /** + * Returns a new vector representing {@code a * this + b * y}, the linear + * combination of {@code this} and {@code y}. + * Returns a new vector. Does not change instance data. + * + * @param a Coefficient of {@code this}. + * @param b Coefficient of {@code y}. + * @param y Vector with which {@code this} is linearly combined. + * @return a vector containing {@code a * this[i] + b * y[i]} for all + * {@code i}. + * @throws DimensionMismatchException if {@code y} is not the same size as + * {@code this} vector. + */ + public RealVector combine(double a, double b, RealVector y) + throws DimensionMismatchException { + return copy().combineToSelf(a, b, y); + } + + /** + * Updates {@code this} with the linear combination of {@code this} and + * {@code y}. + * + * @param a Weight of {@code this}. + * @param b Weight of {@code y}. + * @param y Vector with which {@code this} is linearly combined. + * @return {@code this}, with components equal to + * {@code a * this[i] + b * y[i]} for all {@code i}. + * @throws DimensionMismatchException if {@code y} is not the same size as + * {@code this} vector. + */ + public RealVector combineToSelf(double a, double b, RealVector y) + throws DimensionMismatchException { + checkVectorDimensions(y); + for (int i = 0; i < getDimension(); i++) { + final double xi = getEntry(i); + final double yi = y.getEntry(i); + setEntry(i, a * xi + b * yi); + } + return this; + } + + /** + * Visits (but does not alter) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link RealVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.1 + */ + public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link RealVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.1 + */ + public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link RealVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.1 + */ + public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (but does not alter) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link RealVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.1 + */ + public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** + * Visits (and possibly alters) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process and modify the entries + * of this vector + * @return the value returned by {@link RealVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.1 + */ + public double walkInDefaultOrder(final RealVectorChangingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link RealVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.1 + */ + public double walkInDefaultOrder(final RealVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link RealVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.1 + */ + public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (and possibly change) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link RealVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.1 + */ + public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** An entry in the vector. */ + protected class Entry { + /** Index of this entry. */ + private int index; + + /** Simple constructor. */ + public Entry() { + setIndex(0); + } + + /** + * Get the value of the entry. + * + * @return the value of the entry. + */ + public double getValue() { + return getEntry(getIndex()); + } + + /** + * Set the value of the entry. + * + * @param value New value for the entry. + */ + public void setValue(double value) { + setEntry(getIndex(), value); + } + + /** + * Get the index of the entry. + * + * @return the index of the entry. + */ + public int getIndex() { + return index; + } + + /** + * Set the index of the entry. + * + * @param index New index for the entry. + */ + public void setIndex(int index) { + this.index = index; + } + } + + /** + *

    + * Test for the equality of two real vectors. If all coordinates of two real + * vectors are exactly the same, and none are {@code NaN}, the two real + * vectors are considered to be equal. {@code NaN} coordinates are + * considered to affect globally the vector and be equals to each other - + * i.e, if either (or all) coordinates of the real vector are equal to + * {@code NaN}, the real vector is equal to a vector with all {@code NaN} + * coordinates. + *

    + *

    + * This method must be overriden by concrete subclasses of + * {@link RealVector} (the current implementation throws an exception). + *

    + * + * @param other Object to test for equality. + * @return {@code true} if two vector objects are equal, {@code false} if + * {@code other} is null, not an instance of {@code RealVector}, or + * not equal to this {@code RealVector} instance. + * @throws MathUnsupportedOperationException if this method is not + * overridden. + */ + @Override + public boolean equals(Object other) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** + * {@inheritDoc}. This method must be overriden by concrete + * subclasses of {@link RealVector} (current implementation throws an + * exception). + * + * @throws MathUnsupportedOperationException if this method is not + * overridden. + */ + @Override + public int hashCode() throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** + * This class should rarely be used, but is here to provide + * a default implementation of sparseIterator(), which is implemented + * by walking over the entries, skipping those that are zero. + * + * Concrete subclasses which are SparseVector implementations should + * make their own sparse iterator, rather than using 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 { + /** Dimension of the vector. */ + private final int dim; + /** Last entry returned by {@link #next()}. */ + private Entry current; + /** Next entry for {@link #next()} to return. */ + private Entry next; + + /** Simple constructor. */ + protected SparseEntryIterator() { + dim = getDimension(); + current = new Entry(); + next = new Entry(); + if (next.getValue() == 0) { + advance(next); + } + } + + /** + * Advance an entry up to the next nonzero one. + * + * @param e entry to advance. + */ + protected void advance(Entry e) { + if (e == null) { + return; + } + do { + e.setIndex(e.getIndex() + 1); + } while (e.getIndex() < dim && e.getValue() == 0); + if (e.getIndex() >= dim) { + e.setIndex(-1); + } + } + + /** {@inheritDoc} */ + public boolean hasNext() { + return next.getIndex() >= 0; + } + + /** {@inheritDoc} */ + public Entry next() { + int index = next.getIndex(); + if (index < 0) { + throw new NoSuchElementException(); + } + current.setIndex(index); + advance(next); + return current; + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all circumstances. + */ + public void remove() throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + } + + /** + * Returns an unmodifiable view of the specified vector. + * The returned vector has read-only access. An attempt to modify it will + * result in a {@link MathUnsupportedOperationException}. However, the + * returned vector is not immutable, since any modification of + * {@code v} will also change the returned view. + * For example, in the following piece of code + *
    +     *     RealVector v = new ArrayRealVector(2);
    +     *     RealVector w = RealVector.unmodifiableRealVector(v);
    +     *     v.setEntry(0, 1.2);
    +     *     v.setEntry(1, -3.4);
    +     * 
    + * the changes will be seen in the {@code w} view of {@code v}. + * + * @param v Vector for which an unmodifiable view is to be returned. + * @return an unmodifiable view of {@code v}. + */ + public static RealVector unmodifiableRealVector(final RealVector v) { + /** + * This anonymous class is an implementation of {@link RealVector} + * with read-only access. + * It wraps any {@link RealVector}, and exposes all methods which + * do not modify it. Invoking methods which should normally result + * in the modification of the calling {@link RealVector} results in + * a {@link MathUnsupportedOperationException}. It should be noted + * that {@link UnmodifiableVector} is not immutable. + */ + return new RealVector() { + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all circumstances. + */ + @Override + public RealVector mapToSelf(UnivariateFunction function) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector map(UnivariateFunction function) { + return v.map(function); + } + + /** {@inheritDoc} */ + @Override + public Iterator iterator() { + final Iterator i = v.iterator(); + return new Iterator() { + /** The current entry. */ + private final UnmodifiableEntry e = new UnmodifiableEntry(); + + /** {@inheritDoc} */ + public boolean hasNext() { + return i.hasNext(); + } + + /** {@inheritDoc} */ + public Entry next() { + e.setIndex(i.next().getIndex()); + return e; + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + public void remove() throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + }; + } + + /** {@inheritDoc} */ + @Override + public Iterator sparseIterator() { + final Iterator i = v.sparseIterator(); + + return new Iterator() { + /** The current entry. */ + private final UnmodifiableEntry e = new UnmodifiableEntry(); + + /** {@inheritDoc} */ + public boolean hasNext() { + return i.hasNext(); + } + + /** {@inheritDoc} */ + public Entry next() { + e.setIndex(i.next().getIndex()); + return e; + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + public void remove() + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + }; + } + + /** {@inheritDoc} */ + @Override + public RealVector copy() { + return v.copy(); + } + + /** {@inheritDoc} */ + @Override + public RealVector add(RealVector w) + throws DimensionMismatchException { + return v.add(w); + } + + /** {@inheritDoc} */ + @Override + public RealVector subtract(RealVector w) + throws DimensionMismatchException { + return v.subtract(w); + } + + /** {@inheritDoc} */ + @Override + public RealVector mapAdd(double d) { + return v.mapAdd(d); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public RealVector mapAddToSelf(double d) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector mapSubtract(double d) { + return v.mapSubtract(d); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public RealVector mapSubtractToSelf(double d) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector mapMultiply(double d) { + return v.mapMultiply(d); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public RealVector mapMultiplyToSelf(double d) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector mapDivide(double d) { + return v.mapDivide(d); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public RealVector mapDivideToSelf(double d) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealVector ebeMultiply(RealVector w) + throws DimensionMismatchException { + return v.ebeMultiply(w); + } + + /** {@inheritDoc} */ + @Override + public RealVector ebeDivide(RealVector w) + throws DimensionMismatchException { + return v.ebeDivide(w); + } + + /** {@inheritDoc} */ + @Override + public double dotProduct(RealVector w) + throws DimensionMismatchException { + return v.dotProduct(w); + } + + /** {@inheritDoc} */ + @Override + public double cosine(RealVector w) + throws DimensionMismatchException, MathArithmeticException { + return v.cosine(w); + } + + /** {@inheritDoc} */ + @Override + public double getNorm() { + return v.getNorm(); + } + + /** {@inheritDoc} */ + @Override + public double getL1Norm() { + return v.getL1Norm(); + } + + /** {@inheritDoc} */ + @Override + public double getLInfNorm() { + return v.getLInfNorm(); + } + + /** {@inheritDoc} */ + @Override + public double getDistance(RealVector w) + throws DimensionMismatchException { + return v.getDistance(w); + } + + /** {@inheritDoc} */ + @Override + public double getL1Distance(RealVector w) + throws DimensionMismatchException { + return v.getL1Distance(w); + } + + /** {@inheritDoc} */ + @Override + public double getLInfDistance(RealVector w) + throws DimensionMismatchException { + return v.getLInfDistance(w); + } + + /** {@inheritDoc} */ + @Override + public RealVector unitVector() throws MathArithmeticException { + return v.unitVector(); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void unitize() throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public RealMatrix outerProduct(RealVector w) { + return v.outerProduct(w); + } + + /** {@inheritDoc} */ + @Override + public double getEntry(int index) throws OutOfRangeException { + return v.getEntry(index); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void setEntry(int index, double value) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void addToEntry(int index, double value) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public int getDimension() { + return v.getDimension(); + } + + /** {@inheritDoc} */ + @Override + public RealVector append(RealVector w) { + return v.append(w); + } + + /** {@inheritDoc} */ + @Override + public RealVector append(double d) { + return v.append(d); + } + + /** {@inheritDoc} */ + @Override + public RealVector getSubVector(int index, int n) + throws OutOfRangeException, NotPositiveException { + return v.getSubVector(index, n); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void setSubVector(int index, RealVector w) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void set(double value) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public double[] toArray() { + return v.toArray(); + } + + /** {@inheritDoc} */ + @Override + public boolean isNaN() { + return v.isNaN(); + } + + /** {@inheritDoc} */ + @Override + public boolean isInfinite() { + return v.isInfinite(); + } + + /** {@inheritDoc} */ + @Override + public RealVector combine(double a, double b, RealVector y) + throws DimensionMismatchException { + return v.combine(a, b, y); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public RealVector combineToSelf(double a, double b, RealVector y) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** An entry in the vector. */ + class UnmodifiableEntry extends Entry { + /** {@inheritDoc} */ + @Override + public double getValue() { + return v.getEntry(getIndex()); + } + + /** + * {@inheritDoc} + * + * @throws MathUnsupportedOperationException in all + * circumstances. + */ + @Override + public void setValue(double value) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorChangingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorChangingVisitor.java new file mode 100644 index 000000000..ef583b8b1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorChangingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * This interface defines a visitor for the entries of a vector. Visitors + * implementing this interface may alter the entries of the vector being + * visited. + * + * @since 3.1 + */ +public interface RealVectorChangingVisitor { + /** + * Start visiting a vector. This method is called once, before any entry + * of the vector is visited. + * + * @param dimension the size of the vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + */ + void start(int dimension, int start, int end); + + /** + * Visit one entry of the vector. + * + * @param index the index of the entry being visited + * @param value the value of the entry being visited + * @return the new value of the entry being visited + */ + double visit(int index, double value); + + /** + * End visiting a vector. This method is called once, after all entries of + * the vector have been visited. + * + * @return the value returned by + * {@link RealVector#walkInDefaultOrder(RealVectorChangingVisitor)}, + * {@link RealVector#walkInDefaultOrder(RealVectorChangingVisitor, int, int)}, + * {@link RealVector#walkInOptimizedOrder(RealVectorChangingVisitor)} + * or + * {@link RealVector#walkInOptimizedOrder(RealVectorChangingVisitor, int, int)} + */ + double end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorFormat.java new file mode 100644 index 000000000..a051e709d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorFormat.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 com.fr.third.org.apache.commons.math3.linear; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.util.CompositeFormat; + +/** + * Formats a vector in components list format "{v0; v1; ...; vk-1}". + *

    The prefix and suffix "{" and "}" and the separator "; " can be replaced by + * any user-defined strings. The number format for components can be configured.

    + *

    White space is ignored at parse time, even if it is in the prefix, suffix + * or separator specifications. So even if the default separator does include a space + * character that is used at format time, both input string "{1;1;1}" and + * " { 1 ; 1 ; 1 } " will be parsed without error and the same vector will be + * returned. In the second case, however, the parse position after parsing will be + * just after the closing curly brace, i.e. just before the trailing space.

    + * + * @since 2.0 + */ +public class RealVectorFormat { + + /** The default prefix: "{". */ + private static final String DEFAULT_PREFIX = "{"; + /** The default suffix: "}". */ + private static final String DEFAULT_SUFFIX = "}"; + /** The default separator: ", ". */ + private static final String DEFAULT_SEPARATOR = "; "; + /** Prefix. */ + private final String prefix; + /** Suffix. */ + private final String suffix; + /** Separator. */ + private final String separator; + /** Trimmed prefix. */ + private final String trimmedPrefix; + /** Trimmed suffix. */ + private final String trimmedSuffix; + /** Trimmed separator. */ + private final String trimmedSeparator; + /** The format used for components. */ + private final NumberFormat format; + + /** + * Create an instance with default settings. + *

    The instance uses the default prefix, suffix and separator: + * "{", "}", and "; " and the default number format for components.

    + */ + public RealVectorFormat() { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with a custom number format for components. + * @param format the custom format for components. + */ + public RealVectorFormat(final NumberFormat format) { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format); + } + + /** + * Create an instance with custom prefix, suffix and separator. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + */ + public RealVectorFormat(final String prefix, final String suffix, + final String separator) { + this(prefix, suffix, separator, + CompositeFormat.getDefaultNumberFormat()); + } + + /** + * Create an instance with custom prefix, suffix, separator and format + * for components. + * @param prefix prefix to use instead of the default "{" + * @param suffix suffix to use instead of the default "}" + * @param separator separator to use instead of the default "; " + * @param format the custom format for components. + */ + public RealVectorFormat(final String prefix, final String suffix, + final String separator, final NumberFormat format) { + this.prefix = prefix; + this.suffix = suffix; + this.separator = separator; + trimmedPrefix = prefix.trim(); + trimmedSuffix = suffix.trim(); + trimmedSeparator = separator.trim(); + this.format = format; + } + + /** + * Get the set of locales for which real vectors formats are available. + *

    This is the same set as the {@link NumberFormat} set.

    + * @return available real vector format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * Get the format prefix. + * @return format prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Get the format suffix. + * @return format suffix. + */ + public String getSuffix() { + return suffix; + } + + /** + * Get the format separator between components. + * @return format separator. + */ + public String getSeparator() { + return separator; + } + + /** + * Get the components format. + * @return components format. + */ + public NumberFormat getFormat() { + return format; + } + + /** + * Returns the default real vector format for the current locale. + * @return the default real vector format. + */ + public static RealVectorFormat getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns the default real vector format for the given locale. + * @param locale the specific locale used by the format. + * @return the real vector format specific to the given locale. + */ + public static RealVectorFormat getInstance(final Locale locale) { + return new RealVectorFormat(CompositeFormat.getDefaultNumberFormat(locale)); + } + + /** + * This method calls {@link #format(RealVector,StringBuffer,FieldPosition)}. + * + * @param v RealVector object to format. + * @return a formatted vector. + */ + public String format(RealVector v) { + return format(v, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats a {@link RealVector} object to produce a string. + * @param vector the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(RealVector vector, StringBuffer toAppendTo, + FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + // format prefix + toAppendTo.append(prefix); + + // format components + for (int i = 0; i < vector.getDimension(); ++i) { + if (i > 0) { + toAppendTo.append(separator); + } + CompositeFormat.formatDouble(vector.getEntry(i), format, toAppendTo, pos); + } + + // format suffix + toAppendTo.append(suffix); + + return toAppendTo; + } + + /** + * Parse a string to produce a {@link RealVector} object. + * + * @param source String to parse. + * @return the parsed {@link RealVector} object. + * @throws MathParseException if the beginning of the specified string + * cannot be parsed. + */ + public ArrayRealVector parse(String source) { + final ParsePosition parsePosition = new ParsePosition(0); + final ArrayRealVector result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, + parsePosition.getErrorIndex(), + ArrayRealVector.class); + } + return result; + } + + /** + * Parse a string to produce a {@link RealVector} object. + * + * @param source String to parse. + * @param pos input/ouput parsing parameter. + * @return the parsed {@link RealVector} object. + */ + public ArrayRealVector parse(String source, ParsePosition pos) { + int initialIndex = pos.getIndex(); + + // parse prefix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) { + return null; + } + + // parse components + List components = new ArrayList(); + for (boolean loop = true; loop;){ + + if (!components.isEmpty()) { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedSeparator, pos)) { + loop = false; + } + } + + if (loop) { + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + Number component = CompositeFormat.parseNumber(source, format, pos); + if (component != null) { + components.add(component); + } else { + // invalid component + // set index back to initial, error index should already be set + pos.setIndex(initialIndex); + return null; + } + } + + } + + // parse suffix + CompositeFormat.parseAndIgnoreWhitespace(source, pos); + if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) { + return null; + } + + // build vector + double[] data = new double[components.size()]; + for (int i = 0; i < data.length; ++i) { + data[i] = components.get(i).doubleValue(); + } + return new ArrayRealVector(data, false); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorPreservingVisitor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorPreservingVisitor.java new file mode 100644 index 000000000..a4fe606dc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RealVectorPreservingVisitor.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * This interface defines a visitor for the entries of a vector. Visitors + * implementing this interface do not alter the entries of the vector being + * visited. + * + * @since 3.1 + */ +public interface RealVectorPreservingVisitor { + /** + * Start visiting a vector. This method is called once, before any entry + * of the vector is visited. + * + * @param dimension the size of the vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + */ + void start(int dimension, int start, int end); + + /** + * Visit one entry of the vector. + * + * @param index the index of the entry being visited + * @param value the value of the entry being visited + */ + void visit(int index, double value); + + /** + * End visiting a vector. This method is called once, after all entries of + * the vector have been visited. + * + * @return the value returned by + * {@link RealVector#walkInDefaultOrder(RealVectorPreservingVisitor)}, + * {@link RealVector#walkInDefaultOrder(RealVectorPreservingVisitor, int, int)}, + * {@link RealVector#walkInOptimizedOrder(RealVectorPreservingVisitor)} + * or + * {@link RealVector#walkInOptimizedOrder(RealVectorPreservingVisitor, int, int)} + */ + double end(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java new file mode 100644 index 000000000..fa81ba7ca --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.random.CorrelatedRandomVectorGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Calculates the rectangular Cholesky decomposition of a matrix. + *

    The rectangular Cholesky decomposition of a real symmetric positive + * semidefinite matrix A consists of a rectangular matrix B with the same + * number of rows such that: A is almost equal to BBT, depending + * on a user-defined tolerance. In a sense, this is the square root of A.

    + *

    The difference with respect to the regular {@link CholeskyDecomposition} + * is that rows/columns may be permuted (hence the rectangular shape instead + * of the traditional triangular shape) and there is a threshold to ignore + * small diagonal elements. This is used for example to generate {@link + * CorrelatedRandomVectorGenerator correlated + * random n-dimensions vectors} in a p-dimension subspace (p < n). + * In other words, it allows generating random vectors from a covariance + * matrix that is only positive semidefinite, and not positive definite.

    + *

    Rectangular Cholesky decomposition is not suited for solving + * linear systems, so it does not provide any {@link DecompositionSolver + * decomposition solver}.

    + * + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class RectangularCholeskyDecomposition { + + /** Permutated Cholesky root of the symmetric positive semidefinite matrix. */ + private final RealMatrix root; + + /** Rank of the symmetric positive semidefinite matrix. */ + private int rank; + + /** + * Decompose a symmetric positive semidefinite matrix. + *

    + * Note: this constructor follows the linpack method to detect dependent + * columns by proceeding with the Cholesky algorithm until a nonpositive diagonal + * element is encountered. + * + * @see + * Analysis of the Cholesky Decomposition of a Semi-definite Matrix + * + * @param matrix Symmetric positive semidefinite matrix. + * @exception NonPositiveDefiniteMatrixException if the matrix is not + * positive semidefinite. + * @since 3.1 + */ + public RectangularCholeskyDecomposition(RealMatrix matrix) + throws NonPositiveDefiniteMatrixException { + this(matrix, 0); + } + + /** + * Decompose a symmetric positive semidefinite matrix. + * + * @param matrix Symmetric positive semidefinite matrix. + * @param small Diagonal elements threshold under which columns are + * considered to be dependent on previous ones and are discarded. + * @exception NonPositiveDefiniteMatrixException if the matrix is not + * positive semidefinite. + */ + public RectangularCholeskyDecomposition(RealMatrix matrix, double small) + throws NonPositiveDefiniteMatrixException { + + final int order = matrix.getRowDimension(); + final double[][] c = matrix.getData(); + final double[][] b = new double[order][order]; + + int[] index = new int[order]; + for (int i = 0; i < order; ++i) { + index[i] = i; + } + + int r = 0; + for (boolean loop = true; loop;) { + + // find maximal diagonal element + int swapR = r; + for (int i = r + 1; i < order; ++i) { + int ii = index[i]; + int isr = index[swapR]; + if (c[ii][ii] > c[isr][isr]) { + swapR = i; + } + } + + + // swap elements + if (swapR != r) { + final int tmpIndex = index[r]; + index[r] = index[swapR]; + index[swapR] = tmpIndex; + final double[] tmpRow = b[r]; + b[r] = b[swapR]; + b[swapR] = tmpRow; + } + + // check diagonal element + int ir = index[r]; + if (c[ir][ir] <= small) { + + if (r == 0) { + throw new NonPositiveDefiniteMatrixException(c[ir][ir], ir, small); + } + + // check remaining diagonal elements + for (int i = r; i < order; ++i) { + if (c[index[i]][index[i]] < -small) { + // there is at least one sufficiently negative diagonal element, + // the symmetric positive semidefinite matrix is wrong + throw new NonPositiveDefiniteMatrixException(c[index[i]][index[i]], i, small); + } + } + + // all remaining diagonal elements are close to zero, we consider we have + // found the rank of the symmetric positive semidefinite matrix + loop = false; + + } else { + + // transform the matrix + final double sqrt = FastMath.sqrt(c[ir][ir]); + b[r][r] = sqrt; + final double inverse = 1 / sqrt; + final double inverse2 = 1 / c[ir][ir]; + for (int i = r + 1; i < order; ++i) { + final int ii = index[i]; + final double e = inverse * c[ii][ir]; + b[i][r] = e; + c[ii][ii] -= c[ii][ir] * c[ii][ir] * inverse2; + for (int j = r + 1; j < i; ++j) { + final int ij = index[j]; + final double f = c[ii][ij] - e * b[j][r]; + c[ii][ij] = f; + c[ij][ii] = f; + } + } + + // prepare next iteration + loop = ++r < order; + } + } + + // build the root matrix + rank = r; + root = MatrixUtils.createRealMatrix(order, r); + for (int i = 0; i < order; ++i) { + for (int j = 0; j < r; ++j) { + root.setEntry(index[i], j, b[i][j]); + } + } + + } + + /** Get the root of the covariance matrix. + * The root is the rectangular matrix B such that + * the covariance matrix is equal to B.BT + * @return root of the square matrix + * @see #getRank() + */ + public RealMatrix getRootMatrix() { + return root; + } + + /** Get the rank of the symmetric positive semidefinite matrix. + * The r is the number of independent rows in the symmetric positive semidefinite + * matrix, it is also the number of columns of the rectangular + * matrix of the decomposition. + * @return r of the square matrix. + * @see #getRootMatrix() + */ + public int getRank() { + return rank; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SchurTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SchurTransformer.java new file mode 100644 index 000000000..2434f2ead --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SchurTransformer.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Class transforming a general real matrix to Schur form. + *

    A m × m matrix A can be written as the product of three matrices: A = P + * × T × PT with P an orthogonal matrix and T an quasi-triangular + * matrix. Both P and T are m × m matrices.

    + *

    Transformation to Schur form is often not a goal by itself, but it is an + * intermediate step in more general decomposition algorithms like + * {@link EigenDecomposition eigen decomposition}. This class is therefore + * intended for internal use by the library and is not public. As a consequence + * of this explicitly limited scope, many methods directly returns references to + * internal arrays, not copies.

    + *

    This class is based on the method hqr2 in class EigenvalueDecomposition + * from the JAMA library.

    + * + * @see Schur Decomposition - MathWorld + * @see Schur Decomposition - Wikipedia + * @see Householder Transformations + * @since 3.1 + */ +class SchurTransformer { + /** Maximum allowed iterations for convergence of the transformation. */ + private static final int MAX_ITERATIONS = 100; + + /** P matrix. */ + private final double matrixP[][]; + /** T matrix. */ + private final double matrixT[][]; + /** Cached value of P. */ + private RealMatrix cachedP; + /** Cached value of T. */ + private RealMatrix cachedT; + /** Cached value of PT. */ + private RealMatrix cachedPt; + + /** Epsilon criteria taken from JAMA code (originally was 2^-52). */ + private final double epsilon = Precision.EPSILON; + + /** + * Build the transformation to Schur form of a general real matrix. + * + * @param matrix matrix to transform + * @throws NonSquareMatrixException if the matrix is not square + */ + SchurTransformer(final RealMatrix matrix) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + HessenbergTransformer transformer = new HessenbergTransformer(matrix); + matrixT = transformer.getH().getData(); + matrixP = transformer.getP().getData(); + cachedT = null; + cachedP = null; + cachedPt = null; + + // transform matrix + transform(); + } + + /** + * Returns the matrix P of the transform. + *

    P is an orthogonal matrix, i.e. its inverse is also its transpose.

    + * + * @return the P matrix + */ + public RealMatrix getP() { + if (cachedP == null) { + cachedP = MatrixUtils.createRealMatrix(matrixP); + } + return cachedP; + } + + /** + * Returns the transpose of the matrix P of the transform. + *

    P is an orthogonal matrix, i.e. its inverse is also its transpose.

    + * + * @return the transpose of the P matrix + */ + public RealMatrix getPT() { + if (cachedPt == null) { + cachedPt = getP().transpose(); + } + + // return the cached matrix + return cachedPt; + } + + /** + * Returns the quasi-triangular Schur matrix T of the transform. + * + * @return the T matrix + */ + public RealMatrix getT() { + if (cachedT == null) { + cachedT = MatrixUtils.createRealMatrix(matrixT); + } + + // return the cached matrix + return cachedT; + } + + /** + * Transform original matrix to Schur form. + * @throws MaxCountExceededException if the transformation does not converge + */ + private void transform() { + final int n = matrixT.length; + + // compute matrix norm + final double norm = getNorm(); + + // shift information + final ShiftInfo shift = new ShiftInfo(); + + // Outer loop over eigenvalue index + int iteration = 0; + int iu = n - 1; + while (iu >= 0) { + + // Look for single small sub-diagonal element + final int il = findSmallSubDiagonalElement(iu, norm); + + // Check for convergence + if (il == iu) { + // One root found + matrixT[iu][iu] += shift.exShift; + iu--; + iteration = 0; + } else if (il == iu - 1) { + // Two roots found + double p = (matrixT[iu - 1][iu - 1] - matrixT[iu][iu]) / 2.0; + double q = p * p + matrixT[iu][iu - 1] * matrixT[iu - 1][iu]; + matrixT[iu][iu] += shift.exShift; + matrixT[iu - 1][iu - 1] += shift.exShift; + + if (q >= 0) { + double z = FastMath.sqrt(FastMath.abs(q)); + if (p >= 0) { + z = p + z; + } else { + z = p - z; + } + final double x = matrixT[iu][iu - 1]; + final double s = FastMath.abs(x) + FastMath.abs(z); + p = x / s; + q = z / s; + final double r = FastMath.sqrt(p * p + q * q); + p /= r; + q /= r; + + // Row modification + for (int j = iu - 1; j < n; j++) { + z = matrixT[iu - 1][j]; + matrixT[iu - 1][j] = q * z + p * matrixT[iu][j]; + matrixT[iu][j] = q * matrixT[iu][j] - p * z; + } + + // Column modification + for (int i = 0; i <= iu; i++) { + z = matrixT[i][iu - 1]; + matrixT[i][iu - 1] = q * z + p * matrixT[i][iu]; + matrixT[i][iu] = q * matrixT[i][iu] - p * z; + } + + // Accumulate transformations + for (int i = 0; i <= n - 1; i++) { + z = matrixP[i][iu - 1]; + matrixP[i][iu - 1] = q * z + p * matrixP[i][iu]; + matrixP[i][iu] = q * matrixP[i][iu] - p * z; + } + } + iu -= 2; + iteration = 0; + } else { + // No convergence yet + computeShift(il, iu, iteration, shift); + + // stop transformation after too many iterations + if (++iteration > MAX_ITERATIONS) { + throw new MaxCountExceededException(LocalizedFormats.CONVERGENCE_FAILED, + MAX_ITERATIONS); + } + + // the initial houseHolder vector for the QR step + final double[] hVec = new double[3]; + + final int im = initQRStep(il, iu, shift, hVec); + performDoubleQRStep(il, im, iu, shift, hVec); + } + } + } + + /** + * Computes the L1 norm of the (quasi-)triangular matrix T. + * + * @return the L1 norm of matrix T + */ + private double getNorm() { + double norm = 0.0; + for (int i = 0; i < matrixT.length; i++) { + // as matrix T is (quasi-)triangular, also take the sub-diagonal element into account + for (int j = FastMath.max(i - 1, 0); j < matrixT.length; j++) { + norm += FastMath.abs(matrixT[i][j]); + } + } + return norm; + } + + /** + * Find the first small sub-diagonal element and returns its index. + * + * @param startIdx the starting index for the search + * @param norm the L1 norm of the matrix + * @return the index of the first small sub-diagonal element + */ + private int findSmallSubDiagonalElement(final int startIdx, final double norm) { + int l = startIdx; + while (l > 0) { + double s = FastMath.abs(matrixT[l - 1][l - 1]) + FastMath.abs(matrixT[l][l]); + if (s == 0.0) { + s = norm; + } + if (FastMath.abs(matrixT[l][l - 1]) < epsilon * s) { + break; + } + l--; + } + return l; + } + + /** + * Compute the shift for the current iteration. + * + * @param l the index of the small sub-diagonal element + * @param idx the current eigenvalue index + * @param iteration the current iteration + * @param shift holder for shift information + */ + private void computeShift(final int l, final int idx, final int iteration, final ShiftInfo shift) { + // Form shift + shift.x = matrixT[idx][idx]; + shift.y = shift.w = 0.0; + if (l < idx) { + shift.y = matrixT[idx - 1][idx - 1]; + shift.w = matrixT[idx][idx - 1] * matrixT[idx - 1][idx]; + } + + // Wilkinson's original ad hoc shift + if (iteration == 10) { + shift.exShift += shift.x; + for (int i = 0; i <= idx; i++) { + matrixT[i][i] -= shift.x; + } + final double s = FastMath.abs(matrixT[idx][idx - 1]) + FastMath.abs(matrixT[idx - 1][idx - 2]); + shift.x = 0.75 * s; + shift.y = 0.75 * s; + shift.w = -0.4375 * s * s; + } + + // MATLAB's new ad hoc shift + if (iteration == 30) { + double s = (shift.y - shift.x) / 2.0; + s = s * s + shift.w; + if (s > 0.0) { + s = FastMath.sqrt(s); + if (shift.y < shift.x) { + s = -s; + } + s = shift.x - shift.w / ((shift.y - shift.x) / 2.0 + s); + for (int i = 0; i <= idx; i++) { + matrixT[i][i] -= s; + } + shift.exShift += s; + shift.x = shift.y = shift.w = 0.964; + } + } + } + + /** + * Initialize the householder vectors for the QR step. + * + * @param il the index of the small sub-diagonal element + * @param iu the current eigenvalue index + * @param shift shift information holder + * @param hVec the initial houseHolder vector + * @return the start index for the QR step + */ + private int initQRStep(int il, final int iu, final ShiftInfo shift, double[] hVec) { + // Look for two consecutive small sub-diagonal elements + int im = iu - 2; + while (im >= il) { + final double z = matrixT[im][im]; + final double r = shift.x - z; + double s = shift.y - z; + hVec[0] = (r * s - shift.w) / matrixT[im + 1][im] + matrixT[im][im + 1]; + hVec[1] = matrixT[im + 1][im + 1] - z - r - s; + hVec[2] = matrixT[im + 2][im + 1]; + + if (im == il) { + break; + } + + final double lhs = FastMath.abs(matrixT[im][im - 1]) * (FastMath.abs(hVec[1]) + FastMath.abs(hVec[2])); + final double rhs = FastMath.abs(hVec[0]) * (FastMath.abs(matrixT[im - 1][im - 1]) + + FastMath.abs(z) + + FastMath.abs(matrixT[im + 1][im + 1])); + + if (lhs < epsilon * rhs) { + break; + } + im--; + } + + return im; + } + + /** + * Perform a double QR step involving rows l:idx and columns m:n + * + * @param il the index of the small sub-diagonal element + * @param im the start index for the QR step + * @param iu the current eigenvalue index + * @param shift shift information holder + * @param hVec the initial houseHolder vector + */ + private void performDoubleQRStep(final int il, final int im, final int iu, + final ShiftInfo shift, final double[] hVec) { + + final int n = matrixT.length; + double p = hVec[0]; + double q = hVec[1]; + double r = hVec[2]; + + for (int k = im; k <= iu - 1; k++) { + boolean notlast = k != (iu - 1); + if (k != im) { + p = matrixT[k][k - 1]; + q = matrixT[k + 1][k - 1]; + r = notlast ? matrixT[k + 2][k - 1] : 0.0; + shift.x = FastMath.abs(p) + FastMath.abs(q) + FastMath.abs(r); + if (Precision.equals(shift.x, 0.0, epsilon)) { + continue; + } + p /= shift.x; + q /= shift.x; + r /= shift.x; + } + double s = FastMath.sqrt(p * p + q * q + r * r); + if (p < 0.0) { + s = -s; + } + if (s != 0.0) { + if (k != im) { + matrixT[k][k - 1] = -s * shift.x; + } else if (il != im) { + matrixT[k][k - 1] = -matrixT[k][k - 1]; + } + p += s; + shift.x = p / s; + shift.y = q / s; + double z = r / s; + q /= p; + r /= p; + + // Row modification + for (int j = k; j < n; j++) { + p = matrixT[k][j] + q * matrixT[k + 1][j]; + if (notlast) { + p += r * matrixT[k + 2][j]; + matrixT[k + 2][j] -= p * z; + } + matrixT[k][j] -= p * shift.x; + matrixT[k + 1][j] -= p * shift.y; + } + + // Column modification + for (int i = 0; i <= FastMath.min(iu, k + 3); i++) { + p = shift.x * matrixT[i][k] + shift.y * matrixT[i][k + 1]; + if (notlast) { + p += z * matrixT[i][k + 2]; + matrixT[i][k + 2] -= p * r; + } + matrixT[i][k] -= p; + matrixT[i][k + 1] -= p * q; + } + + // Accumulate transformations + final int high = matrixT.length - 1; + for (int i = 0; i <= high; i++) { + p = shift.x * matrixP[i][k] + shift.y * matrixP[i][k + 1]; + if (notlast) { + p += z * matrixP[i][k + 2]; + matrixP[i][k + 2] -= p * r; + } + matrixP[i][k] -= p; + matrixP[i][k + 1] -= p * q; + } + } // (s != 0) + } // k loop + + // clean up pollution due to round-off errors + for (int i = im + 2; i <= iu; i++) { + matrixT[i][i-2] = 0.0; + if (i > im + 2) { + matrixT[i][i-3] = 0.0; + } + } + } + + /** + * Internal data structure holding the current shift information. + * Contains variable names as present in the original JAMA code. + */ + private static class ShiftInfo { + // CHECKSTYLE: stop all + + /** x shift info */ + double x; + /** y shift info */ + double y; + /** w shift info */ + double w; + /** Indicates an exceptional shift. */ + double exShift; + + // CHECKSTYLE: resume all + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularMatrixException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularMatrixException.java new file mode 100644 index 000000000..4abc87034 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularMatrixException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a non-singular matrix is expected. + * + * @since 3.0 + */ +public class SingularMatrixException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -4206514844735401070L; + + /** + * Construct an exception. + */ + public SingularMatrixException() { + super(LocalizedFormats.SINGULAR_MATRIX); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularOperatorException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularOperatorException.java new file mode 100644 index 000000000..447fa87b2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularOperatorException.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when trying to invert a singular operator. + * + * @since 3.0 + */ +public class SingularOperatorException + extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = -476049978595245033L; + + /** + * Creates a new instance of this class. + */ + public SingularOperatorException() { + super(LocalizedFormats.SINGULAR_OPERATOR); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularValueDecomposition.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularValueDecomposition.java new file mode 100644 index 000000000..bd0c017c3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SingularValueDecomposition.java @@ -0,0 +1,741 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Calculates the compact Singular Value Decomposition of a matrix. + *

    + * The Singular Value Decomposition of matrix A is a set of three matrices: U, + * Σ and V such that A = U × Σ × VT. 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 VT is also orthogonal) where + * p=min(m,n). + *

    + *

    This class is similar to the class with similar name from the + * JAMA library, with the + * following changes:

    + *
      + *
    • the {@code norm2} method which has been renamed as {@link #getNorm() + * getNorm},
    • + *
    • the {@code cond} method which has been renamed as {@link + * #getConditionNumber() getConditionNumber},
    • + *
    • the {@code rank} method which has been renamed as {@link #getRank() + * getRank},
    • + *
    • a {@link #getUT() getUT} method has been added,
    • + *
    • a {@link #getVT() getVT} method has been added,
    • + *
    • a {@link #getSolver() getSolver} method has been added,
    • + *
    • a {@link #getCovariance(double) getCovariance} method has been added.
    • + *
    + * @see MathWorld + * @see Wikipedia + * @since 2.0 (changed to concrete class in 3.0) + */ +public class SingularValueDecomposition { + /** Relative threshold for small singular values. */ + private static final double EPS = 0x1.0p-52; + /** Absolute threshold for small singular values. */ + private static final double TINY = 0x1.0p-966; + /** Computed singular values. */ + private final double[] singularValues; + /** max(row dimension, column dimension). */ + private final int m; + /** min(row dimension, column dimension). */ + private final int n; + /** Indicator for transposed matrix. */ + private final boolean transposed; + /** Cached value of U matrix. */ + private final RealMatrix cachedU; + /** Cached value of transposed U matrix. */ + private RealMatrix cachedUt; + /** Cached value of S (diagonal) matrix. */ + private RealMatrix cachedS; + /** Cached value of V matrix. */ + private final RealMatrix cachedV; + /** Cached value of transposed V matrix. */ + private RealMatrix cachedVt; + /** + * Tolerance value for small singular values, calculated once we have + * populated "singularValues". + **/ + private final double tol; + + /** + * Calculates the compact Singular Value Decomposition of the given matrix. + * + * @param matrix Matrix to decompose. + */ + public SingularValueDecomposition(final RealMatrix matrix) { + final double[][] A; + + // "m" is always the largest dimension. + if (matrix.getRowDimension() < matrix.getColumnDimension()) { + transposed = true; + A = matrix.transpose().getData(); + m = matrix.getColumnDimension(); + n = matrix.getRowDimension(); + } else { + transposed = false; + A = matrix.getData(); + m = matrix.getRowDimension(); + n = matrix.getColumnDimension(); + } + + singularValues = new double[n]; + final double[][] U = new double[m][n]; + final double[][] V = new double[n][n]; + final double[] e = new double[n]; + final double[] work = new double[m]; + // Reduce A to bidiagonal form, storing the diagonal elements + // in s and the super-diagonal elements in e. + final int nct = FastMath.min(m - 1, n); + final int nrt = FastMath.max(0, n - 2); + for (int k = 0; k < FastMath.max(nct, nrt); k++) { + if (k < nct) { + // Compute the transformation for the k-th column and + // place the k-th diagonal in s[k]. + // Compute 2-norm of k-th column without under/overflow. + singularValues[k] = 0; + for (int i = k; i < m; i++) { + singularValues[k] = FastMath.hypot(singularValues[k], A[i][k]); + } + if (singularValues[k] != 0) { + if (A[k][k] < 0) { + singularValues[k] = -singularValues[k]; + } + for (int i = k; i < m; i++) { + A[i][k] /= singularValues[k]; + } + A[k][k] += 1; + } + singularValues[k] = -singularValues[k]; + } + for (int j = k + 1; j < n; j++) { + if (k < nct && + singularValues[k] != 0) { + // Apply the transformation. + double t = 0; + for (int i = k; i < m; i++) { + t += A[i][k] * A[i][j]; + } + t = -t / A[k][k]; + for (int i = k; i < m; i++) { + A[i][j] += t * A[i][k]; + } + } + // Place the k-th row of A into e for the + // subsequent calculation of the row transformation. + e[j] = A[k][j]; + } + if (k < nct) { + // Place the transformation in U for subsequent back + // multiplication. + for (int i = k; i < m; i++) { + U[i][k] = A[i][k]; + } + } + if (k < nrt) { + // Compute the k-th row transformation and place the + // k-th super-diagonal in e[k]. + // Compute 2-norm without under/overflow. + e[k] = 0; + for (int i = k + 1; i < n; i++) { + e[k] = FastMath.hypot(e[k], e[i]); + } + if (e[k] != 0) { + if (e[k + 1] < 0) { + e[k] = -e[k]; + } + for (int i = k + 1; i < n; i++) { + e[i] /= e[k]; + } + e[k + 1] += 1; + } + e[k] = -e[k]; + if (k + 1 < m && + e[k] != 0) { + // Apply the transformation. + for (int i = k + 1; i < m; i++) { + work[i] = 0; + } + for (int j = k + 1; j < n; j++) { + for (int i = k + 1; i < m; i++) { + work[i] += e[j] * A[i][j]; + } + } + for (int j = k + 1; j < n; j++) { + final double t = -e[j] / e[k + 1]; + for (int i = k + 1; i < m; i++) { + A[i][j] += t * work[i]; + } + } + } + + // Place the transformation in V for subsequent + // back multiplication. + for (int i = k + 1; i < n; i++) { + V[i][k] = e[i]; + } + } + } + // Set up the final bidiagonal matrix or order p. + int p = n; + if (nct < n) { + singularValues[nct] = A[nct][nct]; + } + if (m < p) { + singularValues[p - 1] = 0; + } + if (nrt + 1 < p) { + e[nrt] = A[nrt][p - 1]; + } + e[p - 1] = 0; + + // Generate U. + for (int j = nct; j < n; j++) { + for (int i = 0; i < m; i++) { + U[i][j] = 0; + } + U[j][j] = 1; + } + for (int k = nct - 1; k >= 0; k--) { + if (singularValues[k] != 0) { + for (int j = k + 1; j < n; j++) { + double t = 0; + for (int i = k; i < m; i++) { + t += U[i][k] * U[i][j]; + } + t = -t / U[k][k]; + for (int i = k; i < m; i++) { + U[i][j] += t * U[i][k]; + } + } + for (int i = k; i < m; i++) { + U[i][k] = -U[i][k]; + } + U[k][k] = 1 + U[k][k]; + for (int i = 0; i < k - 1; i++) { + U[i][k] = 0; + } + } else { + for (int i = 0; i < m; i++) { + U[i][k] = 0; + } + U[k][k] = 1; + } + } + + // Generate V. + for (int k = n - 1; k >= 0; k--) { + if (k < nrt && + e[k] != 0) { + for (int j = k + 1; j < n; j++) { + double t = 0; + for (int i = k + 1; i < n; i++) { + t += V[i][k] * V[i][j]; + } + t = -t / V[k + 1][k]; + for (int i = k + 1; i < n; i++) { + V[i][j] += t * V[i][k]; + } + } + } + for (int i = 0; i < n; i++) { + V[i][k] = 0; + } + V[k][k] = 1; + } + + // Main iteration loop for the singular values. + final int pp = p - 1; + while (p > 0) { + int k; + int kase; + // Here is where a test for too many iterations would go. + // This section of the program inspects for + // negligible elements in the s and e arrays. On + // completion the variables kase and k are set as follows. + // kase = 1 if s(p) and e[k-1] are negligible and k

    = 0; k--) { + final double threshold + = TINY + EPS * (FastMath.abs(singularValues[k]) + + FastMath.abs(singularValues[k + 1])); + + // the following condition is written this way in order + // to break out of the loop when NaN occurs, writing it + // as "if (FastMath.abs(e[k]) <= threshold)" would loop + // indefinitely in case of NaNs because comparison on NaNs + // always return false, regardless of what is checked + // see issue MATH-947 + if (!(FastMath.abs(e[k]) > threshold)) { + e[k] = 0; + break; + } + + } + + if (k == p - 2) { + kase = 4; + } else { + int ks; + for (ks = p - 1; ks >= k; ks--) { + if (ks == k) { + break; + } + final double t = (ks != p ? FastMath.abs(e[ks]) : 0) + + (ks != k + 1 ? FastMath.abs(e[ks - 1]) : 0); + if (FastMath.abs(singularValues[ks]) <= TINY + EPS * t) { + singularValues[ks] = 0; + break; + } + } + if (ks == k) { + kase = 3; + } else if (ks == p - 1) { + kase = 1; + } else { + kase = 2; + k = ks; + } + } + k++; + // Perform the task indicated by kase. + switch (kase) { + // Deflate negligible s(p). + case 1: { + double f = e[p - 2]; + e[p - 2] = 0; + for (int j = p - 2; j >= k; j--) { + double t = FastMath.hypot(singularValues[j], f); + final double cs = singularValues[j] / t; + final double sn = f / t; + singularValues[j] = t; + if (j != k) { + f = -sn * e[j - 1]; + e[j - 1] = cs * e[j - 1]; + } + + for (int i = 0; i < n; i++) { + t = cs * V[i][j] + sn * V[i][p - 1]; + V[i][p - 1] = -sn * V[i][j] + cs * V[i][p - 1]; + V[i][j] = t; + } + } + } + break; + // Split at negligible s(k). + case 2: { + double f = e[k - 1]; + e[k - 1] = 0; + for (int j = k; j < p; j++) { + double t = FastMath.hypot(singularValues[j], f); + final double cs = singularValues[j] / t; + final double sn = f / t; + singularValues[j] = t; + f = -sn * e[j]; + e[j] = cs * e[j]; + + for (int i = 0; i < m; i++) { + t = cs * U[i][j] + sn * U[i][k - 1]; + U[i][k - 1] = -sn * U[i][j] + cs * U[i][k - 1]; + U[i][j] = t; + } + } + } + break; + // Perform one qr step. + case 3: { + // Calculate the shift. + final double maxPm1Pm2 = FastMath.max(FastMath.abs(singularValues[p - 1]), + FastMath.abs(singularValues[p - 2])); + final double scale = FastMath.max(FastMath.max(FastMath.max(maxPm1Pm2, + FastMath.abs(e[p - 2])), + FastMath.abs(singularValues[k])), + FastMath.abs(e[k])); + final double sp = singularValues[p - 1] / scale; + final double spm1 = singularValues[p - 2] / scale; + final double epm1 = e[p - 2] / scale; + final double sk = singularValues[k] / scale; + final double ek = e[k] / scale; + final double b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2.0; + final double c = (sp * epm1) * (sp * epm1); + double shift = 0; + if (b != 0 || + c != 0) { + shift = FastMath.sqrt(b * b + c); + if (b < 0) { + shift = -shift; + } + shift = c / (b + shift); + } + double f = (sk + sp) * (sk - sp) + shift; + double g = sk * ek; + // Chase zeros. + for (int j = k; j < p - 1; j++) { + double t = FastMath.hypot(f, g); + double cs = f / t; + double sn = g / t; + if (j != k) { + e[j - 1] = t; + } + f = cs * singularValues[j] + sn * e[j]; + e[j] = cs * e[j] - sn * singularValues[j]; + g = sn * singularValues[j + 1]; + singularValues[j + 1] = cs * singularValues[j + 1]; + + for (int i = 0; i < n; i++) { + t = cs * V[i][j] + sn * V[i][j + 1]; + V[i][j + 1] = -sn * V[i][j] + cs * V[i][j + 1]; + V[i][j] = t; + } + t = FastMath.hypot(f, g); + cs = f / t; + sn = g / t; + singularValues[j] = t; + f = cs * e[j] + sn * singularValues[j + 1]; + singularValues[j + 1] = -sn * e[j] + cs * singularValues[j + 1]; + g = sn * e[j + 1]; + e[j + 1] = cs * e[j + 1]; + if (j < m - 1) { + for (int i = 0; i < m; i++) { + t = cs * U[i][j] + sn * U[i][j + 1]; + U[i][j + 1] = -sn * U[i][j] + cs * U[i][j + 1]; + U[i][j] = t; + } + } + } + e[p - 2] = f; + } + break; + // Convergence. + default: { + // Make the singular values positive. + if (singularValues[k] <= 0) { + singularValues[k] = singularValues[k] < 0 ? -singularValues[k] : 0; + + for (int i = 0; i <= pp; i++) { + V[i][k] = -V[i][k]; + } + } + // Order the singular values. + while (k < pp) { + if (singularValues[k] >= singularValues[k + 1]) { + break; + } + double t = singularValues[k]; + singularValues[k] = singularValues[k + 1]; + singularValues[k + 1] = t; + if (k < n - 1) { + for (int i = 0; i < n; i++) { + t = V[i][k + 1]; + V[i][k + 1] = V[i][k]; + V[i][k] = t; + } + } + if (k < m - 1) { + for (int i = 0; i < m; i++) { + t = U[i][k + 1]; + U[i][k + 1] = U[i][k]; + U[i][k] = t; + } + } + k++; + } + p--; + } + break; + } + } + + // Set the small value tolerance used to calculate rank and pseudo-inverse + tol = FastMath.max(m * singularValues[0] * EPS, + FastMath.sqrt(Precision.SAFE_MIN)); + + if (!transposed) { + cachedU = MatrixUtils.createRealMatrix(U); + cachedV = MatrixUtils.createRealMatrix(V); + } else { + cachedU = MatrixUtils.createRealMatrix(V); + cachedV = MatrixUtils.createRealMatrix(U); + } + } + + /** + * Returns the matrix U of the decomposition. + *

    U is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the U matrix + * @see #getUT() + */ + public RealMatrix getU() { + // return the cached matrix + return cachedU; + + } + + /** + * Returns the transpose of the matrix U of the decomposition. + *

    U is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the U matrix (or null if decomposed matrix is singular) + * @see #getU() + */ + public RealMatrix getUT() { + if (cachedUt == null) { + cachedUt = getU().transpose(); + } + // return the cached matrix + return cachedUt; + } + + /** + * Returns the diagonal matrix Σ of the decomposition. + *

    Σ is a diagonal matrix. The singular values are provided in + * non-increasing order, for compatibility with Jama.

    + * @return the Σ matrix + */ + public RealMatrix getS() { + if (cachedS == null) { + // cache the matrix for subsequent calls + cachedS = MatrixUtils.createRealDiagonalMatrix(singularValues); + } + return cachedS; + } + + /** + * Returns the diagonal elements of the matrix Σ of the decomposition. + *

    The singular values are provided in non-increasing order, for + * compatibility with Jama.

    + * @return the diagonal elements of the Σ matrix + */ + public double[] getSingularValues() { + return singularValues.clone(); + } + + /** + * Returns the matrix V of the decomposition. + *

    V is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the V matrix (or null if decomposed matrix is singular) + * @see #getVT() + */ + public RealMatrix getV() { + // return the cached matrix + return cachedV; + } + + /** + * Returns the transpose of the matrix V of the decomposition. + *

    V is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the V matrix (or null if decomposed matrix is singular) + * @see #getV() + */ + public RealMatrix getVT() { + if (cachedVt == null) { + cachedVt = getV().transpose(); + } + // return the cached matrix + return cachedVt; + } + + /** + * Returns the n × n covariance matrix. + *

    The covariance matrix is V × J × VT + * where J is the diagonal matrix of the inverse of the squares of + * the singular values.

    + * @param minSingularValue value below which singular values are ignored + * (a 0 or negative value implies all singular value will be used) + * @return covariance matrix + * @exception IllegalArgumentException if minSingularValue is larger than + * the largest singular value, meaning all singular values are ignored + */ + public RealMatrix getCovariance(final double minSingularValue) { + // get the number of singular values to consider + final int p = singularValues.length; + int dimension = 0; + while (dimension < p && + singularValues[dimension] >= minSingularValue) { + ++dimension; + } + + if (dimension == 0) { + throw new NumberIsTooLargeException(LocalizedFormats.TOO_LARGE_CUTOFF_SINGULAR_VALUE, + minSingularValue, singularValues[0], true); + } + + final double[][] data = new double[dimension][p]; + getVT().walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() { + /** {@inheritDoc} */ + @Override + public void visit(final int row, final int column, + final double value) { + data[row][column] = value / singularValues[row]; + } + }, 0, dimension - 1, 0, p - 1); + + RealMatrix jv = new Array2DRowRealMatrix(data, false); + return jv.transpose().multiply(jv); + } + + /** + * Returns the L2 norm of the matrix. + *

    The L2 norm is max(|A × u|2 / + * |u|2), where |.|2 denotes the vectorial 2-norm + * (i.e. the traditional euclidian norm).

    + * @return norm + */ + public double getNorm() { + return singularValues[0]; + } + + /** + * Return the condition number of the matrix. + * @return condition number of the matrix + */ + public double getConditionNumber() { + return singularValues[0] / singularValues[n - 1]; + } + + /** + * Computes the inverse of the condition number. + * In cases of rank deficiency, the {@link #getConditionNumber() condition + * number} will become undefined. + * + * @return the inverse of the condition number. + */ + public double getInverseConditionNumber() { + return singularValues[n - 1] / singularValues[0]; + } + + /** + * Return the effective numerical matrix rank. + *

    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(s1) where ulp(s1) + * is the least significant bit of the largest singular value.

    + * @return effective numerical matrix rank + */ + public int getRank() { + int r = 0; + for (int i = 0; i < singularValues.length; i++) { + if (singularValues[i] > tol) { + r++; + } + } + return r; + } + + /** + * Get a solver for finding the A × X = B solution in least square sense. + * @return a solver + */ + public DecompositionSolver getSolver() { + return new Solver(singularValues, getUT(), getV(), getRank() == m, tol); + } + + /** Specialized solver. */ + private static class Solver implements DecompositionSolver { + /** Pseudo-inverse of the initial matrix. */ + private final RealMatrix pseudoInverse; + /** Singularity indicator. */ + private boolean nonSingular; + + /** + * Build a solver from decomposed matrix. + * + * @param singularValues Singular values. + * @param uT UT matrix of the decomposition. + * @param v V matrix of the decomposition. + * @param nonSingular Singularity indicator. + * @param tol tolerance for singular values + */ + private Solver(final double[] singularValues, final RealMatrix uT, + final RealMatrix v, final boolean nonSingular, final double tol) { + final double[][] suT = uT.getData(); + for (int i = 0; i < singularValues.length; ++i) { + final double a; + if (singularValues[i] > tol) { + a = 1 / singularValues[i]; + } else { + a = 0; + } + final double[] suTi = suT[i]; + for (int j = 0; j < suTi.length; ++j) { + suTi[j] *= a; + } + } + pseudoInverse = v.multiply(new Array2DRowRealMatrix(suT, false)); + this.nonSingular = nonSingular; + } + + /** + * Solve the linear equation A × X = B in least square sense. + *

    + * The m×n matrix A may not be square, the solution X is such that + * ||A × X - B|| is minimal. + *

    + * @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 DimensionMismatchException + * if the matrices dimensions do not match. + */ + public RealVector solve(final RealVector b) { + return pseudoInverse.operate(b); + } + + /** + * Solve the linear equation A × X = B in least square sense. + *

    + * The m×n matrix A may not be square, the solution X is such that + * ||A × X - B|| is minimal. + *

    + * + * @param b Right-hand side of the equation A × X = B + * @return a matrix X that minimizes the two norm of A × X - B + * @throws DimensionMismatchException + * if the matrices dimensions do not match. + */ + public RealMatrix solve(final RealMatrix b) { + return pseudoInverse.multiply(b); + } + + /** + * Check if the decomposed matrix is non-singular. + * + * @return {@code true} if the decomposed matrix is non-singular. + */ + public boolean isNonSingular() { + return nonSingular; + } + + /** + * Get the pseudo-inverse of the decomposed matrix. + * + * @return the inverse matrix. + */ + public RealMatrix getInverse() { + return pseudoInverse; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldMatrix.java new file mode 100644 index 000000000..3e04696a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldMatrix.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 com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.OpenIntToFieldHashMap; + +/** + * Sparse matrix implementation based on an open addressed map. + * + *

    + * Caveat: This implementation assumes that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results. + *

    + * @param the type of the field elements + * @since 2.0 + */ +public class SparseFieldMatrix> extends AbstractFieldMatrix { + + /** Storage for (sparse) matrix elements. */ + private final OpenIntToFieldHashMap entries; + /** Row dimension. */ + private final int rows; + /** Column dimension. */ + private final int columns; + + /** + * Create a matrix with no data. + * + * @param field Field to which the elements belong. + */ + public SparseFieldMatrix(final Field field) { + super(field); + rows = 0; + columns= 0; + entries = new OpenIntToFieldHashMap(field); + } + + /** + * Create a new SparseFieldMatrix with the supplied row and column + * dimensions. + * + * @param field Field to which the elements belong. + * @param rowDimension Number of rows in the new matrix. + * @param columnDimension Number of columns in the new matrix. + * @throws NotStrictlyPositiveException + * if row or column dimension is not positive. + */ + public SparseFieldMatrix(final Field field, + final int rowDimension, final int columnDimension) { + super(field, rowDimension, columnDimension); + this.rows = rowDimension; + this.columns = columnDimension; + entries = new OpenIntToFieldHashMap(field); + } + + /** + * Copy constructor. + * + * @param other Instance to copy. + */ + public SparseFieldMatrix(SparseFieldMatrix other) { + super(other.getField(), other.getRowDimension(), other.getColumnDimension()); + rows = other.getRowDimension(); + columns = other.getColumnDimension(); + entries = new OpenIntToFieldHashMap(other.entries); + } + + /** + * Generic copy constructor. + * + * @param other Instance to copy. + */ + public SparseFieldMatrix(FieldMatrix other){ + super(other.getField(), other.getRowDimension(), other.getColumnDimension()); + rows = other.getRowDimension(); + columns = other.getColumnDimension(); + entries = new OpenIntToFieldHashMap(getField()); + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + setEntry(i, j, other.getEntry(i, j)); + } + } + } + + /** {@inheritDoc} */ + @Override + public void addToEntry(int row, int column, T increment) { + checkRowIndex(row); + checkColumnIndex(column); + final int key = computeKey(row, column); + final T value = entries.get(key).add(increment); + if (getField().getZero().equals(value)) { + entries.remove(key); + } else { + entries.put(key, value); + } + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix copy() { + return new SparseFieldMatrix(this); + } + + /** {@inheritDoc} */ + @Override + public FieldMatrix createMatrix(int rowDimension, int columnDimension) { + return new SparseFieldMatrix(getField(), rowDimension, columnDimension); + } + + /** {@inheritDoc} */ + @Override + public int getColumnDimension() { + return columns; + } + + /** {@inheritDoc} */ + @Override + public T getEntry(int row, int column) { + checkRowIndex(row); + checkColumnIndex(column); + return entries.get(computeKey(row, column)); + } + + /** {@inheritDoc} */ + @Override + public int getRowDimension() { + return rows; + } + + /** {@inheritDoc} */ + @Override + public void multiplyEntry(int row, int column, T factor) { + checkRowIndex(row); + checkColumnIndex(column); + final int key = computeKey(row, column); + final T value = entries.get(key).multiply(factor); + if (getField().getZero().equals(value)) { + entries.remove(key); + } else { + entries.put(key, value); + } + + } + + /** {@inheritDoc} */ + @Override + public void setEntry(int row, int column, T value) { + checkRowIndex(row); + checkColumnIndex(column); + if (getField().getZero().equals(value)) { + entries.remove(computeKey(row, column)); + } else { + entries.put(computeKey(row, column), value); + } + } + + /** + * Compute the key to access a matrix element. + * + * @param row Row index of the matrix element. + * @param column Column index of the matrix element. + * @return the key within the map to access the matrix element. + */ + private int computeKey(int row, int column) { + return row * columns + column; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldVector.java new file mode 100644 index 000000000..a8e09d907 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseFieldVector.java @@ -0,0 +1,793 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.OpenIntToFieldHashMap; + +/** + * This class implements the {@link FieldVector} interface with a {@link OpenIntToFieldHashMap} backing store. + *

    + * Caveat: This implementation assumes that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results. + *

    + * @param the type of the field elements + * @since 2.0 + */ +public class SparseFieldVector> implements FieldVector, Serializable { + /** Serialization identifier. */ + private static final long serialVersionUID = 7841233292190413362L; + /** Field to which the elements belong. */ + private final Field field; + /** Entries of the vector. */ + private final OpenIntToFieldHashMap entries; + /** Dimension of the vector. */ + private final int virtualSize; + + /** + * Build a 0-length vector. + * Zero-length vectors may be used to initialize construction of vectors + * by data gathering. We start with zero-length and use either the {@link + * #SparseFieldVector(SparseFieldVector, int)} constructor + * or one of the {@code append} method ({@link #append(FieldVector)} or + * {@link #append(SparseFieldVector)}) to gather data into this vector. + * + * @param field Field to which the elements belong. + */ + public SparseFieldVector(Field field) { + this(field, 0); + } + + + /** + * Construct a vector of zeroes. + * + * @param field Field to which the elements belong. + * @param dimension Size of the vector. + */ + public SparseFieldVector(Field field, int dimension) { + this.field = field; + virtualSize = dimension; + entries = new OpenIntToFieldHashMap(field); + } + + /** + * Build a resized vector, for use with append. + * + * @param v Original vector + * @param resize Amount to add. + */ + protected SparseFieldVector(SparseFieldVector v, int resize) { + field = v.field; + virtualSize = v.getDimension() + resize; + entries = new OpenIntToFieldHashMap(v.entries); + } + + + /** + * Build a vector with known the sparseness (for advanced use only). + * + * @param field Field to which the elements belong. + * @param dimension Size of the vector. + * @param expectedSize Expected number of non-zero entries. + */ + public SparseFieldVector(Field field, int dimension, int expectedSize) { + this.field = field; + virtualSize = dimension; + entries = new OpenIntToFieldHashMap(field,expectedSize); + } + + /** + * Create from a Field array. + * Only non-zero entries will be stored. + * + * @param field Field to which the elements belong. + * @param values Set of values to create from. + * @exception NullArgumentException if values is null + */ + public SparseFieldVector(Field field, T[] values) throws NullArgumentException { + MathUtils.checkNotNull(values); + this.field = field; + virtualSize = values.length; + entries = new OpenIntToFieldHashMap(field); + for (int key = 0; key < values.length; key++) { + T value = values[key]; + entries.put(key, value); + } + } + + /** + * Copy constructor. + * + * @param v Instance to copy. + */ + public SparseFieldVector(SparseFieldVector v) { + field = v.field; + virtualSize = v.getDimension(); + entries = new OpenIntToFieldHashMap(v.getEntries()); + } + + /** + * Get the entries of this instance. + * + * @return the entries of this instance + */ + private OpenIntToFieldHashMap getEntries() { + return entries; + } + + /** + * Optimized method to add sparse vectors. + * + * @param v Vector to add. + * @return {@code this + v}. + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this}. + */ + public FieldVector add(SparseFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + SparseFieldVector res = (SparseFieldVector)copy(); + OpenIntToFieldHashMap.Iterator iter = v.getEntries().iterator(); + while (iter.hasNext()) { + iter.advance(); + int key = iter.key(); + T value = iter.value(); + if (entries.containsKey(key)) { + res.setEntry(key, entries.get(key).add(value)); + } else { + res.setEntry(key, value); + } + } + return res; + + } + + /** + * Construct a vector by appending a vector to this vector. + * + * @param v Vector to append to this one. + * @return a new vector. + */ + public FieldVector append(SparseFieldVector v) { + SparseFieldVector res = new SparseFieldVector(this, v.getDimension()); + OpenIntToFieldHashMap.Iterator iter = v.entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res.setEntry(iter.key() + virtualSize, iter.value()); + } + return res; + } + + /** {@inheritDoc} */ + public FieldVector append(FieldVector v) { + if (v instanceof SparseFieldVector) { + return append((SparseFieldVector) v); + } else { + final int n = v.getDimension(); + FieldVector res = new SparseFieldVector(this, n); + for (int i = 0; i < n; i++) { + res.setEntry(i + virtualSize, v.getEntry(i)); + } + return res; + } + } + + /** {@inheritDoc} + * @exception NullArgumentException if d is null + */ + public FieldVector append(T d) throws NullArgumentException { + MathUtils.checkNotNull(d); + FieldVector res = new SparseFieldVector(this, 1); + res.setEntry(virtualSize, d); + return res; + } + + /** {@inheritDoc} */ + public FieldVector copy() { + return new SparseFieldVector(this); + } + + /** {@inheritDoc} */ + public T dotProduct(FieldVector v) throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + T res = field.getZero(); + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res = res.add(v.getEntry(iter.key()).multiply(iter.value())); + } + return res; + } + + /** {@inheritDoc} */ + public FieldVector ebeDivide(FieldVector v) + throws DimensionMismatchException, MathArithmeticException { + checkVectorDimensions(v.getDimension()); + SparseFieldVector res = new SparseFieldVector(this); + OpenIntToFieldHashMap.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 ebeMultiply(FieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + SparseFieldVector res = new SparseFieldVector(this); + OpenIntToFieldHashMap.Iterator iter = res.entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res.setEntry(iter.key(), iter.value().multiply(v.getEntry(iter.key()))); + } + return res; + } + + /** + * {@inheritDoc} + * + * @deprecated as of 3.1, to be removed in 4.0. Please use the {@link #toArray()} method instead. + */ + @Deprecated + public T[] getData() { + return toArray(); + } + + /** {@inheritDoc} */ + public int getDimension() { + return virtualSize; + } + + /** {@inheritDoc} */ + public T getEntry(int index) throws OutOfRangeException { + checkIndex(index); + return entries.get(index); + } + + /** {@inheritDoc} */ + public Field getField() { + return field; + } + + /** {@inheritDoc} */ + public FieldVector getSubVector(int index, int n) + throws OutOfRangeException, NotPositiveException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n); + } + checkIndex(index); + checkIndex(index + n - 1); + SparseFieldVector res = new SparseFieldVector(field,n); + int end = index + n; + OpenIntToFieldHashMap.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 mapAdd(T d) throws NullArgumentException { + return copy().mapAddToSelf(d); + } + + /** {@inheritDoc} */ + public FieldVector mapAddToSelf(T d) throws NullArgumentException { + for (int i = 0; i < virtualSize; i++) { + setEntry(i, getEntry(i).add(d)); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapDivide(T d) + throws NullArgumentException, MathArithmeticException { + return copy().mapDivideToSelf(d); + } + + /** {@inheritDoc} */ + public FieldVector mapDivideToSelf(T d) + throws NullArgumentException, MathArithmeticException { + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + entries.put(iter.key(), iter.value().divide(d)); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapInv() throws MathArithmeticException { + return copy().mapInvToSelf(); + } + + /** {@inheritDoc} */ + public FieldVector mapInvToSelf() throws MathArithmeticException { + for (int i = 0; i < virtualSize; i++) { + setEntry(i, field.getOne().divide(getEntry(i))); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapMultiply(T d) throws NullArgumentException { + return copy().mapMultiplyToSelf(d); + } + + /** {@inheritDoc} */ + public FieldVector mapMultiplyToSelf(T d) throws NullArgumentException { + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + entries.put(iter.key(), iter.value().multiply(d)); + } + return this; + } + + /** {@inheritDoc} */ + public FieldVector mapSubtract(T d) throws NullArgumentException { + return copy().mapSubtractToSelf(d); + } + + /** {@inheritDoc} */ + public FieldVector mapSubtractToSelf(T d) throws NullArgumentException { + return mapAddToSelf(field.getZero().subtract(d)); + } + + /** + * Optimized method to compute outer product when both vectors are sparse. + * @param v vector with which outer product should be computed + * @return the matrix outer product between instance and v + */ + public FieldMatrix outerProduct(SparseFieldVector v) { + final int n = v.getDimension(); + SparseFieldMatrix res = new SparseFieldMatrix(field, virtualSize, n); + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + OpenIntToFieldHashMap.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 outerProduct(FieldVector v) { + if (v instanceof SparseFieldVector) { + return outerProduct((SparseFieldVector)v); + } else { + final int n = v.getDimension(); + FieldMatrix res = new SparseFieldMatrix(field, virtualSize, n); + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + int row = iter.key(); + FieldElementvalue = iter.value(); + for (int col = 0; col < n; col++) { + res.setEntry(row, col, value.multiply(v.getEntry(col))); + } + } + return res; + } + } + + /** {@inheritDoc} */ + public FieldVector projection(FieldVector v) + throws DimensionMismatchException, MathArithmeticException { + checkVectorDimensions(v.getDimension()); + return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v))); + } + + /** {@inheritDoc} + * @exception NullArgumentException if value is null + */ + public void set(T value) { + MathUtils.checkNotNull(value); + for (int i = 0; i < virtualSize; i++) { + setEntry(i, value); + } + } + + /** {@inheritDoc} + * @exception NullArgumentException if value is null + */ + public void setEntry(int index, T value) throws NullArgumentException, OutOfRangeException { + MathUtils.checkNotNull(value); + checkIndex(index); + entries.put(index, value); + } + + /** {@inheritDoc} */ + public void setSubVector(int index, FieldVector v) + throws OutOfRangeException { + checkIndex(index); + checkIndex(index + v.getDimension() - 1); + final int n = v.getDimension(); + for (int i = 0; i < n; i++) { + setEntry(i + index, v.getEntry(i)); + } + } + + /** + * Optimized method to compute {@code this} minus {@code v}. + * @param v vector to be subtracted + * @return {@code this - v} + * @throws DimensionMismatchException if {@code v} is not the same size as + * {@code this}. + */ + public SparseFieldVector subtract(SparseFieldVector v) + throws DimensionMismatchException { + checkVectorDimensions(v.getDimension()); + SparseFieldVector res = (SparseFieldVector)copy(); + OpenIntToFieldHashMap.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 subtract(FieldVector v) + throws DimensionMismatchException { + if (v instanceof SparseFieldVector) { + return subtract((SparseFieldVector)v); + } else { + final int n = v.getDimension(); + checkVectorDimensions(n); + SparseFieldVector res = new SparseFieldVector(this); + for (int i = 0; i < n; i++) { + if (entries.containsKey(i)) { + res.setEntry(i, entries.get(i).subtract(v.getEntry(i))); + } else { + res.setEntry(i, field.getZero().subtract(v.getEntry(i))); + } + } + return res; + } + } + + /** {@inheritDoc} */ + public T[] toArray() { + T[] res = MathArrays.buildArray(field, virtualSize); + OpenIntToFieldHashMap.Iterator iter = entries.iterator(); + while (iter.hasNext()) { + iter.advance(); + res[iter.key()] = iter.value(); + } + return res; + } + + /** + * Check whether an index is valid. + * + * @param index Index to check. + * @throws OutOfRangeException if the index is not valid. + */ + private void checkIndex(final int index) throws OutOfRangeException { + if (index < 0 || index >= getDimension()) { + throw new OutOfRangeException(index, 0, getDimension() - 1); + } + } + + /** + * Checks that the indices of a subvector are valid. + * + * @param start the index of the first entry of the subvector + * @param end the index of the last entry of the subvector (inclusive) + * @throws OutOfRangeException if {@code start} of {@code end} are not valid + * @throws NumberIsTooSmallException if {@code end < start} + * @since 3.3 + */ + private void checkIndices(final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + final int dim = getDimension(); + if ((start < 0) || (start >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, + dim - 1); + } + if ((end < 0) || (end >= dim)) { + throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, + dim - 1); + } + if (end < start) { + throw new NumberIsTooSmallException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, + end, start, false); + } + } + + /** + * Check if instance dimension is equal to some expected value. + * + * @param n Expected dimension. + * @throws DimensionMismatchException if the dimensions do not match. + */ + protected void checkVectorDimensions(int n) + throws DimensionMismatchException { + if (getDimension() != n) { + throw new DimensionMismatchException(getDimension(), n); + } + } + + /** {@inheritDoc} */ + public FieldVector add(FieldVector v) throws DimensionMismatchException { + if (v instanceof SparseFieldVector) { + return add((SparseFieldVector) v); + } else { + final int n = v.getDimension(); + checkVectorDimensions(n); + SparseFieldVector res = new SparseFieldVector(field, + getDimension()); + for (int i = 0; i < n; i++) { + res.setEntry(i, v.getEntry(i).add(getEntry(i))); + } + return res; + } + } + + /** + * Visits (but does not alter) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorPreservingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + visitor.visit(i, getEntry(i)); + } + return visitor.end(); + } + + /** + * Visits (but does not alter) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorPreservingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (but does not alter) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorPreservingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorPreservingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** + * Visits (and possibly alters) all entries of this vector in default order + * (increasing index). + * + * @param visitor the visitor to be used to process and modify the entries + * of this vector + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorChangingVisitor visitor) { + final int dim = getDimension(); + visitor.start(dim, 0, dim - 1); + for (int i = 0; i < dim; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) some entries of this vector in default order + * (increasing index). + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInDefaultOrder(final FieldVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + checkIndices(start, end); + visitor.start(getDimension(), start, end); + for (int i = start; i <= end; i++) { + setEntry(i, visitor.visit(i, getEntry(i))); + } + return visitor.end(); + } + + /** + * Visits (and possibly alters) all entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor the visitor to be used to process the entries of this + * vector + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorChangingVisitor visitor) { + return walkInDefaultOrder(visitor); + } + + /** + * Visits (and possibly change) some entries of this vector in optimized + * order. The order in which the entries are visited is selected so as to + * lead to the most efficient implementation; it might depend on the + * concrete implementation of this abstract class. + * + * @param visitor visitor to be used to process the entries of this vector + * @param start the index of the first entry to be visited + * @param end the index of the last entry to be visited (inclusive) + * @return the value returned by {@link FieldVectorChangingVisitor#end()} + * at the end of the walk + * @throws NumberIsTooSmallException if {@code end < start}. + * @throws OutOfRangeException if the indices are not valid. + * @since 3.3 + */ + public T walkInOptimizedOrder(final FieldVectorChangingVisitor visitor, + final int start, final int end) + throws NumberIsTooSmallException, OutOfRangeException { + return walkInDefaultOrder(visitor, start, end); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + virtualSize; + OpenIntToFieldHashMap.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 other = (SparseFieldVector) 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.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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealMatrix.java new file mode 100644 index 000000000..d21ababa9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealMatrix.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Marker interface for {@link RealMatrix} implementations that require sparse backing storage + * + *

    + * Caveat: Implementation are allowed to assume that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results. + *

    + * @since 2.0 + */ +public interface SparseRealMatrix extends RealMatrix { + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealVector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealVector.java new file mode 100644 index 000000000..bfcd44043 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SparseRealVector.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 com.fr.third.org.apache.commons.math3.linear; + +/** + * Marker class for RealVectors that require sparse backing storage + *

    + * Caveat: Implementation are allowed to assume that, for any {@code x}, + * the equality {@code x * 0d == 0d} holds. But it is is not true for + * {@code NaN}. Moreover, zero entries will lose their sign. + * Some operations (that involve {@code NaN} and/or infinities) may + * thus give incorrect results, like multiplications, divisions or + * functions mapping. + *

    + * @since 2.0 + */ +public abstract class SparseRealVector extends RealVector {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SymmLQ.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SymmLQ.java new file mode 100644 index 000000000..2e83bb89e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/SymmLQ.java @@ -0,0 +1,1222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.linear; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.ExceptionContext; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.IterationManager; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

    + * Implementation of the SYMMLQ iterative linear solver proposed by Paige and Saunders (1975). This implementation is + * largely based on the FORTRAN code by Pr. Michael A. Saunders, available here. + *

    + *

    + * SYMMLQ is designed to solve the system of linear equations A · x = b + * where A is an n × n self-adjoint linear operator (defined as a + * {@link RealLinearOperator}), and b is a given vector. The operator A is not + * required to be positive definite. If A is known to be definite, the method of + * conjugate gradients might be preferred, since it will require about the same + * number of iterations as SYMMLQ but slightly less work per iteration. + *

    + *

    + * SYMMLQ is designed to solve the system (A - shift · I) · x = b, + * where shift is a specified scalar value. If shift and b are suitably chosen, + * the computed vector x may approximate an (unnormalized) eigenvector of A, as + * in the methods of inverse iteration and/or Rayleigh-quotient iteration. + * Again, the linear operator (A - shift · I) need not be positive + * definite (but must be self-adjoint). The work per iteration is very + * slightly less if shift = 0. + *

    + *

    Preconditioning

    + *

    + * Preconditioning may reduce the number of iterations required. The solver may + * be provided with a positive definite preconditioner + * M = PT · P + * that is known to approximate + * (A - shift · I)-1 in some sense, where matrix-vector + * products of the form M · y = x can be computed efficiently. Then + * SYMMLQ will implicitly solve the system of equations + * P · (A - shift · I) · PT · + * xhat = P · b, i.e. + * Ahat · xhat = bhat, + * where + * Ahat = P · (A - shift · I) · PT, + * bhat = P · b, + * and return the solution + * x = PT · xhat. + * The associated residual is + * rhat = bhat - Ahat · xhat + * = P · [b - (A - shift · I) · x] + * = P · r. + *

    + *

    + * In the case of preconditioning, the {@link IterativeLinearSolverEvent}s that + * this solver fires are such that + * {@link IterativeLinearSolverEvent#getNormOfResidual()} returns the norm of + * the preconditioned, updated residual, ||P · r||, not the norm + * of the true residual ||r||. + *

    + *

    Default stopping criterion

    + *

    + * A default stopping criterion is implemented. The iterations stop when || rhat + * || ≤ δ || Ahat || || xhat ||, where xhat is the current estimate of + * the solution of the transformed system, rhat the current estimate of the + * corresponding residual, and δ a user-specified tolerance. + *

    + *

    Iteration count

    + *

    + * In the present context, an iteration should be understood as one evaluation + * of the matrix-vector product A · x. The initialization phase therefore + * counts as one iteration. If the user requires checks on the symmetry of A, + * this entails one further matrix-vector product in the initial phase. This + * further product is not accounted for in the iteration count. In + * other words, the number of iterations required to reach convergence will be + * identical, whether checks have been required or not. + *

    + *

    + * The present definition of the iteration count differs from that adopted in + * the original FOTRAN code, where the initialization phase was not + * taken into account. + *

    + *

    Initial guess of the solution

    + *

    + * The {@code x} parameter in + *

      + *
    • {@link #solve(RealLinearOperator, RealVector, RealVector)},
    • + *
    • {@link #solve(RealLinearOperator, RealLinearOperator, RealVector, RealVector)}},
    • + *
    • {@link #solveInPlace(RealLinearOperator, RealVector, RealVector)},
    • + *
    • {@link #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector)},
    • + *
    • {@link #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector, boolean, double)},
    • + *
    + * should not be considered as an initial guess, as it is set to zero in the + * initial phase. If x0 is known to be a good approximation to x, one + * should compute r0 = b - A · x, solve A · dx = r0, + * and set x = x0 + dx. + *

    + *

    Exception context

    + *

    + * Besides standard {@link DimensionMismatchException}, this class might throw + * {@link NonSelfAdjointOperatorException} if the linear operator or the + * preconditioner are not symmetric. In this case, the {@link ExceptionContext} + * provides more information + *

      + *
    • key {@code "operator"} points to the offending linear operator, say L,
    • + *
    • key {@code "vector1"} points to the first offending vector, say x, + *
    • key {@code "vector2"} points to the second offending vector, say y, such + * that xT · L · y ≠ yT · L + * · x (within a certain accuracy).
    • + *
    + *

    + *

    + * {@link NonPositiveDefiniteOperatorException} might also be thrown in case the + * preconditioner is not positive definite. The relevant keys to the + * {@link ExceptionContext} are + *

      + *
    • key {@code "operator"}, which points to the offending linear operator, + * say L,
    • + *
    • key {@code "vector"}, which points to the offending vector, say x, such + * that xT · L · x < 0.
    • + *
    + *

    + *

    References

    + *
    + *
    Paige and Saunders (1975)
    + *
    C. C. Paige and M. A. Saunders, + * Solution of Sparse Indefinite Systems of Linear Equations, SIAM + * Journal on Numerical Analysis 12(4): 617-629, 1975
    + *
    + * + * @since 3.0 + */ +public class SymmLQ + extends PreconditionedIterativeLinearSolver { + + /* + * IMPLEMENTATION NOTES + * -------------------- + * The implementation follows as closely as possible the notations of Paige + * and Saunders (1975). Attention must be paid to the fact that some + * quantities which are relevant to iteration k can only be computed in + * iteration (k+1). Therefore, minute attention must be paid to the index of + * each state variable of this algorithm. + * + * 1. Preconditioning + * --------------- + * The Lanczos iterations associated with Ahat and bhat read + * beta[1] = ||P * b|| + * v[1] = P * b / beta[1] + * beta[k+1] * v[k+1] = Ahat * v[k] - alpha[k] * v[k] - beta[k] * v[k-1] + * = P * (A - shift * I) * P' * v[k] - alpha[k] * v[k] + * - beta[k] * v[k-1] + * Multiplying both sides by P', we get + * beta[k+1] * (P' * v)[k+1] = M * (A - shift * I) * (P' * v)[k] + * - alpha[k] * (P' * v)[k] + * - beta[k] * (P' * v[k-1]), + * and + * alpha[k+1] = v[k+1]' * Ahat * v[k+1] + * = v[k+1]' * P * (A - shift * I) * P' * v[k+1] + * = (P' * v)[k+1]' * (A - shift * I) * (P' * v)[k+1]. + * + * In other words, the Lanczos iterations are unchanged, except for the fact + * that we really compute (P' * v) instead of v. It can easily be checked + * that all other formulas are unchanged. It must be noted that P is never + * explicitly used, only matrix-vector products involving are invoked. + * + * 2. Accounting for the shift parameter + * ---------------------------------- + * Is trivial: each time A.operate(x) is invoked, one must subtract shift * x + * to the result. + * + * 3. Accounting for the goodb flag + * ----------------------------- + * When goodb is set to true, the component of xL along b is computed + * separately. From Paige and Saunders (1975), equation (5.9), we have + * wbar[k+1] = s[k] * wbar[k] - c[k] * v[k+1], + * wbar[1] = v[1]. + * Introducing wbar2[k] = wbar[k] - s[1] * ... * s[k-1] * v[1], it can + * easily be verified by induction that wbar2 follows the same recursive + * relation + * wbar2[k+1] = s[k] * wbar2[k] - c[k] * v[k+1], + * wbar2[1] = 0, + * and we then have + * w[k] = c[k] * wbar2[k] + s[k] * v[k+1] + * + s[1] * ... * s[k-1] * c[k] * v[1]. + * Introducing w2[k] = w[k] - s[1] * ... * s[k-1] * c[k] * v[1], we find, + * from (5.10) + * xL[k] = zeta[1] * w[1] + ... + zeta[k] * w[k] + * = zeta[1] * w2[1] + ... + zeta[k] * w2[k] + * + (s[1] * c[2] * zeta[2] + ... + * + s[1] * ... * s[k-1] * c[k] * zeta[k]) * v[1] + * = xL2[k] + bstep[k] * v[1], + * where xL2[k] is defined by + * xL2[0] = 0, + * xL2[k+1] = xL2[k] + zeta[k+1] * w2[k+1], + * and bstep is defined by + * bstep[1] = 0, + * bstep[k] = bstep[k-1] + s[1] * ... * s[k-1] * c[k] * zeta[k]. + * We also have, from (5.11) + * xC[k] = xL[k-1] + zbar[k] * wbar[k] + * = xL2[k-1] + zbar[k] * wbar2[k] + * + (bstep[k-1] + s[1] * ... * s[k-1] * zbar[k]) * v[1]. + */ + + /** + *

    + * A simple container holding the non-final variables used in the + * iterations. Making the current state of the solver visible from the + * outside is necessary, because during the iterations, {@code x} does not + * exactly hold the current estimate of the solution. Indeed, + * {@code x} needs in general to be moved from the LQ point to the CG point. + * Besides, additional upudates must be carried out in case {@code goodb} is + * set to {@code true}. + *

    + *

    + * In all subsequent comments, the description of the state variables refer + * to their value after a call to {@link #update()}. In these comments, k is + * the current number of evaluations of matrix-vector products. + *

    + */ + private static class State { + /** The cubic root of {@link #MACH_PREC}. */ + static final double CBRT_MACH_PREC; + + /** The machine precision. */ + static final double MACH_PREC; + + /** Reference to the linear operator. */ + private final RealLinearOperator a; + + /** Reference to the right-hand side vector. */ + private final RealVector b; + + /** {@code true} if symmetry of matrix and conditioner must be checked. */ + private final boolean check; + + /** + * The value of the custom tolerance δ for the default stopping + * criterion. + */ + private final double delta; + + /** The value of beta[k+1]. */ + private double beta; + + /** The value of beta[1]. */ + private double beta1; + + /** The value of bstep[k-1]. */ + private double bstep; + + /** The estimate of the norm of P * rC[k]. */ + private double cgnorm; + + /** The value of dbar[k+1] = -beta[k+1] * c[k-1]. */ + private double dbar; + + /** + * The value of gamma[k] * zeta[k]. Was called {@code rhs1} in the + * initial code. + */ + private double gammaZeta; + + /** The value of gbar[k]. */ + private double gbar; + + /** The value of max(|alpha[1]|, gamma[1], ..., gamma[k-1]). */ + private double gmax; + + /** The value of min(|alpha[1]|, gamma[1], ..., gamma[k-1]). */ + private double gmin; + + /** Copy of the {@code goodb} parameter. */ + private final boolean goodb; + + /** {@code true} if the default convergence criterion is verified. */ + private boolean hasConverged; + + /** The estimate of the norm of P * rL[k-1]. */ + private double lqnorm; + + /** Reference to the preconditioner, M. */ + private final RealLinearOperator m; + + /** + * The value of (-eps[k+1] * zeta[k-1]). Was called {@code rhs2} in the + * initial code. + */ + private double minusEpsZeta; + + /** The value of M * b. */ + private final RealVector mb; + + /** The value of beta[k]. */ + private double oldb; + + /** The value of beta[k] * M^(-1) * P' * v[k]. */ + private RealVector r1; + + /** The value of beta[k+1] * M^(-1) * P' * v[k+1]. */ + private RealVector r2; + + /** + * The value of the updated, preconditioned residual P * r. This value is + * given by {@code min(}{@link #cgnorm}{@code , }{@link #lqnorm}{@code )}. + */ + private double rnorm; + + /** Copy of the {@code shift} parameter. */ + private final double shift; + + /** The value of s[1] * ... * s[k-1]. */ + private double snprod; + + /** + * An estimate of the square of the norm of A * V[k], based on Paige and + * Saunders (1975), equation (3.3). + */ + private double tnorm; + + /** + * The value of P' * wbar[k] or P' * (wbar[k] - s[1] * ... * s[k-1] * + * v[1]) if {@code goodb} is {@code true}. Was called {@code w} in the + * initial code. + */ + private RealVector wbar; + + /** + * A reference to the vector to be updated with the solution. Contains + * the value of xL[k-1] if {@code goodb} is {@code false}, (xL[k-1] - + * bstep[k-1] * v[1]) otherwise. + */ + private final RealVector xL; + + /** The value of beta[k+1] * P' * v[k+1]. */ + private RealVector y; + + /** The value of zeta[1]^2 + ... + zeta[k-1]^2. */ + private double ynorm2; + + /** The value of {@code b == 0} (exact floating-point equality). */ + private boolean bIsNull; + + static { + MACH_PREC = FastMath.ulp(1.); + CBRT_MACH_PREC = FastMath.cbrt(MACH_PREC); + } + + /** + * Creates and inits to k = 1 a new instance of this class. + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param goodb usually {@code false}, except if {@code x} is expected + * to contain a large multiple of {@code b} + * @param shift the amount to be subtracted to all diagonal elements of + * A + * @param delta the δ parameter for the default stopping criterion + * @param check {@code true} if self-adjointedness of both matrix and + * preconditioner should be checked + */ + State(final RealLinearOperator a, + final RealLinearOperator m, + final RealVector b, + final boolean goodb, + final double shift, + final double delta, + final boolean check) { + this.a = a; + this.m = m; + this.b = b; + this.xL = new ArrayRealVector(b.getDimension()); + this.goodb = goodb; + this.shift = shift; + this.mb = m == null ? b : m.operate(b); + this.hasConverged = false; + this.check = check; + this.delta = delta; + } + + /** + * Performs a symmetry check on the specified linear operator, and throws an + * exception in case this check fails. Given a linear operator L, and a + * vector x, this method checks that + * x' · L · y = y' · L · x + * (within a given accuracy), where y = L · x. + * + * @param l the linear operator L + * @param x the candidate vector x + * @param y the candidate vector y = L · x + * @param z the vector z = L · y + * @throws NonSelfAdjointOperatorException when the test fails + */ + private static void checkSymmetry(final RealLinearOperator l, + final RealVector x, final RealVector y, final RealVector z) + throws NonSelfAdjointOperatorException { + final double s = y.dotProduct(y); + final double t = x.dotProduct(z); + final double epsa = (s + MACH_PREC) * CBRT_MACH_PREC; + if (FastMath.abs(s - t) > epsa) { + final NonSelfAdjointOperatorException e; + e = new NonSelfAdjointOperatorException(); + final ExceptionContext context = e.getContext(); + context.setValue(SymmLQ.OPERATOR, l); + context.setValue(SymmLQ.VECTOR1, x); + context.setValue(SymmLQ.VECTOR2, y); + context.setValue(SymmLQ.THRESHOLD, Double.valueOf(epsa)); + throw e; + } + } + + /** + * Throws a new {@link NonPositiveDefiniteOperatorException} with + * appropriate context. + * + * @param l the offending linear operator + * @param v the offending vector + * @throws NonPositiveDefiniteOperatorException in any circumstances + */ + private static void throwNPDLOException(final RealLinearOperator l, + final RealVector v) throws NonPositiveDefiniteOperatorException { + final NonPositiveDefiniteOperatorException e; + e = new NonPositiveDefiniteOperatorException(); + final ExceptionContext context = e.getContext(); + context.setValue(OPERATOR, l); + context.setValue(VECTOR, v); + throw e; + } + + /** + * A clone of the BLAS {@code DAXPY} function, which carries out the + * operation y ← a · x + y. This is for internal use only: no + * dimension checks are provided. + * + * @param a the scalar by which {@code x} is to be multiplied + * @param x the vector to be added to {@code y} + * @param y the vector to be incremented + */ + private static void daxpy(final double a, final RealVector x, + final RealVector y) { + final int n = x.getDimension(); + for (int i = 0; i < n; i++) { + y.setEntry(i, a * x.getEntry(i) + y.getEntry(i)); + } + } + + /** + * A BLAS-like function, for the operation z ← a · x + b + * · y + z. This is for internal use only: no dimension checks are + * provided. + * + * @param a the scalar by which {@code x} is to be multiplied + * @param x the first vector to be added to {@code z} + * @param b the scalar by which {@code y} is to be multiplied + * @param y the second vector to be added to {@code z} + * @param z the vector to be incremented + */ + private static void daxpbypz(final double a, final RealVector x, + final double b, final RealVector y, final RealVector z) { + final int n = z.getDimension(); + for (int i = 0; i < n; i++) { + final double zi; + zi = a * x.getEntry(i) + b * y.getEntry(i) + z.getEntry(i); + z.setEntry(i, zi); + } + } + + /** + *

    + * Move to the CG point if it seems better. In this version of SYMMLQ, + * the convergence tests involve only cgnorm, so we're unlikely to stop + * at an LQ point, except if the iteration limit interferes. + *

    + *

    + * Additional upudates are also carried out in case {@code goodb} is set + * to {@code true}. + *

    + * + * @param x the vector to be updated with the refined value of xL + */ + void refineSolution(final RealVector x) { + final int n = this.xL.getDimension(); + if (lqnorm < cgnorm) { + if (!goodb) { + x.setSubVector(0, this.xL); + } else { + final double step = bstep / beta1; + for (int i = 0; i < n; i++) { + final double bi = mb.getEntry(i); + final double xi = this.xL.getEntry(i); + x.setEntry(i, xi + step * bi); + } + } + } else { + final double anorm = FastMath.sqrt(tnorm); + final double diag = gbar == 0. ? anorm * MACH_PREC : gbar; + final double zbar = gammaZeta / diag; + final double step = (bstep + snprod * zbar) / beta1; + // ynorm = FastMath.sqrt(ynorm2 + zbar * zbar); + if (!goodb) { + for (int i = 0; i < n; i++) { + final double xi = this.xL.getEntry(i); + final double wi = wbar.getEntry(i); + x.setEntry(i, xi + zbar * wi); + } + } else { + for (int i = 0; i < n; i++) { + final double xi = this.xL.getEntry(i); + final double wi = wbar.getEntry(i); + final double bi = mb.getEntry(i); + x.setEntry(i, xi + zbar * wi + step * bi); + } + } + } + } + + /** + * Performs the initial phase of the SYMMLQ algorithm. On return, the + * value of the state variables of {@code this} object correspond to k = + * 1. + */ + void init() { + this.xL.set(0.); + /* + * Set up y for the first Lanczos vector. y and beta1 will be zero + * if b = 0. + */ + this.r1 = this.b.copy(); + this.y = this.m == null ? this.b.copy() : this.m.operate(this.r1); + if ((this.m != null) && this.check) { + checkSymmetry(this.m, this.r1, this.y, this.m.operate(this.y)); + } + + this.beta1 = this.r1.dotProduct(this.y); + if (this.beta1 < 0.) { + throwNPDLOException(this.m, this.y); + } + if (this.beta1 == 0.) { + /* If b = 0 exactly, stop with x = 0. */ + this.bIsNull = true; + return; + } + this.bIsNull = false; + this.beta1 = FastMath.sqrt(this.beta1); + /* At this point + * r1 = b, + * y = M * b, + * beta1 = beta[1]. + */ + final RealVector v = this.y.mapMultiply(1. / this.beta1); + this.y = this.a.operate(v); + if (this.check) { + checkSymmetry(this.a, v, this.y, this.a.operate(this.y)); + } + /* + * Set up y for the second Lanczos vector. y and beta will be zero + * or very small if b is an eigenvector. + */ + daxpy(-this.shift, v, this.y); + final double alpha = v.dotProduct(this.y); + daxpy(-alpha / this.beta1, this.r1, this.y); + /* + * At this point + * alpha = alpha[1] + * y = beta[2] * M^(-1) * P' * v[2] + */ + /* Make sure r2 will be orthogonal to the first v. */ + final double vty = v.dotProduct(this.y); + final double vtv = v.dotProduct(v); + daxpy(-vty / vtv, v, this.y); + this.r2 = this.y.copy(); + if (this.m != null) { + this.y = this.m.operate(this.r2); + } + this.oldb = this.beta1; + this.beta = this.r2.dotProduct(this.y); + if (this.beta < 0.) { + throwNPDLOException(this.m, this.y); + } + this.beta = FastMath.sqrt(this.beta); + /* + * At this point + * oldb = beta[1] + * beta = beta[2] + * y = beta[2] * P' * v[2] + * r2 = beta[2] * M^(-1) * P' * v[2] + */ + this.cgnorm = this.beta1; + this.gbar = alpha; + this.dbar = this.beta; + this.gammaZeta = this.beta1; + this.minusEpsZeta = 0.; + this.bstep = 0.; + this.snprod = 1.; + this.tnorm = alpha * alpha + this.beta * this.beta; + this.ynorm2 = 0.; + this.gmax = FastMath.abs(alpha) + MACH_PREC; + this.gmin = this.gmax; + + if (this.goodb) { + this.wbar = new ArrayRealVector(this.a.getRowDimension()); + this.wbar.set(0.); + } else { + this.wbar = v; + } + updateNorms(); + } + + /** + * Performs the next iteration of the algorithm. The iteration count + * should be incremented prior to calling this method. On return, the + * value of the state variables of {@code this} object correspond to the + * current iteration count {@code k}. + */ + void update() { + final RealVector v = y.mapMultiply(1. / beta); + y = a.operate(v); + daxpbypz(-shift, v, -beta / oldb, r1, y); + final double alpha = v.dotProduct(y); + /* + * At this point + * v = P' * v[k], + * y = (A - shift * I) * P' * v[k] - beta[k] * M^(-1) * P' * v[k-1], + * alpha = v'[k] * P * (A - shift * I) * P' * v[k] + * - beta[k] * v[k]' * P * M^(-1) * P' * v[k-1] + * = v'[k] * P * (A - shift * I) * P' * v[k] + * - beta[k] * v[k]' * v[k-1] + * = alpha[k]. + */ + daxpy(-alpha / beta, r2, y); + /* + * At this point + * y = (A - shift * I) * P' * v[k] - alpha[k] * M^(-1) * P' * v[k] + * - beta[k] * M^(-1) * P' * v[k-1] + * = M^(-1) * P' * (P * (A - shift * I) * P' * v[k] -alpha[k] * v[k] + * - beta[k] * v[k-1]) + * = beta[k+1] * M^(-1) * P' * v[k+1], + * from Paige and Saunders (1975), equation (3.2). + * + * WATCH-IT: the two following lines work only because y is no longer + * updated up to the end of the present iteration, and is + * reinitialized at the beginning of the next iteration. + */ + r1 = r2; + r2 = y; + if (m != null) { + y = m.operate(r2); + } + oldb = beta; + beta = r2.dotProduct(y); + if (beta < 0.) { + throwNPDLOException(m, y); + } + beta = FastMath.sqrt(beta); + /* + * At this point + * r1 = beta[k] * M^(-1) * P' * v[k], + * r2 = beta[k+1] * M^(-1) * P' * v[k+1], + * y = beta[k+1] * P' * v[k+1], + * oldb = beta[k], + * beta = beta[k+1]. + */ + tnorm += alpha * alpha + oldb * oldb + beta * beta; + /* + * Compute the next plane rotation for Q. See Paige and Saunders + * (1975), equation (5.6), with + * gamma = gamma[k-1], + * c = c[k-1], + * s = s[k-1]. + */ + final double gamma = FastMath.sqrt(gbar * gbar + oldb * oldb); + final double c = gbar / gamma; + final double s = oldb / gamma; + /* + * The relations + * gbar[k] = s[k-1] * (-c[k-2] * beta[k]) - c[k-1] * alpha[k] + * = s[k-1] * dbar[k] - c[k-1] * alpha[k], + * delta[k] = c[k-1] * dbar[k] + s[k-1] * alpha[k], + * are not stated in Paige and Saunders (1975), but can be retrieved + * by expanding the (k, k-1) and (k, k) coefficients of the matrix in + * equation (5.5). + */ + final double deltak = c * dbar + s * alpha; + gbar = s * dbar - c * alpha; + final double eps = s * beta; + dbar = -c * beta; + final double zeta = gammaZeta / gamma; + /* + * At this point + * gbar = gbar[k] + * deltak = delta[k] + * eps = eps[k+1] + * dbar = dbar[k+1] + * zeta = zeta[k-1] + */ + final double zetaC = zeta * c; + final double zetaS = zeta * s; + final int n = xL.getDimension(); + for (int i = 0; i < n; i++) { + final double xi = xL.getEntry(i); + final double vi = v.getEntry(i); + final double wi = wbar.getEntry(i); + xL.setEntry(i, xi + wi * zetaC + vi * zetaS); + wbar.setEntry(i, wi * s - vi * c); + } + /* + * At this point + * x = xL[k-1], + * ptwbar = P' wbar[k], + * see Paige and Saunders (1975), equations (5.9) and (5.10). + */ + bstep += snprod * c * zeta; + snprod *= s; + gmax = FastMath.max(gmax, gamma); + gmin = FastMath.min(gmin, gamma); + ynorm2 += zeta * zeta; + gammaZeta = minusEpsZeta - deltak * zeta; + minusEpsZeta = -eps * zeta; + /* + * At this point + * snprod = s[1] * ... * s[k-1], + * gmax = max(|alpha[1]|, gamma[1], ..., gamma[k-1]), + * gmin = min(|alpha[1]|, gamma[1], ..., gamma[k-1]), + * ynorm2 = zeta[1]^2 + ... + zeta[k-1]^2, + * gammaZeta = gamma[k] * zeta[k], + * minusEpsZeta = -eps[k+1] * zeta[k-1]. + * The relation for gammaZeta can be retrieved from Paige and + * Saunders (1975), equation (5.4a), last line of the vector + * gbar[k] * zbar[k] = -eps[k] * zeta[k-2] - delta[k] * zeta[k-1]. + */ + updateNorms(); + } + + /** + * Computes the norms of the residuals, and checks for convergence. + * Updates {@link #lqnorm} and {@link #cgnorm}. + */ + private void updateNorms() { + final double anorm = FastMath.sqrt(tnorm); + final double ynorm = FastMath.sqrt(ynorm2); + final double epsa = anorm * MACH_PREC; + final double epsx = anorm * ynorm * MACH_PREC; + final double epsr = anorm * ynorm * delta; + final double diag = gbar == 0. ? epsa : gbar; + lqnorm = FastMath.sqrt(gammaZeta * gammaZeta + + minusEpsZeta * minusEpsZeta); + final double qrnorm = snprod * beta1; + cgnorm = qrnorm * beta / FastMath.abs(diag); + + /* + * Estimate cond(A). In this version we look at the diagonals of L + * in the factorization of the tridiagonal matrix, T = L * Q. + * Sometimes, T[k] can be misleadingly ill-conditioned when T[k+1] + * is not, so we must be careful not to overestimate acond. + */ + final double acond; + if (lqnorm <= cgnorm) { + acond = gmax / gmin; + } else { + acond = gmax / FastMath.min(gmin, FastMath.abs(diag)); + } + if (acond * MACH_PREC >= 0.1) { + throw new IllConditionedOperatorException(acond); + } + if (beta1 <= epsx) { + /* + * x has converged to an eigenvector of A corresponding to the + * eigenvalue shift. + */ + throw new SingularOperatorException(); + } + rnorm = FastMath.min(cgnorm, lqnorm); + hasConverged = (cgnorm <= epsx) || (cgnorm <= epsr); + } + + /** + * Returns {@code true} if the default stopping criterion is fulfilled. + * + * @return {@code true} if convergence of the iterations has occurred + */ + boolean hasConverged() { + return hasConverged; + } + + /** + * Returns {@code true} if the right-hand side vector is zero exactly. + * + * @return the boolean value of {@code b == 0} + */ + boolean bEqualsNullVector() { + return bIsNull; + } + + /** + * Returns {@code true} if {@code beta} is essentially zero. This method + * is used to check for early stop of the iterations. + * + * @return {@code true} if {@code beta < }{@link #MACH_PREC} + */ + boolean betaEqualsZero() { + return beta < MACH_PREC; + } + + /** + * Returns the norm of the updated, preconditioned residual. + * + * @return the norm of the residual, ||P * r|| + */ + double getNormOfResidual() { + return rnorm; + } + } + + /** Key for the exception context. */ + private static final String OPERATOR = "operator"; + + /** Key for the exception context. */ + private static final String THRESHOLD = "threshold"; + + /** Key for the exception context. */ + private static final String VECTOR = "vector"; + + /** Key for the exception context. */ + private static final String VECTOR1 = "vector1"; + + /** Key for the exception context. */ + private static final String VECTOR2 = "vector2"; + + /** {@code true} if symmetry of matrix and conditioner must be checked. */ + private final boolean check; + + /** + * The value of the custom tolerance δ for the default stopping + * criterion. + */ + private final double delta; + + /** + * Creates a new instance of this class, with default + * stopping criterion. Note that setting {@code check} to {@code true} + * entails an extra matrix-vector product in the initial phase. + * + * @param maxIterations the maximum number of iterations + * @param delta the δ parameter for the default stopping criterion + * @param check {@code true} if self-adjointedness of both matrix and + * preconditioner should be checked + */ + public SymmLQ(final int maxIterations, final double delta, + final boolean check) { + super(maxIterations); + this.delta = delta; + this.check = check; + } + + /** + * Creates a new instance of this class, with default + * stopping criterion and custom iteration manager. Note that setting + * {@code check} to {@code true} entails an extra matrix-vector product in + * the initial phase. + * + * @param manager the custom iteration manager + * @param delta the δ parameter for the default stopping criterion + * @param check {@code true} if self-adjointedness of both matrix and + * preconditioner should be checked + */ + public SymmLQ(final IterationManager manager, final double delta, + final boolean check) { + super(manager); + this.delta = delta; + this.check = check; + } + + /** + * Returns {@code true} if symmetry of the matrix, and symmetry as well as + * positive definiteness of the preconditioner should be checked. + * + * @return {@code true} if the tests are to be performed + */ + public final boolean getCheck() { + return check; + } + + /** + * {@inheritDoc} + * + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} or {@code m} is not self-adjoint + * @throws NonPositiveDefiniteOperatorException if {@code m} is not + * positive definite + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solve(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b) throws + NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, MaxCountExceededException, + NonSelfAdjointOperatorException, NonPositiveDefiniteOperatorException, + IllConditionedOperatorException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + return solveInPlace(a, m, b, x, false, 0.); + } + + /** + * Returns an estimate of the solution to the linear system (A - shift + * · I) · x = b. + *

    + * If the solution x is expected to contain a large multiple of {@code b} + * (as in Rayleigh-quotient iteration), then better precision may be + * achieved with {@code goodb} set to {@code true}; this however requires an + * extra call to the preconditioner. + *

    + *

    + * {@code shift} should be zero if the system A · x = b is to be + * solved. Otherwise, it could be an approximation to an eigenvalue of A, + * such as the Rayleigh quotient bT · A · b / + * (bT · b) corresponding to the vector b. If b is + * sufficiently like an eigenvector corresponding to an eigenvalue near + * shift, then the computed x may have very large components. When + * normalized, x may be closer to an eigenvector than b. + *

    + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param goodb usually {@code false}, except if {@code x} is expected to + * contain a large multiple of {@code b} + * @param shift the amount to be subtracted to all diagonal elements of A + * @return a reference to {@code x} (shallow copy) + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not square + * @throws DimensionMismatchException if {@code m} or {@code b} have dimensions + * inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} or {@code m} is not self-adjoint + * @throws NonPositiveDefiniteOperatorException if {@code m} is not + * positive definite + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + public RealVector solve(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, final boolean goodb, + final double shift) throws NullArgumentException, + NonSquareOperatorException, DimensionMismatchException, + MaxCountExceededException, NonSelfAdjointOperatorException, + NonPositiveDefiniteOperatorException, IllConditionedOperatorException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + return solveInPlace(a, m, b, x, goodb, shift); + } + + /** + * {@inheritDoc} + * + * @param x not meaningful in this implementation; should not be considered + * as an initial guess (more) + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} or {@code m} is not self-adjoint + * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive + * definite + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solve(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, final RealVector x) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, NonSelfAdjointOperatorException, + NonPositiveDefiniteOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + MathUtils.checkNotNull(x); + return solveInPlace(a, m, b, x.copy(), false, 0.); + } + + /** + * {@inheritDoc} + * + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} is not self-adjoint + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solve(final RealLinearOperator a, final RealVector b) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, NonSelfAdjointOperatorException, + IllConditionedOperatorException, MaxCountExceededException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + x.set(0.); + return solveInPlace(a, null, b, x, false, 0.); + } + + /** + * Returns the solution to the system (A - shift · I) · x = b. + *

    + * If the solution x is expected to contain a large multiple of {@code b} + * (as in Rayleigh-quotient iteration), then better precision may be + * achieved with {@code goodb} set to {@code true}. + *

    + *

    + * {@code shift} should be zero if the system A · x = b is to be + * solved. Otherwise, it could be an approximation to an eigenvalue of A, + * such as the Rayleigh quotient bT · A · b / + * (bT · b) corresponding to the vector b. If b is + * sufficiently like an eigenvector corresponding to an eigenvalue near + * shift, then the computed x may have very large components. When + * normalized, x may be closer to an eigenvector than b. + *

    + * + * @param a the linear operator A of the system + * @param b the right-hand side vector + * @param goodb usually {@code false}, except if {@code x} is expected to + * contain a large multiple of {@code b} + * @param shift the amount to be subtracted to all diagonal elements of A + * @return a reference to {@code x} + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} is not square + * @throws DimensionMismatchException if {@code b} has dimensions + * inconsistent with {@code a} + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} is not self-adjoint + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + public RealVector solve(final RealLinearOperator a, final RealVector b, + final boolean goodb, final double shift) throws NullArgumentException, + NonSquareOperatorException, DimensionMismatchException, + NonSelfAdjointOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + MathUtils.checkNotNull(a); + final RealVector x = new ArrayRealVector(a.getColumnDimension()); + return solveInPlace(a, null, b, x, goodb, shift); + } + + /** + * {@inheritDoc} + * + * @param x not meaningful in this implementation; should not be considered + * as an initial guess (more) + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} is not self-adjoint + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solve(final RealLinearOperator a, final RealVector b, + final RealVector x) throws NullArgumentException, + NonSquareOperatorException, DimensionMismatchException, + NonSelfAdjointOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + MathUtils.checkNotNull(x); + return solveInPlace(a, null, b, x.copy(), false, 0.); + } + + /** + * {@inheritDoc} + * + * @param x the vector to be updated with the solution; {@code x} should + * not be considered as an initial guess (more) + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} or {@code m} is not self-adjoint + * @throws NonPositiveDefiniteOperatorException if {@code m} is not + * positive definite + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solveInPlace(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, final RealVector x) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, NonSelfAdjointOperatorException, + NonPositiveDefiniteOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + return solveInPlace(a, m, b, x, false, 0.); + } + + /** + * Returns an estimate of the solution to the linear system (A - shift + * · I) · x = b. The solution is computed in-place. + *

    + * If the solution x is expected to contain a large multiple of {@code b} + * (as in Rayleigh-quotient iteration), then better precision may be + * achieved with {@code goodb} set to {@code true}; this however requires an + * extra call to the preconditioner. + *

    + *

    + * {@code shift} should be zero if the system A · x = b is to be + * solved. Otherwise, it could be an approximation to an eigenvalue of A, + * such as the Rayleigh quotient bT · A · b / + * (bT · b) corresponding to the vector b. If b is + * sufficiently like an eigenvector corresponding to an eigenvalue near + * shift, then the computed x may have very large components. When + * normalized, x may be closer to an eigenvector than b. + *

    + * + * @param a the linear operator A of the system + * @param m the preconditioner, M (can be {@code null}) + * @param b the right-hand side vector + * @param x the vector to be updated with the solution; {@code x} should + * not be considered as an initial guess (more) + * @param goodb usually {@code false}, except if {@code x} is expected to + * contain a large multiple of {@code b} + * @param shift the amount to be subtracted to all diagonal elements of A + * @return a reference to {@code x} (shallow copy). + * @throws NullArgumentException if one of the parameters is {@code null} + * @throws NonSquareOperatorException if {@code a} or {@code m} is not square + * @throws DimensionMismatchException if {@code m}, {@code b} or {@code x} + * have dimensions inconsistent with {@code a}. + * @throws MaxCountExceededException at exhaustion of the iteration count, + * unless a custom + * {@link Incrementor.MaxCountExceededCallback callback} + * has been set at construction of the {@link IterationManager} + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} or {@code m} is not self-adjoint + * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive + * definite + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + public RealVector solveInPlace(final RealLinearOperator a, + final RealLinearOperator m, final RealVector b, + final RealVector x, final boolean goodb, final double shift) + throws NullArgumentException, NonSquareOperatorException, + DimensionMismatchException, NonSelfAdjointOperatorException, + NonPositiveDefiniteOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + checkParameters(a, m, b, x); + + final IterationManager manager = getIterationManager(); + /* Initialization counts as an iteration. */ + manager.resetIterationCount(); + manager.incrementIterationCount(); + + final State state; + state = new State(a, m, b, goodb, shift, delta, check); + state.init(); + state.refineSolution(x); + IterativeLinearSolverEvent event; + event = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), + x, + b, + state.getNormOfResidual()); + if (state.bEqualsNullVector()) { + /* If b = 0 exactly, stop with x = 0. */ + manager.fireTerminationEvent(event); + return x; + } + /* Cause termination if beta is essentially zero. */ + final boolean earlyStop; + earlyStop = state.betaEqualsZero() || state.hasConverged(); + manager.fireInitializationEvent(event); + if (!earlyStop) { + do { + manager.incrementIterationCount(); + event = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), + x, + b, + state.getNormOfResidual()); + manager.fireIterationStartedEvent(event); + state.update(); + state.refineSolution(x); + event = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), + x, + b, + state.getNormOfResidual()); + manager.fireIterationPerformedEvent(event); + } while (!state.hasConverged()); + } + event = new DefaultIterativeLinearSolverEvent(this, + manager.getIterations(), + x, + b, + state.getNormOfResidual()); + manager.fireTerminationEvent(event); + return x; + } + + /** + * {@inheritDoc} + * + * @param x the vector to be updated with the solution; {@code x} should + * not be considered as an initial guess (more) + * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is + * {@code true}, and {@code a} is not self-adjoint + * @throws IllConditionedOperatorException if {@code a} is ill-conditioned + */ + @Override + public RealVector solveInPlace(final RealLinearOperator a, + final RealVector b, final RealVector x) throws NullArgumentException, + NonSquareOperatorException, DimensionMismatchException, + NonSelfAdjointOperatorException, IllConditionedOperatorException, + MaxCountExceededException { + return solveInPlace(a, null, b, x, false, 0.); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/TriDiagonalTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/TriDiagonalTransformer.java new file mode 100644 index 000000000..209d8d52e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/TriDiagonalTransformer.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.linear; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Class transforming a symmetrical matrix to tridiagonal shape. + *

    A symmetrical m × m matrix A can be written as the product of three matrices: + * A = Q × T × QT with Q an orthogonal matrix and T a symmetrical + * tridiagonal matrix. Both Q and T are m × m matrices.

    + *

    This implementation only uses the upper part of the matrix, the part below the + * diagonal is not accessed at all.

    + *

    Transformation to tridiagonal shape is often not a goal by itself, but it is + * an intermediate step in more general decomposition algorithms like {@link + * EigenDecomposition eigen decomposition}. This class is therefore intended for internal + * use by the library and is not public. As a consequence of this explicitly limited scope, + * many methods directly returns references to internal arrays, not copies.

    + * @since 2.0 + */ +class TriDiagonalTransformer { + /** Householder vectors. */ + private final double householderVectors[][]; + /** Main diagonal. */ + private final double[] main; + /** Secondary diagonal. */ + private final double[] secondary; + /** Cached value of Q. */ + private RealMatrix cachedQ; + /** Cached value of Qt. */ + private RealMatrix cachedQt; + /** Cached value of T. */ + private RealMatrix cachedT; + + /** + * Build the transformation to tridiagonal shape of a symmetrical matrix. + *

    The specified matrix is assumed to be symmetrical without any check. + * Only the upper triangular part of the matrix is used.

    + * + * @param matrix Symmetrical matrix to transform. + * @throws NonSquareMatrixException if the matrix is not square. + */ + TriDiagonalTransformer(RealMatrix matrix) { + if (!matrix.isSquare()) { + throw new NonSquareMatrixException(matrix.getRowDimension(), + matrix.getColumnDimension()); + } + + final int m = matrix.getRowDimension(); + householderVectors = matrix.getData(); + main = new double[m]; + secondary = new double[m - 1]; + cachedQ = null; + cachedQt = null; + cachedT = null; + + // transform matrix + transform(); + } + + /** + * Returns the matrix Q of the transform. + *

    Q is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the Q matrix + */ + public RealMatrix getQ() { + if (cachedQ == null) { + cachedQ = getQT().transpose(); + } + return cachedQ; + } + + /** + * Returns the transpose of the matrix Q of the transform. + *

    Q is an orthogonal matrix, i.e. its transpose is also its inverse.

    + * @return the Q matrix + */ + public RealMatrix getQT() { + if (cachedQt == null) { + final int m = householderVectors.length; + double[][] qta = new double[m][m]; + + // build up first part of the matrix by applying Householder transforms + for (int k = m - 1; k >= 1; --k) { + final double[] hK = householderVectors[k - 1]; + qta[k][k] = 1; + if (hK[k] != 0.0) { + final double inv = 1.0 / (secondary[k - 1] * hK[k]); + double beta = 1.0 / secondary[k - 1]; + qta[k][k] = 1 + beta * hK[k]; + for (int i = k + 1; i < m; ++i) { + qta[k][i] = beta * hK[i]; + } + for (int j = k + 1; j < m; ++j) { + beta = 0; + for (int i = k + 1; i < m; ++i) { + beta += qta[j][i] * hK[i]; + } + beta *= inv; + qta[j][k] = beta * hK[k]; + for (int i = k + 1; i < m; ++i) { + qta[j][i] += beta * hK[i]; + } + } + } + } + qta[0][0] = 1; + cachedQt = MatrixUtils.createRealMatrix(qta); + } + + // return the cached matrix + return cachedQt; + } + + /** + * Returns the tridiagonal matrix T of the transform. + * @return the T matrix + */ + public RealMatrix getT() { + if (cachedT == null) { + final int m = main.length; + double[][] ta = new double[m][m]; + for (int i = 0; i < m; ++i) { + ta[i][i] = main[i]; + if (i > 0) { + ta[i][i - 1] = secondary[i - 1]; + } + if (i < main.length - 1) { + ta[i][i + 1] = secondary[i]; + } + } + cachedT = MatrixUtils.createRealMatrix(ta); + } + + // return the cached matrix + return cachedT; + } + + /** + * Get the Householder vectors of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the main diagonal elements of the B matrix + */ + double[][] getHouseholderVectorsRef() { + return householderVectors; + } + + /** + * Get the main diagonal elements of the matrix T of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the main diagonal elements of the T matrix + */ + double[] getMainDiagonalRef() { + return main; + } + + /** + * Get the secondary diagonal elements of the matrix T of the transform. + *

    Note that since this class is only intended for internal use, + * it returns directly a reference to its internal arrays, not a copy.

    + * @return the secondary diagonal elements of the T matrix + */ + double[] getSecondaryDiagonalRef() { + return secondary; + } + + /** + * Transform original matrix to tridiagonal form. + *

    Transformation is done using Householder transforms.

    + */ + private void transform() { + final int m = householderVectors.length; + final double[] z = new double[m]; + for (int k = 0; k < m - 1; k++) { + + //zero-out a row and a column simultaneously + final double[] hK = householderVectors[k]; + main[k] = hK[k]; + double xNormSqr = 0; + for (int j = k + 1; j < m; ++j) { + final double c = hK[j]; + xNormSqr += c * c; + } + final double a = (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr); + secondary[k] = a; + if (a != 0.0) { + // apply Householder transform from left and right simultaneously + + hK[k + 1] -= a; + final double beta = -1 / (a * hK[k + 1]); + + // compute a = beta A v, where v is the Householder vector + // this loop is written in such a way + // 1) only the upper triangular part of the matrix is accessed + // 2) access is cache-friendly for a matrix stored in rows + Arrays.fill(z, k + 1, m, 0); + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + final double hKI = hK[i]; + double zI = hI[i] * hKI; + for (int j = i + 1; j < m; ++j) { + final double hIJ = hI[j]; + zI += hIJ * hK[j]; + z[j] += hIJ * hKI; + } + z[i] = beta * (z[i] + zI); + } + + // compute gamma = beta vT z / 2 + double gamma = 0; + for (int i = k + 1; i < m; ++i) { + gamma += z[i] * hK[i]; + } + gamma *= beta / 2; + + // compute z = z - gamma v + for (int i = k + 1; i < m; ++i) { + z[i] -= gamma * hK[i]; + } + + // update matrix: A = A - v zT - z vT + // only the upper triangular part of the matrix is updated + for (int i = k + 1; i < m; ++i) { + final double[] hI = householderVectors[i]; + for (int j = i; j < m; ++j) { + hI[j] -= hK[i] * z[j] + z[i] * hK[j]; + } + } + } + } + main[m - 1] = householderVectors[m - 1][m - 1]; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/package-info.java new file mode 100644 index 000000000..bf050f4fd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/linear/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Linear algebra support. + */ +package com.fr.third.org.apache.commons.math3.linear; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/CentroidCluster.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/CentroidCluster.java new file mode 100644 index 000000000..66e026f41 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/CentroidCluster.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering; + +/** + * A Cluster used by centroid-based clustering algorithms. + *

    + * Defines additionally a cluster center which may not necessarily be a member + * of the original data set. + * + * @param the type of points that can be clustered + * @since 3.2 + */ +public class CentroidCluster extends Cluster { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -3075288519071812288L; + + /** Center of the cluster. */ + private final Clusterable center; + + /** + * Build a cluster centered at a specified point. + * @param center the point which is to be the center of this cluster + */ + public CentroidCluster(final Clusterable center) { + super(); + this.center = center; + } + + /** + * Get the point chosen to be the center of this cluster. + * @return chosen cluster center + */ + public Clusterable getCenter() { + return center; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Cluster.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Cluster.java new file mode 100644 index 000000000..d5afa86f7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Cluster.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Cluster holding a set of {@link Clusterable} points. + * @param the type of points that can be clustered + * @since 3.2 + */ +public class Cluster implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -3442297081515880464L; + + /** The points contained in this cluster. */ + private final List points; + + /** + * Build a cluster centered at a specified point. + */ + public Cluster() { + points = new ArrayList(); + } + + /** + * 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 getPoints() { + return points; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterable.java new file mode 100644 index 000000000..04768a6ee --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterable.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering; + +/** + * Interface for n-dimensional points that can be clustered together. + * @since 3.2 + */ +public interface Clusterable { + + /** + * Gets the n-dimensional point. + * + * @return the point array + */ + double[] getPoint(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterer.java new file mode 100644 index 000000000..f76f4f423 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/Clusterer.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Base class for clustering algorithms. + * + * @param the type of points that can be clustered + * @since 3.2 + */ +public abstract class Clusterer { + + /** The distance measure to use. */ + private DistanceMeasure measure; + + /** + * Build a new clusterer with the given {@link DistanceMeasure}. + * + * @param measure the distance measure to use + */ + protected Clusterer(final DistanceMeasure measure) { + this.measure = measure; + } + + /** + * Perform a cluster analysis on the given set of {@link Clusterable} instances. + * + * @param points the set of {@link Clusterable} instances + * @return a {@link List} of clusters + * @throws MathIllegalArgumentException if points are null or the number of + * data points is not compatible with this clusterer + * @throws ConvergenceException if the algorithm has not yet converged after + * the maximum number of iterations has been exceeded + */ + public abstract List> cluster(Collection points) + throws MathIllegalArgumentException, ConvergenceException; + + /** + * Returns the {@link DistanceMeasure} instance used by this clusterer. + * + * @return the distance measure + */ + public DistanceMeasure getDistanceMeasure() { + return measure; + } + + /** + * Calculates the distance between two {@link Clusterable} instances + * with the configured {@link DistanceMeasure}. + * + * @param p1 the first clusterable + * @param p2 the second clusterable + * @return the distance between the two clusterables + */ + protected double distance(final Clusterable p1, final Clusterable p2) { + return measure.compute(p1.getPoint(), p2.getPoint()); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java new file mode 100644 index 000000000..42f5a40c0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.ml.distance.EuclideanDistance; + +/** + * DBSCAN (density-based spatial clustering of applications with noise) algorithm. + *

    + * The DBSCAN algorithm forms clusters based on the idea of density connectivity, i.e. + * a point p is density connected to another point q, if there exists a chain of + * points pi, with i = 1 .. n and p1 = p and pn = q, + * such that each pair <pi, pi+1> is directly density-reachable. + * A point q is directly density-reachable from point p if it is in the ε-neighborhood + * of this point. + *

    + * Any point that is not density-reachable from a formed cluster is treated as noise, and + * will thus not be present in the result. + *

    + * The algorithm requires two parameters: + *

      + *
    • eps: the distance that defines the ε-neighborhood of a point + *
    • minPoints: the minimum number of density-connected points required to form a cluster + *
    + * + * @param type of the points to cluster + * @see DBSCAN (wikipedia) + * @see + * A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise + * @since 3.2 + */ +public class DBSCANClusterer extends Clusterer { + + /** Maximum radius of the neighborhood to be considered. */ + private final double eps; + + /** Minimum number of points needed for a cluster. */ + private final int minPts; + + /** Status of a point during the clustering process. */ + private enum PointStatus { + /** The point has is considered to be noise. */ + NOISE, + /** The point is already part of a cluster. */ + PART_OF_CLUSTER + } + + /** + * Creates a new instance of a DBSCANClusterer. + *

    + * The euclidean distance will be used as default distance measure. + * + * @param eps maximum radius of the neighborhood to be considered + * @param minPts minimum number of points needed for a cluster + * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0} + */ + public DBSCANClusterer(final double eps, final int minPts) + throws NotPositiveException { + this(eps, minPts, new EuclideanDistance()); + } + + /** + * Creates a new instance of a DBSCANClusterer. + * + * @param eps maximum radius of the neighborhood to be considered + * @param minPts minimum number of points needed for a cluster + * @param measure the distance measure to use + * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0} + */ + public DBSCANClusterer(final double eps, final int minPts, final DistanceMeasure measure) + throws NotPositiveException { + super(measure); + + if (eps < 0.0d) { + throw new NotPositiveException(eps); + } + if (minPts < 0) { + throw new NotPositiveException(minPts); + } + this.eps = eps; + this.minPts = minPts; + } + + /** + * Returns the maximum radius of the neighborhood to be considered. + * @return maximum radius of the neighborhood + */ + public double getEps() { + return eps; + } + + /** + * Returns the minimum number of points needed for a cluster. + * @return minimum number of points needed for a cluster + */ + public int getMinPts() { + return minPts; + } + + /** + * Performs DBSCAN cluster analysis. + * + * @param points the points to cluster + * @return the list of clusters + * @throws NullArgumentException if the data points are null + */ + @Override + public List> cluster(final Collection points) throws NullArgumentException { + + // sanity checks + MathUtils.checkNotNull(points); + + final List> clusters = new ArrayList>(); + final Map visited = new HashMap(); + + for (final T point : points) { + if (visited.get(point) != null) { + continue; + } + final List neighbors = getNeighbors(point, points); + if (neighbors.size() >= minPts) { + // DBSCAN does not care about center points + final Cluster cluster = new Cluster(); + clusters.add(expandCluster(cluster, point, neighbors, points, visited)); + } else { + visited.put(point, PointStatus.NOISE); + } + } + + return clusters; + } + + /** + * Expands the cluster to include density-reachable items. + * + * @param cluster Cluster to expand + * @param point Point to add to cluster + * @param neighbors List of neighbors + * @param points the data set + * @param visited the set of already visited points + * @return the expanded cluster + */ + private Cluster expandCluster(final Cluster cluster, + final T point, + final List neighbors, + final Collection points, + final Map visited) { + cluster.addPoint(point); + visited.put(point, PointStatus.PART_OF_CLUSTER); + + List seeds = new ArrayList(neighbors); + int index = 0; + while (index < seeds.size()) { + final T current = seeds.get(index); + PointStatus pStatus = visited.get(current); + // only check non-visited points + if (pStatus == null) { + final List currentNeighbors = getNeighbors(current, points); + if (currentNeighbors.size() >= minPts) { + seeds = merge(seeds, currentNeighbors); + } + } + + if (pStatus != PointStatus.PART_OF_CLUSTER) { + visited.put(current, PointStatus.PART_OF_CLUSTER); + cluster.addPoint(current); + } + + index++; + } + return cluster; + } + + /** + * Returns a list of density-reachable neighbors of a {@code point}. + * + * @param point the point to look for + * @param points possible neighbors + * @return the List of neighbors + */ + private List getNeighbors(final T point, final Collection points) { + final List neighbors = new ArrayList(); + for (final T neighbor : points) { + if (point != neighbor && distance(neighbor, point) <= eps) { + neighbors.add(neighbor); + } + } + return neighbors; + } + + /** + * Merges two lists together. + * + * @param one first list + * @param two second list + * @return merged lists + */ + private List merge(final List one, final List two) { + final Set oneSet = new HashSet(one); + for (T item : two) { + if (!oneSet.contains(item)) { + one.add(item); + } + } + return one; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DoublePoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DoublePoint.java new file mode 100644 index 000000000..eec3e7f67 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/DoublePoint.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * A simple implementation of {@link Clusterable} for points with double coordinates. + * @since 3.2 + */ +public class DoublePoint implements Clusterable, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 3946024775784901369L; + + /** Point coordinates. */ + private final double[] point; + + /** + * Build an instance wrapping an double array. + *

    + * The wrapped array is referenced, it is not copied. + * + * @param point the n-dimensional point in double space + */ + public DoublePoint(final double[] point) { + this.point = point; + } + + /** + * Build an instance wrapping an integer array. + *

    + * The wrapped array is copied to an internal double array. + * + * @param point the n-dimensional point in integer space + */ + public DoublePoint(final int[] point) { + this.point = new double[point.length]; + for ( int i = 0; i < point.length; i++) { + this.point[i] = point[i]; + } + } + + /** {@inheritDoc} */ + public double[] getPoint() { + return point; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object other) { + if (!(other instanceof DoublePoint)) { + return false; + } + return Arrays.equals(point, ((DoublePoint) other).point); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(point); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return Arrays.toString(point); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java new file mode 100644 index 000000000..592cf36b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java @@ -0,0 +1,426 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.random.JDKRandomGenerator; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.ml.distance.EuclideanDistance; + +/** + * Fuzzy K-Means clustering algorithm. + *

    + * The Fuzzy K-Means algorithm is a variation of the classical K-Means algorithm, with the + * major difference that a single data point is not uniquely assigned to a single cluster. + * Instead, each point i has a set of weights uij which indicate the degree of membership + * to the cluster j. + *

    + * The algorithm then tries to minimize the objective function: + *

    + * J = ∑i=1..Ck=1..N uikmdik2
    + * 
    + * with dik being the distance between data point i and the cluster center k. + *

    + * The algorithm requires two parameters: + *

      + *
    • k: the number of clusters + *
    • fuzziness: determines the level of cluster fuzziness, larger values lead to fuzzier clusters + *
    + * Additional, optional parameters: + *
      + *
    • maxIterations: the maximum number of iterations + *
    • epsilon: the convergence criteria, default is 1e-3 + *
    + *

    + * The fuzzy variant of the K-Means algorithm is more robust with regard to the selection + * of the initial cluster centers. + * + * @param type of the points to cluster + * @since 3.3 + */ +public class FuzzyKMeansClusterer extends Clusterer { + + /** The default value for the convergence criteria. */ + private static final double DEFAULT_EPSILON = 1e-3; + + /** The number of clusters. */ + private final int k; + + /** The maximum number of iterations. */ + private final int maxIterations; + + /** The fuzziness factor. */ + private final double fuzziness; + + /** The convergence criteria. */ + private final double epsilon; + + /** Random generator for choosing initial centers. */ + private final RandomGenerator random; + + /** The membership matrix. */ + private double[][] membershipMatrix; + + /** The list of points used in the last call to {@link #cluster(Collection)}. */ + private List points; + + /** The list of clusters resulting from the last call to {@link #cluster(Collection)}. */ + private List> clusters; + + /** + * Creates a new instance of a FuzzyKMeansClusterer. + *

    + * The euclidean distance will be used as default distance measure. + * + * @param k the number of clusters to split the data into + * @param fuzziness the fuzziness factor, must be > 1.0 + * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0} + */ + public FuzzyKMeansClusterer(final int k, final double fuzziness) throws NumberIsTooSmallException { + this(k, fuzziness, -1, new EuclideanDistance()); + } + + /** + * Creates a new instance of a FuzzyKMeansClusterer. + * + * @param k the number of clusters to split the data into + * @param fuzziness the fuzziness factor, must be > 1.0 + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + * @param measure the distance measure to use + * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0} + */ + public FuzzyKMeansClusterer(final int k, final double fuzziness, + final int maxIterations, final DistanceMeasure measure) + throws NumberIsTooSmallException { + this(k, fuzziness, maxIterations, measure, DEFAULT_EPSILON, new JDKRandomGenerator()); + } + + /** + * Creates a new instance of a FuzzyKMeansClusterer. + * + * @param k the number of clusters to split the data into + * @param fuzziness the fuzziness factor, must be > 1.0 + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + * @param measure the distance measure to use + * @param epsilon the convergence criteria (default is 1e-3) + * @param random random generator to use for choosing initial centers + * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0} + */ + public FuzzyKMeansClusterer(final int k, final double fuzziness, + final int maxIterations, final DistanceMeasure measure, + final double epsilon, final RandomGenerator random) + throws NumberIsTooSmallException { + + super(measure); + + if (fuzziness <= 1.0d) { + throw new NumberIsTooSmallException(fuzziness, 1.0, false); + } + this.k = k; + this.fuzziness = fuzziness; + this.maxIterations = maxIterations; + this.epsilon = epsilon; + this.random = random; + + this.membershipMatrix = null; + this.points = null; + this.clusters = null; + } + + /** + * Return the number of clusters this instance will use. + * @return the number of clusters + */ + public int getK() { + return k; + } + + /** + * Returns the fuzziness factor used by this instance. + * @return the fuzziness factor + */ + public double getFuzziness() { + return fuzziness; + } + + /** + * Returns the maximum number of iterations this instance will use. + * @return the maximum number of iterations, or -1 if no maximum is set + */ + public int getMaxIterations() { + return maxIterations; + } + + /** + * Returns the convergence criteria used by this instance. + * @return the convergence criteria + */ + public double getEpsilon() { + return epsilon; + } + + /** + * Returns the random generator this instance will use. + * @return the random generator + */ + public RandomGenerator getRandomGenerator() { + return random; + } + + /** + * Returns the {@code nxk} membership matrix, where {@code n} is the number + * of data points and {@code k} the number of clusters. + *

    + * The element Ui,j represents the membership value for data point {@code i} + * to cluster {@code j}. + * + * @return the membership matrix + * @throws MathIllegalStateException if {@link #cluster(Collection)} has not been called before + */ + public RealMatrix getMembershipMatrix() { + if (membershipMatrix == null) { + throw new MathIllegalStateException(); + } + return MatrixUtils.createRealMatrix(membershipMatrix); + } + + /** + * Returns an unmodifiable list of the data points used in the last + * call to {@link #cluster(Collection)}. + * @return the list of data points, or {@code null} if {@link #cluster(Collection)} has + * not been called before. + */ + public List getDataPoints() { + return points; + } + + /** + * Returns the list of clusters resulting from the last call to {@link #cluster(Collection)}. + * @return the list of clusters, or {@code null} if {@link #cluster(Collection)} has + * not been called before. + */ + public List> getClusters() { + return clusters; + } + + /** + * Get the value of the objective function. + * @return the objective function evaluation as double value + * @throws MathIllegalStateException if {@link #cluster(Collection)} has not been called before + */ + public double getObjectiveFunctionValue() { + if (points == null || clusters == null) { + throw new MathIllegalStateException(); + } + + int i = 0; + double objFunction = 0.0; + for (final T point : points) { + int j = 0; + for (final CentroidCluster cluster : clusters) { + final double dist = distance(point, cluster.getCenter()); + objFunction += (dist * dist) * FastMath.pow(membershipMatrix[i][j], fuzziness); + j++; + } + i++; + } + return objFunction; + } + + /** + * Performs Fuzzy K-Means cluster analysis. + * + * @param dataPoints the points to cluster + * @return the list of clusters + * @throws MathIllegalArgumentException if the data points are null or the number + * of clusters is larger than the number of data points + */ + @Override + public List> cluster(final Collection dataPoints) + throws MathIllegalArgumentException { + + // sanity checks + MathUtils.checkNotNull(dataPoints); + + final int size = dataPoints.size(); + + // number of clusters has to be smaller or equal the number of data points + if (size < k) { + throw new NumberIsTooSmallException(size, k, false); + } + + // copy the input collection to an unmodifiable list with indexed access + points = Collections.unmodifiableList(new ArrayList(dataPoints)); + clusters = new ArrayList>(); + membershipMatrix = new double[size][k]; + final double[][] oldMatrix = new double[size][k]; + + // if no points are provided, return an empty list of clusters + if (size == 0) { + return clusters; + } + + initializeMembershipMatrix(); + + // there is at least one point + final int pointDimension = points.get(0).getPoint().length; + for (int i = 0; i < k; i++) { + clusters.add(new CentroidCluster(new DoublePoint(new double[pointDimension]))); + } + + int iteration = 0; + final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations; + double difference = 0.0; + + do { + saveMembershipMatrix(oldMatrix); + updateClusterCenters(); + updateMembershipMatrix(); + difference = calculateMaxMembershipChange(oldMatrix); + } while (difference > epsilon && ++iteration < max); + + return clusters; + } + + /** + * Update the cluster centers. + */ + private void updateClusterCenters() { + int j = 0; + final List> newClusters = new ArrayList>(k); + for (final CentroidCluster cluster : clusters) { + final Clusterable center = cluster.getCenter(); + int i = 0; + double[] arr = new double[center.getPoint().length]; + double sum = 0.0; + for (final T point : points) { + final double u = FastMath.pow(membershipMatrix[i][j], fuzziness); + final double[] pointArr = point.getPoint(); + for (int idx = 0; idx < arr.length; idx++) { + arr[idx] += u * pointArr[idx]; + } + sum += u; + i++; + } + MathArrays.scaleInPlace(1.0 / sum, arr); + newClusters.add(new CentroidCluster(new DoublePoint(arr))); + j++; + } + clusters.clear(); + clusters = newClusters; + } + + /** + * Updates the membership matrix and assigns the points to the cluster with + * the highest membership. + */ + private void updateMembershipMatrix() { + for (int i = 0; i < points.size(); i++) { + final T point = points.get(i); + double maxMembership = Double.MIN_VALUE; + int newCluster = -1; + for (int j = 0; j < clusters.size(); j++) { + double sum = 0.0; + final double distA = FastMath.abs(distance(point, clusters.get(j).getCenter())); + + if (distA != 0.0) { + for (final CentroidCluster c : clusters) { + final double distB = FastMath.abs(distance(point, c.getCenter())); + if (distB == 0.0) { + sum = Double.POSITIVE_INFINITY; + break; + } + sum += FastMath.pow(distA / distB, 2.0 / (fuzziness - 1.0)); + } + } + + double membership; + if (sum == 0.0) { + membership = 1.0; + } else if (sum == Double.POSITIVE_INFINITY) { + membership = 0.0; + } else { + membership = 1.0 / sum; + } + membershipMatrix[i][j] = membership; + + if (membershipMatrix[i][j] > maxMembership) { + maxMembership = membershipMatrix[i][j]; + newCluster = j; + } + } + clusters.get(newCluster).addPoint(point); + } + } + + /** + * Initialize the membership matrix with random values. + */ + private void initializeMembershipMatrix() { + for (int i = 0; i < points.size(); i++) { + for (int j = 0; j < k; j++) { + membershipMatrix[i][j] = random.nextDouble(); + } + membershipMatrix[i] = MathArrays.normalizeArray(membershipMatrix[i], 1.0); + } + } + + /** + * Calculate the maximum element-by-element change of the membership matrix + * for the current iteration. + * + * @param matrix the membership matrix of the previous iteration + * @return the maximum membership matrix change + */ + private double calculateMaxMembershipChange(final double[][] matrix) { + double maxMembership = 0.0; + for (int i = 0; i < points.size(); i++) { + for (int j = 0; j < clusters.size(); j++) { + double v = FastMath.abs(membershipMatrix[i][j] - matrix[i][j]); + maxMembership = FastMath.max(v, maxMembership); + } + } + return maxMembership; + } + + /** + * Copy the membership matrix into the provided matrix. + * + * @param matrix the place to store the membership matrix + */ + private void saveMembershipMatrix(final double[][] matrix) { + for (int i = 0; i < points.size(); i++) { + System.arraycopy(membershipMatrix[i], 0, matrix[i], 0, clusters.size()); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java new file mode 100644 index 000000000..f76d4c780 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java @@ -0,0 +1,565 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.JDKRandomGenerator; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.ml.distance.EuclideanDistance; + +/** + * Clustering algorithm based on David Arthur and Sergei Vassilvitski k-means++ algorithm. + * @param type of the points to cluster + * @see K-means++ (wikipedia) + * @since 3.2 + */ +public class KMeansPlusPlusClusterer extends Clusterer { + + /** Strategies to use for replacing an empty cluster. */ + public enum EmptyClusterStrategy { + + /** Split the cluster with largest distance variance. */ + LARGEST_VARIANCE, + + /** Split the cluster with largest number of points. */ + LARGEST_POINTS_NUMBER, + + /** Create a cluster around the point farthest from its centroid. */ + FARTHEST_POINT, + + /** Generate an error. */ + ERROR + + } + + /** The number of clusters. */ + private final int k; + + /** The maximum number of iterations. */ + private final int maxIterations; + + /** Random generator for choosing initial centers. */ + private final RandomGenerator random; + + /** Selected strategy for empty clusters. */ + private final EmptyClusterStrategy emptyStrategy; + + /** Build a clusterer. + *

    + * The default strategy for handling empty clusters that may appear during + * algorithm iterations is to split the cluster with largest distance variance. + *

    + * The euclidean distance will be used as default distance measure. + * + * @param k the number of clusters to split the data into + */ + public KMeansPlusPlusClusterer(final int k) { + this(k, -1); + } + + /** Build a clusterer. + *

    + * The default strategy for handling empty clusters that may appear during + * algorithm iterations is to split the cluster with largest distance variance. + *

    + * The euclidean distance will be used as default distance measure. + * + * @param k the number of clusters to split the data into + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + */ + public KMeansPlusPlusClusterer(final int k, final int maxIterations) { + this(k, maxIterations, new EuclideanDistance()); + } + + /** Build a clusterer. + *

    + * The default strategy for handling empty clusters that may appear during + * algorithm iterations is to split the cluster with largest distance variance. + * + * @param k the number of clusters to split the data into + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + * @param measure the distance measure to use + */ + public KMeansPlusPlusClusterer(final int k, final int maxIterations, final DistanceMeasure measure) { + this(k, maxIterations, measure, new JDKRandomGenerator()); + } + + /** Build a clusterer. + *

    + * The default strategy for handling empty clusters that may appear during + * algorithm iterations is to split the cluster with largest distance variance. + * + * @param k the number of clusters to split the data into + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + * @param measure the distance measure to use + * @param random random generator to use for choosing initial centers + */ + public KMeansPlusPlusClusterer(final int k, final int maxIterations, + final DistanceMeasure measure, + final RandomGenerator random) { + this(k, maxIterations, measure, random, EmptyClusterStrategy.LARGEST_VARIANCE); + } + + /** Build a clusterer. + * + * @param k the number of clusters to split the data into + * @param maxIterations the maximum number of iterations to run the algorithm for. + * If negative, no maximum will be used. + * @param measure the distance measure to use + * @param random random generator to use for choosing initial centers + * @param emptyStrategy strategy to use for handling empty clusters that + * may appear during algorithm iterations + */ + public KMeansPlusPlusClusterer(final int k, final int maxIterations, + final DistanceMeasure measure, + final RandomGenerator random, + final EmptyClusterStrategy emptyStrategy) { + super(measure); + this.k = k; + this.maxIterations = maxIterations; + this.random = random; + this.emptyStrategy = emptyStrategy; + } + + /** + * Return the number of clusters this instance will use. + * @return the number of clusters + */ + public int getK() { + return k; + } + + /** + * Returns the maximum number of iterations this instance will use. + * @return the maximum number of iterations, or -1 if no maximum is set + */ + public int getMaxIterations() { + return maxIterations; + } + + /** + * Returns the random generator this instance will use. + * @return the random generator + */ + public RandomGenerator getRandomGenerator() { + return random; + } + + /** + * Returns the {@link EmptyClusterStrategy} used by this instance. + * @return the {@link EmptyClusterStrategy} + */ + public EmptyClusterStrategy getEmptyClusterStrategy() { + return emptyStrategy; + } + + /** + * Runs the K-means++ clustering algorithm. + * + * @param points the points to cluster + * @return a list of clusters containing the points + * @throws MathIllegalArgumentException if the data points are null or the number + * of clusters is larger than the number of data points + * @throws ConvergenceException if an empty cluster is encountered and the + * {@link #emptyStrategy} is set to {@code ERROR} + */ + @Override + public List> cluster(final Collection points) + throws MathIllegalArgumentException, ConvergenceException { + + // sanity checks + MathUtils.checkNotNull(points); + + // number of clusters has to be smaller or equal the number of data points + if (points.size() < k) { + throw new NumberIsTooSmallException(points.size(), k, false); + } + + // create the initial clusters + List> clusters = chooseInitialCenters(points); + + // create an array containing the latest assignment of a point to a cluster + // no need to initialize the array, as it will be filled with the first assignment + int[] assignments = new int[points.size()]; + assignPointsToClusters(clusters, points, assignments); + + // iterate through updating the centers until we're done + final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations; + for (int count = 0; count < max; count++) { + boolean emptyCluster = false; + List> newClusters = new ArrayList>(); + for (final CentroidCluster cluster : clusters) { + final Clusterable newCenter; + if (cluster.getPoints().isEmpty()) { + switch (emptyStrategy) { + case LARGEST_VARIANCE : + newCenter = getPointFromLargestVarianceCluster(clusters); + break; + case LARGEST_POINTS_NUMBER : + newCenter = getPointFromLargestNumberCluster(clusters); + break; + case FARTHEST_POINT : + newCenter = getFarthestPoint(clusters); + break; + default : + throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS); + } + emptyCluster = true; + } else { + newCenter = centroidOf(cluster.getPoints(), cluster.getCenter().getPoint().length); + } + newClusters.add(new CentroidCluster(newCenter)); + } + int changes = assignPointsToClusters(newClusters, points, assignments); + clusters = newClusters; + + // if there were no more changes in the point-to-cluster assignment + // and there are no empty clusters left, return the current clusters + if (changes == 0 && !emptyCluster) { + return clusters; + } + } + return clusters; + } + + /** + * Adds the given points to the closest {@link Cluster}. + * + * @param clusters the {@link Cluster}s to add the points to + * @param points the points to add to the given {@link Cluster}s + * @param assignments points assignments to clusters + * @return the number of points assigned to different clusters as the iteration before + */ + private int assignPointsToClusters(final List> clusters, + final Collection points, + final int[] assignments) { + int assignedDifferently = 0; + int pointIndex = 0; + for (final T p : points) { + int clusterIndex = getNearestCluster(clusters, p); + if (clusterIndex != assignments[pointIndex]) { + assignedDifferently++; + } + + CentroidCluster cluster = clusters.get(clusterIndex); + cluster.addPoint(p); + assignments[pointIndex++] = clusterIndex; + } + + return assignedDifferently; + } + + /** + * Use K-means++ to choose the initial centers. + * + * @param points the points to choose the initial centers from + * @return the initial centers + */ + private List> chooseInitialCenters(final Collection points) { + + // Convert to list for indexed access. Make it unmodifiable, since removal of items + // would screw up the logic of this method. + final List pointList = Collections.unmodifiableList(new ArrayList (points)); + + // The number of points in the list. + final int numPoints = pointList.size(); + + // Set the corresponding element in this array to indicate when + // elements of pointList are no longer available. + final boolean[] taken = new boolean[numPoints]; + + // The resulting list of initial centers. + final List> resultSet = new ArrayList>(); + + // Choose one center uniformly at random from among the data points. + final int firstPointIndex = random.nextInt(numPoints); + + final T firstPoint = pointList.get(firstPointIndex); + + resultSet.add(new CentroidCluster(firstPoint)); + + // Must mark it as taken + taken[firstPointIndex] = true; + + // To keep track of the minimum distance squared of elements of + // pointList to elements of resultSet. + final double[] minDistSquared = new double[numPoints]; + + // Initialize the elements. Since the only point in resultSet is firstPoint, + // this is very easy. + for (int i = 0; i < numPoints; i++) { + if (i != firstPointIndex) { // That point isn't considered + double d = distance(firstPoint, pointList.get(i)); + minDistSquared[i] = d*d; + } + } + + while (resultSet.size() < k) { + + // Sum up the squared distances for the points in pointList not + // already taken. + double distSqSum = 0.0; + + for (int i = 0; i < numPoints; i++) { + if (!taken[i]) { + distSqSum += minDistSquared[i]; + } + } + + // Add one new data point as a center. Each point x is chosen with + // probability proportional to D(x)2 + final double r = random.nextDouble() * distSqSum; + + // The index of the next point to be added to the resultSet. + int nextPointIndex = -1; + + // Sum through the squared min distances again, stopping when + // sum >= r. + double sum = 0.0; + for (int i = 0; i < numPoints; i++) { + if (!taken[i]) { + sum += minDistSquared[i]; + if (sum >= r) { + nextPointIndex = i; + break; + } + } + } + + // If it's not set to >= 0, the point wasn't found in the previous + // for loop, probably because distances are extremely small. Just pick + // the last available point. + if (nextPointIndex == -1) { + for (int i = numPoints - 1; i >= 0; i--) { + if (!taken[i]) { + nextPointIndex = i; + break; + } + } + } + + // We found one. + if (nextPointIndex >= 0) { + + final T p = pointList.get(nextPointIndex); + + resultSet.add(new CentroidCluster (p)); + + // Mark it as taken. + taken[nextPointIndex] = true; + + if (resultSet.size() < k) { + // Now update elements of minDistSquared. We only have to compute + // the distance to the new center to do this. + for (int j = 0; j < numPoints; j++) { + // Only have to worry about the points still not taken. + if (!taken[j]) { + double d = distance(p, pointList.get(j)); + double d2 = d * d; + if (d2 < minDistSquared[j]) { + minDistSquared[j] = d2; + } + } + } + } + + } else { + // None found -- + // Break from the while loop to prevent + // an infinite loop. + break; + } + } + + return resultSet; + } + + /** + * Get a random point from the {@link Cluster} with the largest distance variance. + * + * @param clusters the {@link Cluster}s to search + * @return a random point from the selected cluster + * @throws ConvergenceException if clusters are all empty + */ + private T getPointFromLargestVarianceCluster(final Collection> clusters) + throws ConvergenceException { + + double maxVariance = Double.NEGATIVE_INFINITY; + Cluster selected = null; + for (final CentroidCluster cluster : clusters) { + if (!cluster.getPoints().isEmpty()) { + + // compute the distance variance of the current cluster + final Clusterable center = cluster.getCenter(); + final Variance stat = new Variance(); + for (final T point : cluster.getPoints()) { + stat.increment(distance(point, center)); + } + final double variance = stat.getResult(); + + // select the cluster with the largest variance + if (variance > maxVariance) { + maxVariance = variance; + selected = cluster; + } + + } + } + + // did we find at least one non-empty cluster ? + if (selected == null) { + throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS); + } + + // extract a random point from the cluster + final List selectedPoints = selected.getPoints(); + return selectedPoints.remove(random.nextInt(selectedPoints.size())); + + } + + /** + * Get a random point from the {@link Cluster} with the largest number of points + * + * @param clusters the {@link Cluster}s to search + * @return a random point from the selected cluster + * @throws ConvergenceException if clusters are all empty + */ + private T getPointFromLargestNumberCluster(final Collection> clusters) + throws ConvergenceException { + + int maxNumber = 0; + Cluster selected = null; + for (final Cluster 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 selectedPoints = selected.getPoints(); + return selectedPoints.remove(random.nextInt(selectedPoints.size())); + + } + + /** + * Get the point farthest to its cluster center + * + * @param clusters the {@link Cluster}s to search + * @return point farthest to its cluster center + * @throws ConvergenceException if clusters are all empty + */ + private T getFarthestPoint(final Collection> clusters) throws ConvergenceException { + + double maxDistance = Double.NEGATIVE_INFINITY; + Cluster selectedCluster = null; + int selectedPoint = -1; + for (final CentroidCluster cluster : clusters) { + + // get the farthest point + final Clusterable center = cluster.getCenter(); + final List points = cluster.getPoints(); + for (int i = 0; i < points.size(); ++i) { + final double distance = distance(points.get(i), center); + if (distance > maxDistance) { + maxDistance = distance; + selectedCluster = cluster; + selectedPoint = i; + } + } + + } + + // did we find at least one non-empty cluster ? + if (selectedCluster == null) { + throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS); + } + + return selectedCluster.getPoints().remove(selectedPoint); + + } + + /** + * Returns the nearest {@link Cluster} to the given point + * + * @param clusters the {@link Cluster}s to search + * @param point the point to find the nearest {@link Cluster} for + * @return the index of the nearest {@link Cluster} to the given point + */ + private int getNearestCluster(final Collection> clusters, final T point) { + double minDistance = Double.MAX_VALUE; + int clusterIndex = 0; + int minCluster = 0; + for (final CentroidCluster c : clusters) { + final double distance = distance(point, c.getCenter()); + if (distance < minDistance) { + minDistance = distance; + minCluster = clusterIndex; + } + clusterIndex++; + } + return minCluster; + } + + /** + * Computes the centroid for a set of points. + * + * @param points the set of points + * @param dimension the point dimension + * @return the computed centroid for the set of points + */ + private Clusterable centroidOf(final Collection points, final int dimension) { + final double[] centroid = new double[dimension]; + for (final T p : points) { + final double[] point = p.getPoint(); + for (int i = 0; i < centroid.length; i++) { + centroid[i] += point[i]; + } + } + for (int i = 0; i < centroid.length; i++) { + centroid[i] /= points.size(); + } + return new DoublePoint(centroid); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java new file mode 100644 index 000000000..72d5a4717 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering; + +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.ml.clustering.evaluation.ClusterEvaluator; +import com.fr.third.org.apache.commons.math3.ml.clustering.evaluation.SumOfClusterVariances; + +/** + * A wrapper around a k-means++ clustering algorithm which performs multiple trials + * and returns the best solution. + * @param type of the points to cluster + * @since 3.2 + */ +public class MultiKMeansPlusPlusClusterer extends Clusterer { + + /** The underlying k-means clusterer. */ + private final KMeansPlusPlusClusterer clusterer; + + /** The number of trial runs. */ + private final int numTrials; + + /** The cluster evaluator to use. */ + private final ClusterEvaluator evaluator; + + /** Build a clusterer. + * @param clusterer the k-means clusterer to use + * @param numTrials number of trial runs + */ + public MultiKMeansPlusPlusClusterer(final KMeansPlusPlusClusterer clusterer, + final int numTrials) { + this(clusterer, numTrials, new SumOfClusterVariances(clusterer.getDistanceMeasure())); + } + + /** Build a clusterer. + * @param clusterer the k-means clusterer to use + * @param numTrials number of trial runs + * @param evaluator the cluster evaluator to use + * @since 3.3 + */ + public MultiKMeansPlusPlusClusterer(final KMeansPlusPlusClusterer clusterer, + final int numTrials, + final ClusterEvaluator evaluator) { + super(clusterer.getDistanceMeasure()); + this.clusterer = clusterer; + this.numTrials = numTrials; + this.evaluator = evaluator; + } + + /** + * Returns the embedded k-means clusterer used by this instance. + * @return the embedded clusterer + */ + public KMeansPlusPlusClusterer getClusterer() { + return clusterer; + } + + /** + * Returns the number of trials this instance will do. + * @return the number of trials + */ + public int getNumTrials() { + return numTrials; + } + + /** + * Returns the {@link ClusterEvaluator} used to determine the "best" clustering. + * @return the used {@link ClusterEvaluator} + * @since 3.3 + */ + public ClusterEvaluator getClusterEvaluator() { + return evaluator; + } + + /** + * Runs the K-means++ clustering algorithm. + * + * @param points the points to cluster + * @return a list of clusters containing the points + * @throws MathIllegalArgumentException if the data points are null or the number + * of clusters is larger than the number of data points + * @throws ConvergenceException if an empty cluster is encountered and the + * underlying {@link KMeansPlusPlusClusterer} has its + * {@link KMeansPlusPlusClusterer.EmptyClusterStrategy} is set to {@code ERROR}. + */ + @Override + public List> cluster(final Collection points) + throws MathIllegalArgumentException, ConvergenceException { + + // at first, we have not found any clusters list yet + List> best = null; + double bestVarianceSum = Double.POSITIVE_INFINITY; + + // do several clustering trials + for (int i = 0; i < numTrials; ++i) { + + // compute a clusters list + List> clusters = clusterer.cluster(points); + + // compute the variance of the current list + final double varianceSum = evaluator.score(clusters); + + if (evaluator.isBetterScore(varianceSum, bestVarianceSum)) { + // this one is the best we have found so far, remember it + best = clusters; + bestVarianceSum = varianceSum; + } + + } + + // return the best clusters list found + return best; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java new file mode 100644 index 000000000..58228a97e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering.evaluation; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.ml.clustering.CentroidCluster; +import com.fr.third.org.apache.commons.math3.ml.clustering.Cluster; +import com.fr.third.org.apache.commons.math3.ml.clustering.Clusterable; +import com.fr.third.org.apache.commons.math3.ml.clustering.DoublePoint; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.ml.distance.EuclideanDistance; + +/** + * Base class for cluster evaluation methods. + * + * @param type of the clustered points + * @since 3.3 + */ +public abstract class ClusterEvaluator { + + /** The distance measure to use when evaluating the cluster. */ + private final DistanceMeasure measure; + + /** + * Creates a new cluster evaluator with an {@link EuclideanDistance} + * as distance measure. + */ + public ClusterEvaluator() { + this(new EuclideanDistance()); + } + + /** + * Creates a new cluster evaluator with the given distance measure. + * @param measure the distance measure to use + */ + public ClusterEvaluator(final DistanceMeasure measure) { + this.measure = measure; + } + + /** + * Computes the evaluation score for the given list of clusters. + * @param clusters the clusters to evaluate + * @return the computed score + */ + public abstract double score(List> clusters); + + /** + * Returns whether the first evaluation score is considered to be better + * than the second one by this evaluator. + *

    + * Specific implementations shall override this method if the returned scores + * do not follow the same ordering, i.e. smaller score is better. + * + * @param score1 the first score + * @param score2 the second score + * @return {@code true} if the first score is considered to be better, {@code false} otherwise + */ + public boolean isBetterScore(double score1, double score2) { + return score1 < score2; + } + + /** + * Calculates the distance between two {@link Clusterable} instances + * with the configured {@link DistanceMeasure}. + * + * @param p1 the first clusterable + * @param p2 the second clusterable + * @return the distance between the two clusterables + */ + protected double distance(final Clusterable p1, final Clusterable p2) { + return measure.compute(p1.getPoint(), p2.getPoint()); + } + + /** + * Computes the centroid for a cluster. + * + * @param cluster the cluster + * @return the computed centroid for the cluster, + * or {@code null} if the cluster does not contain any points + */ + protected Clusterable centroidOf(final Cluster cluster) { + final List points = cluster.getPoints(); + if (points.isEmpty()) { + return null; + } + + // in case the cluster is of type CentroidCluster, no need to compute the centroid + if (cluster instanceof CentroidCluster) { + return ((CentroidCluster) cluster).getCenter(); + } + + final int dimension = points.get(0).getPoint().length; + final double[] centroid = new double[dimension]; + for (final T p : points) { + final double[] point = p.getPoint(); + for (int i = 0; i < centroid.length; i++) { + centroid[i] += point[i]; + } + } + for (int i = 0; i < centroid.length; i++) { + centroid[i] /= points.size(); + } + return new DoublePoint(centroid); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java new file mode 100644 index 000000000..17b393e05 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.clustering.evaluation; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.ml.clustering.Cluster; +import com.fr.third.org.apache.commons.math3.ml.clustering.Clusterable; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Computes the sum of intra-cluster distance variances according to the formula: + *

    + * \( score = \sum\limits_{i=1}^n \sigma_i^2 \)
    + * 
    + * where n is the number of clusters and \( \sigma_i^2 \) is the variance of + * intra-cluster distances of cluster \( c_i \). + * + * @param the type of the clustered points + * @since 3.3 + */ +public class SumOfClusterVariances extends ClusterEvaluator { + + /** + * + * @param measure the distance measure to use + */ + public SumOfClusterVariances(final DistanceMeasure measure) { + super(measure); + } + + /** {@inheritDoc} */ + @Override + public double score(final List> clusters) { + double varianceSum = 0.0; + for (final Cluster cluster : clusters) { + if (!cluster.getPoints().isEmpty()) { + + final Clusterable center = centroidOf(cluster); + + // compute the distance variance of the current cluster + final Variance stat = new Variance(); + for (final T point : cluster.getPoints()) { + stat.increment(distance(point, center)); + } + varianceSum += stat.getResult(); + + } + } + return varianceSum; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/package-info.java new file mode 100644 index 000000000..f62ea0821 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/evaluation/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Cluster evaluation methods. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering.evaluation; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/package-info.java new file mode 100644 index 000000000..195702352 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/clustering/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Clustering algorithms. + */ +package com.fr.third.org.apache.commons.math3.ml.clustering; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/CanberraDistance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/CanberraDistance.java new file mode 100644 index 000000000..da8ce82b4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/CanberraDistance.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the Canberra distance between two points. + * + * @since 3.2 + */ +public class CanberraDistance implements DistanceMeasure { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -6972277381587032228L; + + /** {@inheritDoc} */ + public double compute(double[] a, double[] b) + throws DimensionMismatchException { + MathArrays.checkEqualLength(a, b); + double sum = 0; + for (int i = 0; i < a.length; i++) { + final double num = FastMath.abs(a[i] - b[i]); + final double denom = FastMath.abs(a[i]) + FastMath.abs(b[i]); + sum += num == 0.0 && denom == 0.0 ? 0.0 : num / denom; + } + return sum; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ChebyshevDistance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ChebyshevDistance.java new file mode 100644 index 000000000..2f626ad31 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ChebyshevDistance.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the L (max of abs) distance between two points. + * + * @since 3.2 + */ +public class ChebyshevDistance implements DistanceMeasure { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -4694868171115238296L; + + /** {@inheritDoc} */ + public double compute(double[] a, double[] b) + throws DimensionMismatchException { + return MathArrays.distanceInf(a, b); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/DistanceMeasure.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/DistanceMeasure.java new file mode 100644 index 000000000..71a670e4c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/DistanceMeasure.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * Interface for distance measures of n-dimensional vectors. + * + * @since 3.2 + */ +public interface DistanceMeasure extends Serializable { + + /** + * Compute the distance between two n-dimensional vectors. + *

    + * The two vectors are required to have the same dimension. + * + * @param a the first vector + * @param b the second vector + * @return the distance between the two vectors + * @throws DimensionMismatchException if the array lengths differ. + */ + double compute(double[] a, double[] b) throws DimensionMismatchException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EarthMoversDistance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EarthMoversDistance.java new file mode 100644 index 000000000..107d6efbb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EarthMoversDistance.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the Earh Mover's distance (also known as Wasserstein metric) between two distributions. + * + * @see Earth Mover's distance (Wikipedia) + * + * @since 3.3 + */ +public class EarthMoversDistance implements DistanceMeasure { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -5406732779747414922L; + + /** {@inheritDoc} */ + public double compute(double[] a, double[] b) + throws DimensionMismatchException { + MathArrays.checkEqualLength(a, b); + double lastDistance = 0; + double totalDistance = 0; + for (int i = 0; i < a.length; i++) { + final double currentDistance = (a[i] + lastDistance) - b[i]; + totalDistance += FastMath.abs(currentDistance); + lastDistance = currentDistance; + } + return totalDistance; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EuclideanDistance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EuclideanDistance.java new file mode 100644 index 000000000..399e138cf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/EuclideanDistance.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the L2 (Euclidean) distance between two points. + * + * @since 3.2 + */ +public class EuclideanDistance implements DistanceMeasure { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 1717556319784040040L; + + /** {@inheritDoc} */ + public double compute(double[] a, double[] b) + throws DimensionMismatchException { + return MathArrays.distance(a, b); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ManhattanDistance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ManhattanDistance.java new file mode 100644 index 000000000..bd509f1ac --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/ManhattanDistance.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Calculates the L1 (sum of abs) distance between two points. + * + * @since 3.2 + */ +public class ManhattanDistance implements DistanceMeasure { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -9108154600539125566L; + + /** {@inheritDoc} */ + public double compute(double[] a, double[] b) + throws DimensionMismatchException { + return MathArrays.distance1(a, b); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/package-info.java new file mode 100644 index 000000000..628af4de8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/distance/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Common distance measures. + */ +package com.fr.third.org.apache.commons.math3.ml.distance; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java new file mode 100644 index 000000000..1d2c48db6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +/** + * Defines how to assign the first value of a neuron's feature. + * + * @since 3.3 + */ +public interface FeatureInitializer { + /** + * Selects the initial value. + * + * @return the initial value. + */ + double value(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.java new file mode 100644 index 000000000..35033a60f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.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 com.fr.third.org.apache.commons.math3.ml.neuralnet; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.function.Constant; +import com.fr.third.org.apache.commons.math3.distribution.RealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.UniformRealDistribution; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; + +/** + * Creates functions that will select the initial values of a neuron's + * features. + * + * @since 3.3 + */ +public class FeatureInitializerFactory { + /** Class contains only static methods. */ + private FeatureInitializerFactory() {} + + /** + * Uniform sampling of the given range. + * + * @param min Lower bound of the range. + * @param max Upper bound of the range. + * @param rng Random number generator used to draw samples from a + * uniform distribution. + * @return an initializer such that the features will be initialized with + * values within the given range. + * @throws NumberIsTooLargeException + * if {@code min >= max}. + */ + public static FeatureInitializer uniform(final RandomGenerator rng, + final double min, + final double max) { + return randomize(new UniformRealDistribution(rng, min, max), + function(new Constant(0), 0, 0)); + } + + /** + * Uniform sampling of the given range. + * + * @param min Lower bound of the range. + * @param max Upper bound of the range. + * @return an initializer such that the features will be initialized with + * values within the given range. + * @throws NumberIsTooLargeException + * if {@code min >= max}. + */ + public static FeatureInitializer uniform(final double min, + final double max) { + return randomize(new UniformRealDistribution(min, max), + function(new Constant(0), 0, 0)); + } + + /** + * Creates an initializer from a univariate function {@code f(x)}. + * The argument {@code x} is set to {@code init} at the first call + * and will be incremented at each call. + * + * @param f Function. + * @param init Initial value. + * @param inc Increment + * @return the initializer. + */ + public static FeatureInitializer function(final UnivariateFunction f, + final double init, + final double inc) { + return new FeatureInitializer() { + /** Argument. */ + private double arg = init; + + /** {@inheritDoc} */ + public double value() { + final double result = f.value(arg); + arg += inc; + return result; + } + }; + } + + /** + * Adds some amount of random data to the given initializer. + * + * @param random Random variable distribution. + * @param orig Original initializer. + * @return an initializer whose {@link FeatureInitializer#value() value} + * method will return {@code orig.value() + random.sample()}. + */ + public static FeatureInitializer randomize(final RealDistribution random, + final FeatureInitializer orig) { + return new FeatureInitializer() { + /** {@inheritDoc} */ + public double value() { + return orig.value() + random.sample(); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/MapUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/MapUtils.java new file mode 100644 index 000000000..a6ed8ee98 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/MapUtils.java @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.util.Pair; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; + +/** + * Utilities for network maps. + * + * @since 3.3 + */ +public class MapUtils { + /** + * Class contains only static methods. + */ + private MapUtils() {} + + /** + * Finds the neuron that best matches the given features. + * + * @param features Data. + * @param neurons List of neurons to scan. If the list is empty + * {@code null} will be returned. + * @param distance Distance function. The neuron's features are + * passed as the first argument to {@link DistanceMeasure#compute(double[],double[])}. + * @return the neuron whose features are closest to the given data. + * @throws DimensionMismatchException + * if the size of the input is not compatible with the neurons features + * size. + */ + public static Neuron findBest(double[] features, + Iterable neurons, + DistanceMeasure distance) { + Neuron best = null; + double min = Double.POSITIVE_INFINITY; + for (final Neuron n : neurons) { + final double d = distance.compute(n.getFeatures(), features); + if (d < min) { + min = d; + best = n; + } + } + + return best; + } + + /** + * Finds the two neurons that best match the given features. + * + * @param features Data. + * @param neurons List of neurons to scan. If the list is empty + * {@code null} will be returned. + * @param distance Distance function. The neuron's features are + * passed as the first argument to {@link DistanceMeasure#compute(double[],double[])}. + * @return the two neurons whose features are closest to the given data. + * @throws DimensionMismatchException + * if the size of the input is not compatible with the neurons features + * size. + */ + public static Pair findBestAndSecondBest(double[] features, + Iterable neurons, + DistanceMeasure distance) { + Neuron[] best = { null, null }; + double[] min = { Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY }; + for (final Neuron n : neurons) { + final double d = distance.compute(n.getFeatures(), features); + if (d < min[0]) { + // Replace second best with old best. + min[1] = min[0]; + best[1] = best[0]; + + // Store current as new best. + min[0] = d; + best[0] = n; + } else if (d < min[1]) { + // Replace old second best with current. + min[1] = d; + best[1] = n; + } + } + + return new Pair(best[0], best[1]); + } + + /** + * Creates a list of neurons sorted in increased order of the distance + * to the given {@code features}. + * + * @param features Data. + * @param neurons List of neurons to scan. If it is empty, an empty array + * will be returned. + * @param distance Distance function. + * @return the neurons, sorted in increasing order of distance in data + * space. + * @throws DimensionMismatchException + * if the size of the input is not compatible with the neurons features + * size. + * + * @see #findBest(double[],Iterable,DistanceMeasure) + * @see #findBestAndSecondBest(double[],Iterable,DistanceMeasure) + * + * @since 3.6 + */ + public static Neuron[] sort(double[] features, + Iterable neurons, + DistanceMeasure distance) { + final List list = new ArrayList(); + + for (final Neuron n : neurons) { + final double d = distance.compute(n.getFeatures(), features); + list.add(new PairNeuronDouble(n, d)); + } + + Collections.sort(list, PairNeuronDouble.COMPARATOR); + + final int len = list.size(); + final Neuron[] sorted = new Neuron[len]; + + for (int i = 0; i < len; i++) { + sorted[i] = list.get(i).getNeuron(); + } + return sorted; + } + + /** + * Computes the + * U-matrix of a two-dimensional map. + * + * @param map Network. + * @param distance Function to use for computing the average + * distance from a neuron to its neighbours. + * @return the matrix of average distances. + */ + public static double[][] computeU(NeuronSquareMesh2D map, + DistanceMeasure distance) { + final int numRows = map.getNumberOfRows(); + final int numCols = map.getNumberOfColumns(); + final double[][] uMatrix = new double[numRows][numCols]; + + final Network net = map.getNetwork(); + + for (int i = 0; i < numRows; i++) { + for (int j = 0; j < numCols; j++) { + final Neuron neuron = map.getNeuron(i, j); + final Collection neighbours = net.getNeighbours(neuron); + final double[] features = neuron.getFeatures(); + + double d = 0; + int count = 0; + for (Neuron n : neighbours) { + ++count; + d += distance.compute(features, n.getFeatures()); + } + + uMatrix[i][j] = d / count; + } + } + + return uMatrix; + } + + /** + * Computes the "hit" histogram of a two-dimensional map. + * + * @param data Feature vectors. + * @param map Network. + * @param distance Function to use for determining the best matching unit. + * @return the number of hits for each neuron in the map. + */ + public static int[][] computeHitHistogram(Iterable data, + NeuronSquareMesh2D map, + DistanceMeasure distance) { + final HashMap hit = new HashMap(); + final Network net = map.getNetwork(); + + for (double[] f : data) { + final Neuron best = findBest(f, net, distance); + final Integer count = hit.get(best); + if (count == null) { + hit.put(best, 1); + } else { + hit.put(best, count + 1); + } + } + + // Copy the histogram data into a 2D map. + final int numRows = map.getNumberOfRows(); + final int numCols = map.getNumberOfColumns(); + final int[][] histo = new int[numRows][numCols]; + + for (int i = 0; i < numRows; i++) { + for (int j = 0; j < numCols; j++) { + final Neuron neuron = map.getNeuron(i, j); + final Integer count = hit.get(neuron); + if (count == null) { + histo[i][j] = 0; + } else { + histo[i][j] = count; + } + } + } + + return histo; + } + + /** + * Computes the quantization error. + * The quantization error is the average distance between a feature vector + * and its "best matching unit" (closest neuron). + * + * @param data Feature vectors. + * @param neurons List of neurons to scan. + * @param distance Distance function. + * @return the error. + * @throws NoDataException if {@code data} is empty. + */ + public static double computeQuantizationError(Iterable data, + Iterable neurons, + DistanceMeasure distance) { + double d = 0; + int count = 0; + for (double[] f : data) { + ++count; + d += distance.compute(f, findBest(f, neurons, distance).getFeatures()); + } + + if (count == 0) { + throw new NoDataException(); + } + + return d / count; + } + + /** + * Computes the topographic error. + * The topographic error is the proportion of data for which first and + * second best matching units are not adjacent in the map. + * + * @param data Feature vectors. + * @param net Network. + * @param distance Distance function. + * @return the error. + * @throws NoDataException if {@code data} is empty. + */ + public static double computeTopographicError(Iterable data, + Network net, + DistanceMeasure distance) { + int notAdjacentCount = 0; + int count = 0; + for (double[] f : data) { + ++count; + final Pair p = findBestAndSecondBest(f, net, distance); + if (!net.getNeighbours(p.getFirst()).contains(p.getSecond())) { + // Increment count if first and second best matching units + // are not neighbours. + ++notAdjacentCount; + } + } + + if (count == 0) { + throw new NoDataException(); + } + + return ((double) notAdjacentCount) / count; + } + + /** + * Helper data structure holding a (Neuron, double) pair. + */ + private static class PairNeuronDouble { + /** Comparator. */ + static final Comparator COMPARATOR + = new Comparator() { + /** {@inheritDoc} */ + public int compare(PairNeuronDouble o1, + PairNeuronDouble o2) { + return Double.compare(o1.value, o2.value); + } + }; + /** Key. */ + private final Neuron neuron; + /** Value. */ + private final double value; + + /** + * @param neuron Neuron. + * @param value Value. + */ + PairNeuronDouble(Neuron neuron, double value) { + this.neuron = neuron; + this.value = value; + } + + /** @return the neuron. */ + public Neuron getNeuron() { + return neuron; + } + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Network.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Network.java new file mode 100644 index 000000000..0179da904 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Network.java @@ -0,0 +1,500 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +import java.io.Serializable; +import java.io.ObjectInputStream; +import java.util.NoSuchElementException; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Comparator; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; + +/** + * Neural network, composed of {@link Neuron} instances and the links + * between them. + * + * Although updating a neuron's state is thread-safe, modifying the + * network's topology (adding or removing links) is not. + * + * @since 3.3 + */ +public class Network + implements Iterable, + Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130207L; + /** Neurons. */ + private final ConcurrentHashMap neuronMap + = new ConcurrentHashMap(); + /** Next available neuron identifier. */ + private final AtomicLong nextId; + /** Neuron's features set size. */ + private final int featureSize; + /** Links. */ + private final ConcurrentHashMap> linkMap + = new ConcurrentHashMap>(); + + /** + * Comparator that prescribes an order of the neurons according + * to the increasing order of their identifier. + */ + public static class NeuronIdentifierComparator + implements Comparator, + Serializable { + /** Version identifier. */ + private static final long serialVersionUID = 20130207L; + + /** {@inheritDoc} */ + public int compare(Neuron a, + Neuron b) { + final long aId = a.getIdentifier(); + final long bId = b.getIdentifier(); + return aId < bId ? -1 : + aId > bId ? 1 : 0; + } + } + + /** + * Constructor with restricted access, solely used for deserialization. + * + * @param nextId Next available identifier. + * @param featureSize Number of features. + * @param neuronList Neurons. + * @param neighbourIdList Links associated to each of the neurons in + * {@code neuronList}. + * @throws MathIllegalStateException if an inconsistency is detected + * (which probably means that the serialized form has been corrupted). + */ + Network(long nextId, + int featureSize, + Neuron[] neuronList, + long[][] neighbourIdList) { + final int numNeurons = neuronList.length; + if (numNeurons != neighbourIdList.length) { + throw new MathIllegalStateException(); + } + + for (int i = 0; i < numNeurons; i++) { + final Neuron n = neuronList[i]; + final long id = n.getIdentifier(); + if (id >= nextId) { + throw new MathIllegalStateException(); + } + neuronMap.put(id, n); + linkMap.put(id, new HashSet()); + } + + for (int i = 0; i < numNeurons; i++) { + final long aId = neuronList[i].getIdentifier(); + final Set aLinks = linkMap.get(aId); + for (Long bId : neighbourIdList[i]) { + if (neuronMap.get(bId) == null) { + throw new MathIllegalStateException(); + } + addLinkToLinkSet(aLinks, bId); + } + } + + this.nextId = new AtomicLong(nextId); + this.featureSize = featureSize; + } + + /** + * @param initialIdentifier Identifier for the first neuron that + * will be added to this network. + * @param featureSize Size of the neuron's features. + */ + public Network(long initialIdentifier, + int featureSize) { + nextId = new AtomicLong(initialIdentifier); + this.featureSize = featureSize; + } + + /** + * Performs a deep copy of this instance. + * Upon return, the copied and original instances will be independent: + * Updating one will not affect the other. + * + * @return a new instance with the same state as this instance. + * @since 3.6 + */ + public synchronized Network copy() { + final Network copy = new Network(nextId.get(), + featureSize); + + + for (Map.Entry e : neuronMap.entrySet()) { + copy.neuronMap.put(e.getKey(), e.getValue().copy()); + } + + for (Map.Entry> e : linkMap.entrySet()) { + copy.linkMap.put(e.getKey(), new HashSet(e.getValue())); + } + + return copy; + } + + /** + * {@inheritDoc} + */ + public Iterator iterator() { + return neuronMap.values().iterator(); + } + + /** + * Creates a list of the neurons, sorted in a custom order. + * + * @param comparator {@link Comparator} used for sorting the neurons. + * @return a list of neurons, sorted in the order prescribed by the + * given {@code comparator}. + * @see NeuronIdentifierComparator + */ + public Collection getNeurons(Comparator comparator) { + final List neurons = new ArrayList(); + neurons.addAll(neuronMap.values()); + + Collections.sort(neurons, comparator); + + return neurons; + } + + /** + * Creates a neuron and assigns it a unique identifier. + * + * @param features Initial values for the neuron's features. + * @return the neuron's identifier. + * @throws DimensionMismatchException if the length of {@code features} + * is different from the expected size (as set by the + * {@link #Network(long,int) constructor}). + */ + public long createNeuron(double[] features) { + if (features.length != featureSize) { + throw new DimensionMismatchException(features.length, featureSize); + } + + final long id = createNextId(); + neuronMap.put(id, new Neuron(id, features)); + linkMap.put(id, new HashSet()); + return id; + } + + /** + * Deletes a neuron. + * Links from all neighbours to the removed neuron will also be + * {@link #deleteLink(Neuron,Neuron) deleted}. + * + * @param neuron Neuron to be removed from this network. + * @throws NoSuchElementException if {@code n} does not belong to + * this network. + */ + public void deleteNeuron(Neuron neuron) { + final Collection neighbours = getNeighbours(neuron); + + // Delete links to from neighbours. + for (Neuron n : neighbours) { + deleteLink(n, neuron); + } + + // Remove neuron. + neuronMap.remove(neuron.getIdentifier()); + } + + /** + * Gets the size of the neurons' features set. + * + * @return the size of the features set. + */ + public int getFeaturesSize() { + return featureSize; + } + + /** + * Adds a link from neuron {@code a} to neuron {@code b}. + * Note: the link is not bi-directional; if a bi-directional link is + * required, an additional call must be made with {@code a} and + * {@code b} exchanged in the argument list. + * + * @param a Neuron. + * @param b Neuron. + * @throws NoSuchElementException if the neurons do not exist in the + * network. + */ + public void addLink(Neuron a, + Neuron b) { + final long aId = a.getIdentifier(); + final long bId = b.getIdentifier(); + + // Check that the neurons belong to this network. + if (a != getNeuron(aId)) { + throw new NoSuchElementException(Long.toString(aId)); + } + if (b != getNeuron(bId)) { + throw new NoSuchElementException(Long.toString(bId)); + } + + // Add link from "a" to "b". + addLinkToLinkSet(linkMap.get(aId), bId); + } + + /** + * Adds a link to neuron {@code id} in given {@code linkSet}. + * Note: no check verifies that the identifier indeed belongs + * to this network. + * + * @param linkSet Neuron identifier. + * @param id Neuron identifier. + */ + private void addLinkToLinkSet(Set linkSet, + long id) { + linkSet.add(id); + } + + /** + * Deletes the link between neurons {@code a} and {@code b}. + * + * @param a Neuron. + * @param b Neuron. + * @throws NoSuchElementException if the neurons do not exist in the + * network. + */ + public void deleteLink(Neuron a, + Neuron b) { + final long aId = a.getIdentifier(); + final long bId = b.getIdentifier(); + + // Check that the neurons belong to this network. + if (a != getNeuron(aId)) { + throw new NoSuchElementException(Long.toString(aId)); + } + if (b != getNeuron(bId)) { + throw new NoSuchElementException(Long.toString(bId)); + } + + // Delete link from "a" to "b". + deleteLinkFromLinkSet(linkMap.get(aId), bId); + } + + /** + * Deletes a link to neuron {@code id} in given {@code linkSet}. + * Note: no check verifies that the identifier indeed belongs + * to this network. + * + * @param linkSet Neuron identifier. + * @param id Neuron identifier. + */ + private void deleteLinkFromLinkSet(Set linkSet, + long id) { + linkSet.remove(id); + } + + /** + * Retrieves the neuron with the given (unique) {@code id}. + * + * @param id Identifier. + * @return the neuron associated with the given {@code id}. + * @throws NoSuchElementException if the neuron does not exist in the + * network. + */ + public Neuron getNeuron(long id) { + final Neuron n = neuronMap.get(id); + if (n == null) { + throw new NoSuchElementException(Long.toString(id)); + } + return n; + } + + /** + * Retrieves the neurons in the neighbourhood of any neuron in the + * {@code neurons} list. + * @param neurons Neurons for which to retrieve the neighbours. + * @return the list of neighbours. + * @see #getNeighbours(Iterable,Iterable) + */ + public Collection getNeighbours(Iterable neurons) { + return getNeighbours(neurons, null); + } + + /** + * Retrieves the neurons in the neighbourhood of any neuron in the + * {@code neurons} list. + * The {@code exclude} list allows to retrieve the "concentric" + * neighbourhoods by removing the neurons that belong to the inner + * "circles". + * + * @param neurons Neurons for which to retrieve the neighbours. + * @param exclude Neurons to exclude from the returned list. + * Can be {@code null}. + * @return the list of neighbours. + */ + public Collection getNeighbours(Iterable neurons, + Iterable exclude) { + final Set idList = new HashSet(); + + for (Neuron n : neurons) { + idList.addAll(linkMap.get(n.getIdentifier())); + } + if (exclude != null) { + for (Neuron n : exclude) { + idList.remove(n.getIdentifier()); + } + } + + final List neuronList = new ArrayList(); + for (Long id : idList) { + neuronList.add(getNeuron(id)); + } + + return neuronList; + } + + /** + * Retrieves the neighbours of the given neuron. + * + * @param neuron Neuron for which to retrieve the neighbours. + * @return the list of neighbours. + * @see #getNeighbours(Neuron,Iterable) + */ + public Collection getNeighbours(Neuron neuron) { + return getNeighbours(neuron, null); + } + + /** + * Retrieves the neighbours of the given neuron. + * + * @param neuron Neuron for which to retrieve the neighbours. + * @param exclude Neurons to exclude from the returned list. + * Can be {@code null}. + * @return the list of neighbours. + */ + public Collection getNeighbours(Neuron neuron, + Iterable exclude) { + final Set idList = linkMap.get(neuron.getIdentifier()); + if (exclude != null) { + for (Neuron n : exclude) { + idList.remove(n.getIdentifier()); + } + } + + final List neuronList = new ArrayList(); + for (Long id : idList) { + neuronList.add(getNeuron(id)); + } + + return neuronList; + } + + /** + * Creates a neuron identifier. + * + * @return a value that will serve as a unique identifier. + */ + private Long createNextId() { + return nextId.getAndIncrement(); + } + + /** + * Prevents proxy bypass. + * + * @param in Input stream. + */ + private void readObject(ObjectInputStream in) { + throw new IllegalStateException(); + } + + /** + * Custom serialization. + * + * @return the proxy instance that will be actually serialized. + */ + private Object writeReplace() { + final Neuron[] neuronList = neuronMap.values().toArray(new Neuron[0]); + final long[][] neighbourIdList = new long[neuronList.length][]; + + for (int i = 0; i < neuronList.length; i++) { + final Collection neighbours = getNeighbours(neuronList[i]); + final long[] neighboursId = new long[neighbours.size()]; + int count = 0; + for (Neuron n : neighbours) { + neighboursId[count] = n.getIdentifier(); + ++count; + } + neighbourIdList[i] = neighboursId; + } + + return new SerializationProxy(nextId.get(), + featureSize, + neuronList, + neighbourIdList); + } + + /** + * Serialization. + */ + private static class SerializationProxy implements Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130207L; + /** Next identifier. */ + private final long nextId; + /** Number of features. */ + private final int featureSize; + /** Neurons. */ + private final Neuron[] neuronList; + /** Links. */ + private final long[][] neighbourIdList; + + /** + * @param nextId Next available identifier. + * @param featureSize Number of features. + * @param neuronList Neurons. + * @param neighbourIdList Links associated to each of the neurons in + * {@code neuronList}. + */ + SerializationProxy(long nextId, + int featureSize, + Neuron[] neuronList, + long[][] neighbourIdList) { + this.nextId = nextId; + this.featureSize = featureSize; + this.neuronList = neuronList; + this.neighbourIdList = neighbourIdList; + } + + /** + * Custom serialization. + * + * @return the {@link Network} for which this instance is the proxy. + */ + private Object readResolve() { + return new Network(nextId, + featureSize, + neuronList, + neighbourIdList); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Neuron.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Neuron.java new file mode 100644 index 000000000..1211490fc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/Neuron.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +import java.io.Serializable; +import java.io.ObjectInputStream; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicLong; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.util.Precision; + + +/** + * Describes a neuron element of a neural network. + * + * This class aims to be thread-safe. + * + * @since 3.3 + */ +public class Neuron implements Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130207L; + /** Identifier. */ + private final long identifier; + /** Length of the feature set. */ + private final int size; + /** Neuron data. */ + private final AtomicReference features; + /** Number of attempts to update a neuron. */ + private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0); + /** Number of successful updates of a neuron. */ + private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0); + + /** + * Creates a neuron. + * The size of the feature set is fixed to the length of the given + * argument. + *
    + * Constructor is package-private: Neurons must be + * {@link Network#createNeuron(double[]) created} by the network + * instance to which they will belong. + * + * @param identifier Identifier (assigned by the {@link Network}). + * @param features Initial values of the feature set. + */ + Neuron(long identifier, + double[] features) { + this.identifier = identifier; + this.size = features.length; + this.features = new AtomicReference(features.clone()); + } + + /** + * Performs a deep copy of this instance. + * Upon return, the copied and original instances will be independent: + * Updating one will not affect the other. + * + * @return a new instance with the same state as this instance. + * @since 3.6 + */ + public synchronized Neuron copy() { + final Neuron copy = new Neuron(getIdentifier(), + getFeatures()); + copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get()); + copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get()); + + return copy; + } + + /** + * Gets the neuron's identifier. + * + * @return the identifier. + */ + public long getIdentifier() { + return identifier; + } + + /** + * Gets the length of the feature set. + * + * @return the number of features. + */ + public int getSize() { + return size; + } + + /** + * Gets the neuron's features. + * + * @return a copy of the neuron's features. + */ + public double[] getFeatures() { + return features.get().clone(); + } + + /** + * Tries to atomically update the neuron's features. + * Update will be performed only if the expected values match the + * current values.
    + * In effect, when concurrent threads call this method, the state + * could be modified by one, so that it does not correspond to the + * the state assumed by another. + * Typically, a caller {@link #getFeatures() retrieves the current state}, + * and uses it to compute the new state. + * During this computation, another thread might have done the same + * thing, and updated the state: If the current thread were to proceed + * with its own update, it would overwrite the new state (which might + * already have been used by yet other threads). + * To prevent this, the method does not perform the update when a + * concurrent modification has been detected, and returns {@code false}. + * When this happens, the caller should fetch the new current state, + * redo its computation, and call this method again. + * + * @param expect Current values of the features, as assumed by the caller. + * Update will never succeed if the contents of this array does not match + * the values returned by {@link #getFeatures()}. + * @param update Features's new values. + * @return {@code true} if the update was successful, {@code false} + * otherwise. + * @throws DimensionMismatchException if the length of {@code update} is + * not the same as specified in the {@link #Neuron(long,double[]) + * constructor}. + */ + public boolean compareAndSetFeatures(double[] expect, + double[] update) { + if (update.length != size) { + throw new DimensionMismatchException(update.length, size); + } + + // Get the internal reference. Note that this must not be a copy; + // otherwise the "compareAndSet" below will always fail. + final double[] current = features.get(); + if (!containSameValues(current, expect)) { + // Some other thread already modified the state. + return false; + } + + // Increment attempt counter. + numberOfAttemptedUpdates.incrementAndGet(); + + if (features.compareAndSet(current, update.clone())) { + // The current thread could atomically update the state (attempt succeeded). + numberOfSuccessfulUpdates.incrementAndGet(); + return true; + } else { + // Some other thread came first (attempt failed). + return false; + } + } + + /** + * Retrieves the number of calls to the + * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures} + * method. + * Note that if the caller wants to use this method in combination with + * {@link #getNumberOfSuccessfulUpdates()}, additional synchronization + * may be required to ensure consistency. + * + * @return the number of update attempts. + * @since 3.6 + */ + public long getNumberOfAttemptedUpdates() { + return numberOfAttemptedUpdates.get(); + } + + /** + * Retrieves the number of successful calls to the + * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures} + * method. + * Note that if the caller wants to use this method in combination with + * {@link #getNumberOfAttemptedUpdates()}, additional synchronization + * may be required to ensure consistency. + * + * @return the number of successful updates. + * @since 3.6 + */ + public long getNumberOfSuccessfulUpdates() { + return numberOfSuccessfulUpdates.get(); + } + + /** + * Checks whether the contents of both arrays is the same. + * + * @param current Current values. + * @param expect Expected values. + * @throws DimensionMismatchException if the length of {@code expected} + * is not the same as specified in the {@link #Neuron(long,double[]) + * constructor}. + * @return {@code true} if the arrays contain the same values. + */ + private boolean containSameValues(double[] current, + double[] expect) { + if (expect.length != size) { + throw new DimensionMismatchException(expect.length, size); + } + + for (int i = 0; i < size; i++) { + if (!Precision.equals(current[i], expect[i])) { + return false; + } + } + return true; + } + + /** + * Prevents proxy bypass. + * + * @param in Input stream. + */ + private void readObject(ObjectInputStream in) { + throw new IllegalStateException(); + } + + /** + * Custom serialization. + * + * @return the proxy instance that will be actually serialized. + */ + private Object writeReplace() { + return new SerializationProxy(identifier, + features.get()); + } + + /** + * Serialization. + */ + private static class SerializationProxy implements Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130207L; + /** Features. */ + private final double[] features; + /** Identifier. */ + private final long identifier; + + /** + * @param identifier Identifier. + * @param features Features. + */ + SerializationProxy(long identifier, + double[] features) { + this.identifier = identifier; + this.features = features; + } + + /** + * Custom serialization. + * + * @return the {@link Neuron} for which this instance is the proxy. + */ + private Object readResolve() { + return new Neuron(identifier, + features); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java new file mode 100644 index 000000000..a5c752503 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +/** + * Defines neighbourhood types. + * + * @since 3.3 + */ +public enum SquareNeighbourhood { + /** + * : in two dimensions, each (internal) + * neuron has four neighbours. + */ + VON_NEUMANN, + /** + * : in two dimensions, each (internal) + * neuron has eight neighbours. + */ + MOORE, +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/UpdateAction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/UpdateAction.java new file mode 100644 index 000000000..9fae47abb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/UpdateAction.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; + +/** + * Describes how to update the network in response to a training + * sample. + * + * @since 3.3 + */ +public interface UpdateAction { + /** + * Updates the network in response to the sample {@code features}. + * + * @param net Network. + * @param features Training data. + */ + void update(Network net, double[] features); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.java new file mode 100644 index 000000000..dbbe4a156 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.oned; + +import java.io.Serializable; +import java.io.ObjectInputStream; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.FeatureInitializer; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; + +/** + * Neural network with the topology of a one-dimensional line. + * Each neuron defines one point on the line. + * + * @since 3.3 + */ +public class NeuronString implements Serializable { + /** Serial version ID */ + private static final long serialVersionUID = 1L; + /** Underlying network. */ + private final Network network; + /** Number of neurons. */ + private final int size; + /** Wrap. */ + private final boolean wrap; + + /** + * Mapping of the 1D coordinate to the neuron identifiers + * (attributed by the {@link #network} instance). + */ + private final long[] identifiers; + + /** + * Constructor with restricted access, solely used for deserialization. + * + * @param wrap Whether to wrap the dimension (i.e the first and last + * neurons will be linked together). + * @param featuresList Arrays that will initialize the features sets of + * the network's neurons. + * @throws NumberIsTooSmallException if {@code num < 2}. + */ + NeuronString(boolean wrap, + double[][] featuresList) { + size = featuresList.length; + + if (size < 2) { + throw new NumberIsTooSmallException(size, 2, true); + } + + this.wrap = wrap; + + final int fLen = featuresList[0].length; + network = new Network(0, fLen); + identifiers = new long[size]; + + // Add neurons. + for (int i = 0; i < size; i++) { + identifiers[i] = network.createNeuron(featuresList[i]); + } + + // Add links. + createLinks(); + } + + /** + * Creates a one-dimensional network: + * Each neuron not located on the border of the mesh has two + * neurons linked to it. + *
    + * The links are bi-directional. + * Neurons created successively are neighbours (i.e. there are + * links between them). + *
    + * The topology of the network can also be a circle (if the + * dimension is wrapped). + * + * @param num Number of neurons. + * @param wrap Whether to wrap the dimension (i.e the first and last + * neurons will be linked together). + * @param featureInit Arrays that will initialize the features sets of + * the network's neurons. + * @throws NumberIsTooSmallException if {@code num < 2}. + */ + public NeuronString(int num, + boolean wrap, + FeatureInitializer[] featureInit) { + if (num < 2) { + throw new NumberIsTooSmallException(num, 2, true); + } + + size = num; + this.wrap = wrap; + identifiers = new long[num]; + + final int fLen = featureInit.length; + network = new Network(0, fLen); + + // Add neurons. + for (int i = 0; i < num; i++) { + final double[] features = new double[fLen]; + for (int fIndex = 0; fIndex < fLen; fIndex++) { + features[fIndex] = featureInit[fIndex].value(); + } + identifiers[i] = network.createNeuron(features); + } + + // Add links. + createLinks(); + } + + /** + * Retrieves the underlying network. + * A reference is returned (enabling, for example, the network to be + * trained). + * This also implies that calling methods that modify the {@link Network} + * topology may cause this class to become inconsistent. + * + * @return the network. + */ + public Network getNetwork() { + return network; + } + + /** + * Gets the number of neurons. + * + * @return the number of neurons. + */ + public int getSize() { + return size; + } + + /** + * Retrieves the features set from the neuron at location + * {@code i} in the map. + * + * @param i Neuron index. + * @return the features of the neuron at index {@code i}. + * @throws OutOfRangeException if {@code i} is out of range. + */ + public double[] getFeatures(int i) { + if (i < 0 || + i >= size) { + throw new OutOfRangeException(i, 0, size - 1); + } + + return network.getNeuron(identifiers[i]).getFeatures(); + } + + /** + * Creates the neighbour relationships between neurons. + */ + private void createLinks() { + for (int i = 0; i < size - 1; i++) { + network.addLink(network.getNeuron(i), network.getNeuron(i + 1)); + } + for (int i = size - 1; i > 0; i--) { + network.addLink(network.getNeuron(i), network.getNeuron(i - 1)); + } + if (wrap) { + network.addLink(network.getNeuron(0), network.getNeuron(size - 1)); + network.addLink(network.getNeuron(size - 1), network.getNeuron(0)); + } + } + + /** + * Prevents proxy bypass. + * + * @param in Input stream. + */ + private void readObject(ObjectInputStream in) { + throw new IllegalStateException(); + } + + /** + * Custom serialization. + * + * @return the proxy instance that will be actually serialized. + */ + private Object writeReplace() { + final double[][] featuresList = new double[size][]; + for (int i = 0; i < size; i++) { + featuresList[i] = getFeatures(i); + } + + return new SerializationProxy(wrap, + featuresList); + } + + /** + * Serialization. + */ + private static class SerializationProxy implements Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130226L; + /** Wrap. */ + private final boolean wrap; + /** Neurons' features. */ + private final double[][] featuresList; + + /** + * @param wrap Whether the dimension is wrapped. + * @param featuresList List of neurons features. + * {@code neuronList}. + */ + SerializationProxy(boolean wrap, + double[][] featuresList) { + this.wrap = wrap; + this.featuresList = featuresList; + } + + /** + * Custom serialization. + * + * @return the {@link Neuron} for which this instance is the proxy. + */ + private Object readResolve() { + return new NeuronString(wrap, + featuresList); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/package-info.java new file mode 100644 index 000000000..1f3a44362 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/oned/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * One-dimensional neural networks. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.oned; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/package-info.java new file mode 100644 index 000000000..b9cbe5c25 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Neural networks. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.java new file mode 100644 index 000000000..d1aa314c3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +import java.util.Iterator; + +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; + +/** + * Trainer for Kohonen's Self-Organizing Map. + * + * @since 3.3 + */ +public class KohonenTrainingTask implements Runnable { + /** SOFM to be trained. */ + private final Network net; + /** Training data. */ + private final Iterator featuresIterator; + /** Update procedure. */ + private final KohonenUpdateAction updateAction; + + /** + * Creates a (sequential) trainer for the given network. + * + * @param net Network to be trained with the SOFM algorithm. + * @param featuresIterator Training data iterator. + * @param updateAction SOFM update procedure. + */ + public KohonenTrainingTask(Network net, + Iterator featuresIterator, + KohonenUpdateAction updateAction) { + this.net = net; + this.featuresIterator = featuresIterator; + this.updateAction = updateAction; + } + + /** + * {@inheritDoc} + */ + public void run() { + while (featuresIterator.hasNext()) { + updateAction.update(net, featuresIterator.next()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java new file mode 100644 index 000000000..bb039fa00 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicLong; + +import com.fr.third.org.apache.commons.math3.analysis.function.Gaussian; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.MapUtils; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.UpdateAction; + +/** + * Update formula for
    + * Kohonen's Self-Organizing Map. + *
    + * The {@link #update(Network,double[]) update} method modifies the + * features {@code w} of the "winning" neuron and its neighbours + * according to the following rule: + * + * wnew = wold + α e(-d / σ) * (sample - wold) + * + * where + *

      + *
    • α is the current learning rate,
    • + *
    • σ is the current neighbourhood size, and
    • + *
    • {@code d} is the number of links to traverse in order to reach + * the neuron from the winning neuron.
    • + *
    + *
    + * This class is thread-safe as long as the arguments passed to the + * {@link #KohonenUpdateAction(DistanceMeasure,LearningFactorFunction, + * NeighbourhoodSizeFunction) constructor} are instances of thread-safe + * classes. + *
    + * Each call to the {@link #update(Network,double[]) update} method + * will increment the internal counter used to compute the current + * values for + *
      + *
    • the learning rate, and
    • + *
    • the neighbourhood size.
    • + *
    + * Consequently, the function instances that compute those values (passed + * to the constructor of this class) must take into account whether this + * class's instance will be shared by multiple threads, as this will impact + * the training process. + * + * @since 3.3 + */ +public class KohonenUpdateAction implements UpdateAction { + /** Distance function. */ + private final DistanceMeasure distance; + /** Learning factor update function. */ + private final LearningFactorFunction learningFactor; + /** Neighbourhood size update function. */ + private final NeighbourhoodSizeFunction neighbourhoodSize; + /** Number of calls to {@link #update(Network,double[])}. */ + private final AtomicLong numberOfCalls = new AtomicLong(0); + + /** + * @param distance Distance function. + * @param learningFactor Learning factor update function. + * @param neighbourhoodSize Neighbourhood size update function. + */ + public KohonenUpdateAction(DistanceMeasure distance, + LearningFactorFunction learningFactor, + NeighbourhoodSizeFunction neighbourhoodSize) { + this.distance = distance; + this.learningFactor = learningFactor; + this.neighbourhoodSize = neighbourhoodSize; + } + + /** + * {@inheritDoc} + */ + public void update(Network net, + double[] features) { + final long numCalls = numberOfCalls.incrementAndGet() - 1; + final double currentLearning = learningFactor.value(numCalls); + final Neuron best = findAndUpdateBestNeuron(net, + features, + currentLearning); + + final int currentNeighbourhood = neighbourhoodSize.value(numCalls); + // The farther away the neighbour is from the winning neuron, the + // smaller the learning rate will become. + final Gaussian neighbourhoodDecay + = new Gaussian(currentLearning, + 0, + currentNeighbourhood); + + if (currentNeighbourhood > 0) { + // Initial set of neurons only contains the winning neuron. + Collection neighbours = new HashSet(); + neighbours.add(best); + // Winning neuron must be excluded from the neighbours. + final HashSet exclude = new HashSet(); + exclude.add(best); + + int radius = 1; + do { + // Retrieve immediate neighbours of the current set of neurons. + neighbours = net.getNeighbours(neighbours, exclude); + + // Update all the neighbours. + for (Neuron n : neighbours) { + updateNeighbouringNeuron(n, features, neighbourhoodDecay.value(radius)); + } + + // Add the neighbours to the exclude list so that they will + // not be update more than once per training step. + exclude.addAll(neighbours); + ++radius; + } while (radius <= currentNeighbourhood); + } + } + + /** + * Retrieves the number of calls to the {@link #update(Network,double[]) update} + * method. + * + * @return the current number of calls. + */ + public long getNumberOfCalls() { + return numberOfCalls.get(); + } + + /** + * Tries to update a neuron. + * + * @param n Neuron to be updated. + * @param features Training data. + * @param learningRate Learning factor. + * @return {@code true} if the update succeeded, {@code true} if a + * concurrent update has been detected. + */ + private boolean attemptNeuronUpdate(Neuron n, + double[] features, + double learningRate) { + final double[] expect = n.getFeatures(); + final double[] update = computeFeatures(expect, + features, + learningRate); + + return n.compareAndSetFeatures(expect, update); + } + + /** + * Atomically updates the given neuron. + * + * @param n Neuron to be updated. + * @param features Training data. + * @param learningRate Learning factor. + */ + private void updateNeighbouringNeuron(Neuron n, + double[] features, + double learningRate) { + while (true) { + if (attemptNeuronUpdate(n, features, learningRate)) { + break; + } + } + } + + /** + * Searches for the neuron whose features are closest to the given + * sample, and atomically updates its features. + * + * @param net Network. + * @param features Sample data. + * @param learningRate Current learning factor. + * @return the winning neuron. + */ + private Neuron findAndUpdateBestNeuron(Network net, + double[] features, + double learningRate) { + while (true) { + final Neuron best = MapUtils.findBest(features, net, distance); + + if (attemptNeuronUpdate(best, features, learningRate)) { + return best; + } + + // If another thread modified the state of the winning neuron, + // it may not be the best match anymore for the given training + // sample: Hence, the winner search is performed again. + } + } + + /** + * Computes the new value of the features set. + * + * @param current Current values of the features. + * @param sample Training data. + * @param learningRate Learning factor. + * @return the new values for the features. + */ + private double[] computeFeatures(double[] current, + double[] sample, + double learningRate) { + final ArrayRealVector c = new ArrayRealVector(current, false); + final ArrayRealVector s = new ArrayRealVector(sample, false); + // c + learningRate * (s - c) + return s.subtract(c).mapMultiplyToSelf(learningRate).add(c).toArray(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java new file mode 100644 index 000000000..26a02d943 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +/** + * Provides the learning rate as a function of the number of calls + * already performed during the learning task. + * + * @since 3.3 + */ +public interface LearningFactorFunction { + /** + * Computes the learning rate at the current call. + * + * @param numCall Current step of the training task. + * @return the value of the function at {@code numCall}. + */ + double value(long numCall); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.java new file mode 100644 index 000000000..a6e112aff --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util.ExponentialDecayFunction; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction; + +/** + * Factory for creating instances of {@link LearningFactorFunction}. + * + * @since 3.3 + */ +public class LearningFactorFunctionFactory { + /** Class contains only static methods. */ + private LearningFactorFunctionFactory() {} + + /** + * Creates an exponential decay {@link LearningFactorFunction function}. + * It will compute a e-x / b, + * where {@code x} is the (integer) independent variable and + *
      + *
    • a = initValue + *
    • b = -numCall / ln(valueAtNumCall / initValue) + *
    + * + * @param initValue Initial value, i.e. + * {@link LearningFactorFunction#value(long) value(0)}. + * @param valueAtNumCall Value of the function at {@code numCall}. + * @param numCall Argument for which the function returns + * {@code valueAtNumCall}. + * @return the learning factor function. + * @throws OutOfRangeException + * if {@code initValue <= 0} or {@code initValue > 1}. + * @throws NotStrictlyPositiveException + * if {@code valueAtNumCall <= 0}. + * @throws NumberIsTooLargeException + * if {@code valueAtNumCall >= initValue}. + * @throws NotStrictlyPositiveException + * if {@code numCall <= 0}. + */ + public static LearningFactorFunction exponentialDecay(final double initValue, + final double valueAtNumCall, + final long numCall) { + if (initValue <= 0 || + initValue > 1) { + throw new OutOfRangeException(initValue, 0, 1); + } + + return new LearningFactorFunction() { + /** DecayFunction. */ + private final ExponentialDecayFunction decay + = new ExponentialDecayFunction(initValue, valueAtNumCall, numCall); + + /** {@inheritDoc} */ + public double value(long n) { + return decay.value(n); + } + }; + } + + /** + * Creates an sigmoid-like {@code LearningFactorFunction function}. + * The function {@code f} will have the following properties: + *
      + *
    • {@code f(0) = initValue}
    • + *
    • {@code numCall} is the inflexion point
    • + *
    • {@code slope = f'(numCall)}
    • + *
    + * + * @param initValue Initial value, i.e. + * {@link LearningFactorFunction#value(long) value(0)}. + * @param slope Value of the function derivative at {@code numCall}. + * @param numCall Inflexion point. + * @return the learning factor function. + * @throws OutOfRangeException + * if {@code initValue <= 0} or {@code initValue > 1}. + * @throws NumberIsTooLargeException + * if {@code slope >= 0}. + * @throws NotStrictlyPositiveException + * if {@code numCall <= 0}. + */ + public static LearningFactorFunction quasiSigmoidDecay(final double initValue, + final double slope, + final long numCall) { + if (initValue <= 0 || + initValue > 1) { + throw new OutOfRangeException(initValue, 0, 1); + } + + return new LearningFactorFunction() { + /** DecayFunction. */ + private final QuasiSigmoidDecayFunction decay + = new QuasiSigmoidDecayFunction(initValue, slope, numCall); + + /** {@inheritDoc} */ + public double value(long n) { + return decay.value(n); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java new file mode 100644 index 000000000..232bf283c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +/** + * Provides the network neighbourhood's size as a function of the + * number of calls already performed during the learning task. + * The "neighbourhood" is the set of neurons that can be reached + * by traversing at most the number of links returned by this + * function. + * + * @since 3.3 + */ +public interface NeighbourhoodSizeFunction { + /** + * Computes the neighbourhood size at the current call. + * + * @param numCall Current step of the training task. + * @return the value of the function at {@code numCall}. + */ + int value(long numCall); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java new file mode 100644 index 000000000..8f79de0bc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util.ExponentialDecayFunction; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction; + +/** + * Factory for creating instances of {@link NeighbourhoodSizeFunction}. + * + * @since 3.3 + */ +public class NeighbourhoodSizeFunctionFactory { + /** Class contains only static methods. */ + private NeighbourhoodSizeFunctionFactory() {} + + /** + * Creates an exponential decay {@link NeighbourhoodSizeFunction function}. + * It will compute a e-x / b, + * where {@code x} is the (integer) independent variable and + *
      + *
    • a = initValue + *
    • b = -numCall / ln(valueAtNumCall / initValue) + *
    + * + * @param initValue Initial value, i.e. + * {@link NeighbourhoodSizeFunction#value(long) value(0)}. + * @param valueAtNumCall Value of the function at {@code numCall}. + * @param numCall Argument for which the function returns + * {@code valueAtNumCall}. + * @return the neighbourhood size function. + * @throws NotStrictlyPositiveException + * if {@code initValue <= 0}. + * @throws NotStrictlyPositiveException + * if {@code valueAtNumCall <= 0}. + * @throws NumberIsTooLargeException + * if {@code valueAtNumCall >= initValue}. + * @throws NotStrictlyPositiveException + * if {@code numCall <= 0}. + */ + public static NeighbourhoodSizeFunction exponentialDecay(final double initValue, + final double valueAtNumCall, + final long numCall) { + return new NeighbourhoodSizeFunction() { + /** DecayFunction. */ + private final ExponentialDecayFunction decay + = new ExponentialDecayFunction(initValue, valueAtNumCall, numCall); + + /** {@inheritDoc} */ + public int value(long n) { + return (int) FastMath.rint(decay.value(n)); + } + }; + } + + /** + * Creates an sigmoid-like {@code NeighbourhoodSizeFunction function}. + * The function {@code f} will have the following properties: + *
      + *
    • {@code f(0) = initValue}
    • + *
    • {@code numCall} is the inflexion point
    • + *
    • {@code slope = f'(numCall)}
    • + *
    + * + * @param initValue Initial value, i.e. + * {@link NeighbourhoodSizeFunction#value(long) value(0)}. + * @param slope Value of the function derivative at {@code numCall}. + * @param numCall Inflexion point. + * @return the neighbourhood size function. + * @throws NotStrictlyPositiveException + * if {@code initValue <= 0}. + * @throws NumberIsTooLargeException + * if {@code slope >= 0}. + * @throws NotStrictlyPositiveException + * if {@code numCall <= 0}. + */ + public static NeighbourhoodSizeFunction quasiSigmoidDecay(final double initValue, + final double slope, + final long numCall) { + return new NeighbourhoodSizeFunction() { + /** DecayFunction. */ + private final QuasiSigmoidDecayFunction decay + = new QuasiSigmoidDecayFunction(initValue, slope, numCall); + + /** {@inheritDoc} */ + public int value(long n) { + return (int) FastMath.rint(decay.value(n)); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java new file mode 100644 index 000000000..94225b1f0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Self Organizing Feature Map. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java new file mode 100644 index 000000000..d68c9bbad --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Exponential decay function: a e-x / b, + * where {@code x} is the (integer) independent variable. + *
    + * Class is immutable. + * + * @since 3.3 + */ +public class ExponentialDecayFunction { + /** Factor {@code a}. */ + private final double a; + /** Factor {@code 1 / b}. */ + private final double oneOverB; + + /** + * Creates an instance. It will be such that + *
      + *
    • {@code a = initValue}
    • + *
    • {@code b = -numCall / ln(valueAtNumCall / initValue)}
    • + *
    + * + * @param initValue Initial value, i.e. {@link #value(long) value(0)}. + * @param valueAtNumCall Value of the function at {@code numCall}. + * @param numCall Argument for which the function returns + * {@code valueAtNumCall}. + * @throws NotStrictlyPositiveException if {@code initValue <= 0}. + * @throws NotStrictlyPositiveException if {@code valueAtNumCall <= 0}. + * @throws NumberIsTooLargeException if {@code valueAtNumCall >= initValue}. + * @throws NotStrictlyPositiveException if {@code numCall <= 0}. + */ + public ExponentialDecayFunction(double initValue, + double valueAtNumCall, + long numCall) { + if (initValue <= 0) { + throw new NotStrictlyPositiveException(initValue); + } + if (valueAtNumCall <= 0) { + throw new NotStrictlyPositiveException(valueAtNumCall); + } + if (valueAtNumCall >= initValue) { + throw new NumberIsTooLargeException(valueAtNumCall, initValue, false); + } + if (numCall <= 0) { + throw new NotStrictlyPositiveException(numCall); + } + + a = initValue; + oneOverB = -FastMath.log(valueAtNumCall / initValue) / numCall; + } + + /** + * Computes a e-numCall / b. + * + * @param numCall Current step of the training task. + * @return the value of the function at {@code numCall}. + */ + public double value(long numCall) { + return a * FastMath.exp(-numCall * oneOverB); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java new file mode 100644 index 000000000..e8d09140a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util; + +import com.fr.third.org.apache.commons.math3.analysis.function.Logistic; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; + +/** + * Decay function whose shape is similar to a sigmoid. + *
    + * Class is immutable. + * + * @since 3.3 + */ +public class QuasiSigmoidDecayFunction { + /** Sigmoid. */ + private final Logistic sigmoid; + /** See {@link #value(long)}. */ + private final double scale; + + /** + * Creates an instance. + * The function {@code f} will have the following properties: + *
      + *
    • {@code f(0) = initValue}
    • + *
    • {@code numCall} is the inflexion point
    • + *
    • {@code slope = f'(numCall)}
    • + *
    + * + * @param initValue Initial value, i.e. {@link #value(long) value(0)}. + * @param slope Value of the function derivative at {@code numCall}. + * @param numCall Inflexion point. + * @throws NotStrictlyPositiveException if {@code initValue <= 0}. + * @throws NumberIsTooLargeException if {@code slope >= 0}. + * @throws NotStrictlyPositiveException if {@code numCall <= 0}. + */ + public QuasiSigmoidDecayFunction(double initValue, + double slope, + long numCall) { + if (initValue <= 0) { + throw new NotStrictlyPositiveException(initValue); + } + if (slope >= 0) { + throw new NumberIsTooLargeException(slope, 0, false); + } + if (numCall <= 1) { + throw new NotStrictlyPositiveException(numCall); + } + + final double k = initValue; + final double m = numCall; + final double b = 4 * slope / initValue; + final double q = 1; + final double a = 0; + final double n = 1; + sigmoid = new Logistic(k, m, b, q, a, n); + + final double y0 = sigmoid.value(0); + scale = k / y0; + } + + /** + * Computes the value of the learning factor. + * + * @param numCall Current step of the training task. + * @return the value of the function at {@code numCall}. + */ + public double value(long numCall) { + return scale * sigmoid.value(numCall); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java new file mode 100644 index 000000000..81f7d1c42 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Miscellaneous utilities. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.sofm.util; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.java new file mode 100644 index 000000000..0180cf32a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.twod; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; +import java.io.Serializable; +import java.io.ObjectInputStream; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.FeatureInitializer; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.SquareNeighbourhood; + +/** + * Neural network with the topology of a two-dimensional surface. + * Each neuron defines one surface element. + *
    + * This network is primarily intended to represent a + * + * Self Organizing Feature Map. + * + * @see org.apache.commons.math3.ml.neuralnet.sofm + * @since 3.3 + */ +public class NeuronSquareMesh2D + implements Iterable, + Serializable { + /** Serial version ID */ + private static final long serialVersionUID = 1L; + /** Underlying network. */ + private final Network network; + /** Number of rows. */ + private final int numberOfRows; + /** Number of columns. */ + private final int numberOfColumns; + /** Wrap. */ + private final boolean wrapRows; + /** Wrap. */ + private final boolean wrapColumns; + /** Neighbourhood type. */ + private final SquareNeighbourhood neighbourhood; + /** + * Mapping of the 2D coordinates (in the rectangular mesh) to + * the neuron identifiers (attributed by the {@link #network} + * instance). + */ + private final long[][] identifiers; + + /** + * Horizontal (along row) direction. + * @since 3.6 + */ + public enum HorizontalDirection { + /** Column at the right of the current column. */ + RIGHT, + /** Current column. */ + CENTER, + /** Column at the left of the current column. */ + LEFT, + } + /** + * Vertical (along column) direction. + * @since 3.6 + */ + public enum VerticalDirection { + /** Row above the current row. */ + UP, + /** Current row. */ + CENTER, + /** Row below the current row. */ + DOWN, + } + + /** + * Constructor with restricted access, solely used for deserialization. + * + * @param wrapRowDim Whether to wrap the first dimension (i.e the first + * and last neurons will be linked together). + * @param wrapColDim Whether to wrap the second dimension (i.e the first + * and last neurons will be linked together). + * @param neighbourhoodType Neighbourhood type. + * @param featuresList Arrays that will initialize the features sets of + * the network's neurons. + * @throws NumberIsTooSmallException if {@code numRows < 2} or + * {@code numCols < 2}. + */ + NeuronSquareMesh2D(boolean wrapRowDim, + boolean wrapColDim, + SquareNeighbourhood neighbourhoodType, + double[][][] featuresList) { + numberOfRows = featuresList.length; + numberOfColumns = featuresList[0].length; + + if (numberOfRows < 2) { + throw new NumberIsTooSmallException(numberOfRows, 2, true); + } + if (numberOfColumns < 2) { + throw new NumberIsTooSmallException(numberOfColumns, 2, true); + } + + wrapRows = wrapRowDim; + wrapColumns = wrapColDim; + neighbourhood = neighbourhoodType; + + final int fLen = featuresList[0][0].length; + network = new Network(0, fLen); + identifiers = new long[numberOfRows][numberOfColumns]; + + // Add neurons. + for (int i = 0; i < numberOfRows; i++) { + for (int j = 0; j < numberOfColumns; j++) { + identifiers[i][j] = network.createNeuron(featuresList[i][j]); + } + } + + // Add links. + createLinks(); + } + + /** + * Creates a two-dimensional network composed of square cells: + * Each neuron not located on the border of the mesh has four + * neurons linked to it. + *
    + * The links are bi-directional. + *
    + * The topology of the network can also be a cylinder (if one + * of the dimensions is wrapped) or a torus (if both dimensions + * are wrapped). + * + * @param numRows Number of neurons in the first dimension. + * @param wrapRowDim Whether to wrap the first dimension (i.e the first + * and last neurons will be linked together). + * @param numCols Number of neurons in the second dimension. + * @param wrapColDim Whether to wrap the second dimension (i.e the first + * and last neurons will be linked together). + * @param neighbourhoodType Neighbourhood type. + * @param featureInit Array of functions that will initialize the + * corresponding element of the features set of each newly created + * neuron. In particular, the size of this array defines the size of + * feature set. + * @throws NumberIsTooSmallException if {@code numRows < 2} or + * {@code numCols < 2}. + */ + public NeuronSquareMesh2D(int numRows, + boolean wrapRowDim, + int numCols, + boolean wrapColDim, + SquareNeighbourhood neighbourhoodType, + FeatureInitializer[] featureInit) { + if (numRows < 2) { + throw new NumberIsTooSmallException(numRows, 2, true); + } + if (numCols < 2) { + throw new NumberIsTooSmallException(numCols, 2, true); + } + + numberOfRows = numRows; + wrapRows = wrapRowDim; + numberOfColumns = numCols; + wrapColumns = wrapColDim; + neighbourhood = neighbourhoodType; + identifiers = new long[numberOfRows][numberOfColumns]; + + final int fLen = featureInit.length; + network = new Network(0, fLen); + + // Add neurons. + for (int i = 0; i < numRows; i++) { + for (int j = 0; j < numCols; j++) { + final double[] features = new double[fLen]; + for (int fIndex = 0; fIndex < fLen; fIndex++) { + features[fIndex] = featureInit[fIndex].value(); + } + identifiers[i][j] = network.createNeuron(features); + } + } + + // Add links. + createLinks(); + } + + /** + * Constructor with restricted access, solely used for making a + * {@link #copy() deep copy}. + * + * @param wrapRowDim Whether to wrap the first dimension (i.e the first + * and last neurons will be linked together). + * @param wrapColDim Whether to wrap the second dimension (i.e the first + * and last neurons will be linked together). + * @param neighbourhoodType Neighbourhood type. + * @param net Underlying network. + * @param idGrid Neuron identifiers. + */ + private NeuronSquareMesh2D(boolean wrapRowDim, + boolean wrapColDim, + SquareNeighbourhood neighbourhoodType, + Network net, + long[][] idGrid) { + numberOfRows = idGrid.length; + numberOfColumns = idGrid[0].length; + wrapRows = wrapRowDim; + wrapColumns = wrapColDim; + neighbourhood = neighbourhoodType; + network = net; + identifiers = idGrid; + } + + /** + * Performs a deep copy of this instance. + * Upon return, the copied and original instances will be independent: + * Updating one will not affect the other. + * + * @return a new instance with the same state as this instance. + * @since 3.6 + */ + public synchronized NeuronSquareMesh2D copy() { + final long[][] idGrid = new long[numberOfRows][numberOfColumns]; + for (int r = 0; r < numberOfRows; r++) { + for (int c = 0; c < numberOfColumns; c++) { + idGrid[r][c] = identifiers[r][c]; + } + } + + return new NeuronSquareMesh2D(wrapRows, + wrapColumns, + neighbourhood, + network.copy(), + idGrid); + } + + /** + * {@inheritDoc} + * @since 3.6 + */ + public Iterator iterator() { + return network.iterator(); + } + + /** + * Retrieves the underlying network. + * A reference is returned (enabling, for example, the network to be + * trained). + * This also implies that calling methods that modify the {@link Network} + * topology may cause this class to become inconsistent. + * + * @return the network. + */ + public Network getNetwork() { + return network; + } + + /** + * Gets the number of neurons in each row of this map. + * + * @return the number of rows. + */ + public int getNumberOfRows() { + return numberOfRows; + } + + /** + * Gets the number of neurons in each column of this map. + * + * @return the number of column. + */ + public int getNumberOfColumns() { + return numberOfColumns; + } + + /** + * Retrieves the neuron at location {@code (i, j)} in the map. + * The neuron at position {@code (0, 0)} is located at the upper-left + * corner of the map. + * + * @param i Row index. + * @param j Column index. + * @return the neuron at {@code (i, j)}. + * @throws OutOfRangeException if {@code i} or {@code j} is + * out of range. + * + * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection) + */ + public Neuron getNeuron(int i, + int j) { + if (i < 0 || + i >= numberOfRows) { + throw new OutOfRangeException(i, 0, numberOfRows - 1); + } + if (j < 0 || + j >= numberOfColumns) { + throw new OutOfRangeException(j, 0, numberOfColumns - 1); + } + + return network.getNeuron(identifiers[i][j]); + } + + /** + * Retrieves the neuron at {@code (location[0], location[1])} in the map. + * The neuron at position {@code (0, 0)} is located at the upper-left + * corner of the map. + * + * @param row Row index. + * @param col Column index. + * @param alongRowDir Direction along the given {@code row} (i.e. an + * offset will be added to the given column index. + * @param alongColDir Direction along the given {@code col} (i.e. an + * offset will be added to the given row index. + * @return the neuron at the requested location, or {@code null} if + * the location is not on the map. + * + * @see #getNeuron(int,int) + */ + public Neuron getNeuron(int row, + int col, + HorizontalDirection alongRowDir, + VerticalDirection alongColDir) { + final int[] location = getLocation(row, col, alongRowDir, alongColDir); + + return location == null ? null : getNeuron(location[0], location[1]); + } + + /** + * Computes the location of a neighbouring neuron. + * It will return {@code null} if the resulting location is not part + * of the map. + * Position {@code (0, 0)} is at the upper-left corner of the map. + * + * @param row Row index. + * @param col Column index. + * @param alongRowDir Direction along the given {@code row} (i.e. an + * offset will be added to the given column index. + * @param alongColDir Direction along the given {@code col} (i.e. an + * offset will be added to the given row index. + * @return an array of length 2 containing the indices of the requested + * location, or {@code null} if that location is not part of the map. + * + * @see #getNeuron(int,int) + */ + private int[] getLocation(int row, + int col, + HorizontalDirection alongRowDir, + VerticalDirection alongColDir) { + final int colOffset; + switch (alongRowDir) { + case LEFT: + colOffset = -1; + break; + case RIGHT: + colOffset = 1; + break; + case CENTER: + colOffset = 0; + break; + default: + // Should never happen. + throw new MathInternalError(); + } + int colIndex = col + colOffset; + if (wrapColumns) { + if (colIndex < 0) { + colIndex += numberOfColumns; + } else { + colIndex %= numberOfColumns; + } + } + + final int rowOffset; + switch (alongColDir) { + case UP: + rowOffset = -1; + break; + case DOWN: + rowOffset = 1; + break; + case CENTER: + rowOffset = 0; + break; + default: + // Should never happen. + throw new MathInternalError(); + } + int rowIndex = row + rowOffset; + if (wrapRows) { + if (rowIndex < 0) { + rowIndex += numberOfRows; + } else { + rowIndex %= numberOfRows; + } + } + + if (rowIndex < 0 || + rowIndex >= numberOfRows || + colIndex < 0 || + colIndex >= numberOfColumns) { + return null; + } else { + return new int[] { rowIndex, colIndex }; + } + } + + /** + * Creates the neighbour relationships between neurons. + */ + private void createLinks() { + // "linkEnd" will store the identifiers of the "neighbours". + final List linkEnd = new ArrayList(); + final int iLast = numberOfRows - 1; + final int jLast = numberOfColumns - 1; + for (int i = 0; i < numberOfRows; i++) { + for (int j = 0; j < numberOfColumns; j++) { + linkEnd.clear(); + + switch (neighbourhood) { + + case MOORE: + // Add links to "diagonal" neighbours. + if (i > 0) { + if (j > 0) { + linkEnd.add(identifiers[i - 1][j - 1]); + } + if (j < jLast) { + linkEnd.add(identifiers[i - 1][j + 1]); + } + } + if (i < iLast) { + if (j > 0) { + linkEnd.add(identifiers[i + 1][j - 1]); + } + if (j < jLast) { + linkEnd.add(identifiers[i + 1][j + 1]); + } + } + if (wrapRows) { + if (i == 0) { + if (j > 0) { + linkEnd.add(identifiers[iLast][j - 1]); + } + if (j < jLast) { + linkEnd.add(identifiers[iLast][j + 1]); + } + } else if (i == iLast) { + if (j > 0) { + linkEnd.add(identifiers[0][j - 1]); + } + if (j < jLast) { + linkEnd.add(identifiers[0][j + 1]); + } + } + } + if (wrapColumns) { + if (j == 0) { + if (i > 0) { + linkEnd.add(identifiers[i - 1][jLast]); + } + if (i < iLast) { + linkEnd.add(identifiers[i + 1][jLast]); + } + } else if (j == jLast) { + if (i > 0) { + linkEnd.add(identifiers[i - 1][0]); + } + if (i < iLast) { + linkEnd.add(identifiers[i + 1][0]); + } + } + } + if (wrapRows && + wrapColumns) { + if (i == 0 && + j == 0) { + linkEnd.add(identifiers[iLast][jLast]); + } else if (i == 0 && + j == jLast) { + linkEnd.add(identifiers[iLast][0]); + } else if (i == iLast && + j == 0) { + linkEnd.add(identifiers[0][jLast]); + } else if (i == iLast && + j == jLast) { + linkEnd.add(identifiers[0][0]); + } + } + + // Case falls through since the "Moore" neighbourhood + // also contains the neurons that belong to the "Von + // Neumann" neighbourhood. + + // fallthru (CheckStyle) + case VON_NEUMANN: + // Links to preceding and following "row". + if (i > 0) { + linkEnd.add(identifiers[i - 1][j]); + } + if (i < iLast) { + linkEnd.add(identifiers[i + 1][j]); + } + if (wrapRows) { + if (i == 0) { + linkEnd.add(identifiers[iLast][j]); + } else if (i == iLast) { + linkEnd.add(identifiers[0][j]); + } + } + + // Links to preceding and following "column". + if (j > 0) { + linkEnd.add(identifiers[i][j - 1]); + } + if (j < jLast) { + linkEnd.add(identifiers[i][j + 1]); + } + if (wrapColumns) { + if (j == 0) { + linkEnd.add(identifiers[i][jLast]); + } else if (j == jLast) { + linkEnd.add(identifiers[i][0]); + } + } + break; + + default: + throw new MathInternalError(); // Cannot happen. + } + + final Neuron aNeuron = network.getNeuron(identifiers[i][j]); + for (long b : linkEnd) { + final Neuron bNeuron = network.getNeuron(b); + // Link to all neighbours. + // The reverse links will be added as the loop proceeds. + network.addLink(aNeuron, bNeuron); + } + } + } + } + + /** + * Prevents proxy bypass. + * + * @param in Input stream. + */ + private void readObject(ObjectInputStream in) { + throw new IllegalStateException(); + } + + /** + * Custom serialization. + * + * @return the proxy instance that will be actually serialized. + */ + private Object writeReplace() { + final double[][][] featuresList = new double[numberOfRows][numberOfColumns][]; + for (int i = 0; i < numberOfRows; i++) { + for (int j = 0; j < numberOfColumns; j++) { + featuresList[i][j] = getNeuron(i, j).getFeatures(); + } + } + + return new SerializationProxy(wrapRows, + wrapColumns, + neighbourhood, + featuresList); + } + + /** + * Serialization. + */ + private static class SerializationProxy implements Serializable { + /** Serializable. */ + private static final long serialVersionUID = 20130226L; + /** Wrap. */ + private final boolean wrapRows; + /** Wrap. */ + private final boolean wrapColumns; + /** Neighbourhood type. */ + private final SquareNeighbourhood neighbourhood; + /** Neurons' features. */ + private final double[][][] featuresList; + + /** + * @param wrapRows Whether the row dimension is wrapped. + * @param wrapColumns Whether the column dimension is wrapped. + * @param neighbourhood Neighbourhood type. + * @param featuresList List of neurons features. + * {@code neuronList}. + */ + SerializationProxy(boolean wrapRows, + boolean wrapColumns, + SquareNeighbourhood neighbourhood, + double[][][] featuresList) { + this.wrapRows = wrapRows; + this.wrapColumns = wrapColumns; + this.neighbourhood = neighbourhood; + this.featuresList = featuresList; + } + + /** + * Custom serialization. + * + * @return the {@link Neuron} for which this instance is the proxy. + */ + private Object readResolve() { + return new NeuronSquareMesh2D(wrapRows, + wrapColumns, + neighbourhood, + featuresList); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/package-info.java new file mode 100644 index 000000000..d694b1585 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Two-dimensional neural networks. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java new file mode 100644 index 000000000..4aed9ef7a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.ml.neuralnet.MapUtils; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Computes the hit histogram. + * Each bin will contain the number of data for which the corresponding + * neuron is the best matching unit. + * @since 3.6 + */ +public class HitHistogram implements MapDataVisualization { + /** Distance. */ + private final DistanceMeasure distance; + /** Whether to compute relative bin counts. */ + private final boolean normalizeCount; + + /** + * @param normalizeCount Whether to compute relative bin counts. + * If {@code true}, the data count in each bin will be divided by the total + * number of samples. + * @param distance Distance. + */ + public HitHistogram(boolean normalizeCount, + DistanceMeasure distance) { + this.normalizeCount = normalizeCount; + this.distance = distance; + } + + /** {@inheritDoc} */ + public double[][] computeImage(NeuronSquareMesh2D map, + Iterable data) { + final int nR = map.getNumberOfRows(); + final int nC = map.getNumberOfColumns(); + + final LocationFinder finder = new LocationFinder(map); + + // Total number of samples. + int numSamples = 0; + // Hit bins. + final double[][] hit = new double[nR][nC]; + + for (double[] sample : data) { + final Neuron best = MapUtils.findBest(sample, map, distance); + + final LocationFinder.Location loc = finder.getLocation(best); + final int row = loc.getRow(); + final int col = loc.getColumn(); + hit[row][col] += 1; + + ++numSamples; + } + + if (normalizeCount) { + for (int r = 0; r < nR; r++) { + for (int c = 0; c < nC; c++) { + hit[r][c] /= numSamples; + } + } + } + + return hit; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.java new file mode 100644 index 000000000..eccf101b0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import java.util.Map; +import java.util.HashMap; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; + +/** + * Helper class to find the grid coordinates of a neuron. + * @since 3.6 + */ +public class LocationFinder { + /** Identifier to location mapping. */ + private final Map locations = new HashMap(); + + /** + * Container holding a (row, column) pair. + */ + public static class Location { + /** Row index. */ + private final int row; + /** Column index. */ + private final int column; + + /** + * @param row Row index. + * @param column Column index. + */ + public Location(int row, + int column) { + this.row = row; + this.column = column; + } + + /** + * @return the row index. + */ + public int getRow() { + return row; + } + + /** + * @return the column index. + */ + public int getColumn() { + return column; + } + } + + /** + * Builds a finder to retrieve the locations of neurons that + * belong to the given {@code map}. + * + * @param map Map. + * + * @throws MathIllegalStateException if the network contains non-unique + * identifiers. This indicates an inconsistent state due to a bug in + * the construction code of the underlying + * {@link Network network}. + */ + public LocationFinder(NeuronSquareMesh2D map) { + final int nR = map.getNumberOfRows(); + final int nC = map.getNumberOfColumns(); + + for (int r = 0; r < nR; r++) { + for (int c = 0; c < nC; c++) { + final Long id = map.getNeuron(r, c).getIdentifier(); + if (locations.get(id) != null) { + throw new MathIllegalStateException(); + } + locations.put(id, new Location(r, c)); + } + } + } + + /** + * Retrieves a neuron's grid coordinates. + * + * @param n Neuron. + * @return the (row, column) coordinates of {@code n}, or {@code null} + * if no such neuron belongs to the {@link #LocationFinder(NeuronSquareMesh2D) + * map used to build this instance}. + */ + public Location getLocation(Neuron n) { + return locations.get(n.getIdentifier()); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java new file mode 100644 index 000000000..1fe160054 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; + +/** + * Interface for algorithms that compute some metrics of the projection of + * data on a 2D-map. + * @since 3.6 + */ +public interface MapDataVisualization { + /** + * Creates an image of the {@code data} metrics when represented by the + * {@code map}. + * + * @param map Map. + * @param data Data. + * @return a 2D-array (in row major order) representing the metrics. + */ + double[][] computeImage(NeuronSquareMesh2D map, + Iterable data); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java new file mode 100644 index 000000000..dcd0a5b0a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; + +/** + * Interface for algorithms that compute some property of a 2D-map. + * @since 3.6 + */ +public interface MapVisualization { + /** + * Creates an image of the {@code map}. + * + * @param map Map. + * @return a 2D-array (in row major order) representing the property. + */ + double[][] computeImage(NeuronSquareMesh2D map); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java new file mode 100644 index 000000000..4d50af0a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.ml.neuralnet.MapUtils; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Computes the quantization error histogram. + * Each bin will contain the average of the distances between samples + * mapped to the corresponding unit and the weight vector of that unit. + * @since 3.6 + */ +public class QuantizationError implements MapDataVisualization { + /** Distance. */ + private final DistanceMeasure distance; + + /** + * @param distance Distance. + */ + public QuantizationError(DistanceMeasure distance) { + this.distance = distance; + } + + /** {@inheritDoc} */ + public double[][] computeImage(NeuronSquareMesh2D map, + Iterable data) { + final int nR = map.getNumberOfRows(); + final int nC = map.getNumberOfColumns(); + + final LocationFinder finder = new LocationFinder(map); + + // Hit bins. + final int[][] hit = new int[nR][nC]; + // Error bins. + final double[][] error = new double[nR][nC]; + + for (double[] sample : data) { + final Neuron best = MapUtils.findBest(sample, map, distance); + + final LocationFinder.Location loc = finder.getLocation(best); + final int row = loc.getRow(); + final int col = loc.getColumn(); + hit[row][col] += 1; + error[row][col] += distance.compute(sample, best.getFeatures()); + } + + for (int r = 0; r < nR; r++) { + for (int c = 0; c < nC; c++) { + final int count = hit[r][c]; + if (count != 0) { + error[r][c] /= count; + } + } + } + + return error; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java new file mode 100644 index 000000000..4635387e3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.MapUtils; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Visualization of high-dimensional data projection on a 2D-map. + * The method is described in + * + * Using Smoothed Data Histograms for Cluster Visualization in Self-Organizing Maps + *
    + * by Elias Pampalk, Andreas Rauber and Dieter Merkl. + *
    + * @since 3.6 + */ +public class SmoothedDataHistogram implements MapDataVisualization { + /** Smoothing parameter. */ + private final int smoothingBins; + /** Distance. */ + private final DistanceMeasure distance; + /** Normalization factor. */ + private final double membershipNormalization; + + /** + * @param smoothingBins Number of bins. + * @param distance Distance. + */ + public SmoothedDataHistogram(int smoothingBins, + DistanceMeasure distance) { + this.smoothingBins = smoothingBins; + this.distance = distance; + + double sum = 0; + for (int i = 0; i < smoothingBins; i++) { + sum += smoothingBins - i; + } + + this.membershipNormalization = 1d / sum; + } + + /** + * {@inheritDoc} + * + * @throws NumberIsTooSmallException if the size of the {@code map} + * is smaller than the number of {@link #SmoothedDataHistogram(int,DistanceMeasure) + * smoothing bins}. + */ + public double[][] computeImage(NeuronSquareMesh2D map, + Iterable data) { + final int nR = map.getNumberOfRows(); + final int nC = map.getNumberOfColumns(); + + final int mapSize = nR * nC; + if (mapSize < smoothingBins) { + throw new NumberIsTooSmallException(mapSize, smoothingBins, true); + } + + final LocationFinder finder = new LocationFinder(map); + + // Histogram bins. + final double[][] histo = new double[nR][nC]; + + for (double[] sample : data) { + final Neuron[] sorted = MapUtils.sort(sample, + map.getNetwork(), + distance); + for (int i = 0; i < smoothingBins; i++) { + final LocationFinder.Location loc = finder.getLocation(sorted[i]); + final int row = loc.getRow(); + final int col = loc.getColumn(); + histo[row][col] += (smoothingBins - i) * membershipNormalization; + } + } + + return histo; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java new file mode 100644 index 000000000..c64aa627c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import com.fr.third.org.apache.commons.math3.util.Pair; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.MapUtils; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * Computes the topographic error histogram. + * Each bin will contain the number of data for which the first and + * second best matching units are not adjacent in the map. + * @since 3.6 + */ +public class TopographicErrorHistogram implements MapDataVisualization { + /** Distance. */ + private final DistanceMeasure distance; + /** Whether to compute relative bin counts. */ + private final boolean relativeCount; + + /** + * @param relativeCount Whether to compute relative bin counts. + * If {@code true}, the data count in each bin will be divided by the total + * number of samples mapped to the neuron represented by that bin. + * @param distance Distance. + */ + public TopographicErrorHistogram(boolean relativeCount, + DistanceMeasure distance) { + this.relativeCount = relativeCount; + this.distance = distance; + } + + /** {@inheritDoc} */ + public double[][] computeImage(NeuronSquareMesh2D map, + Iterable data) { + final int nR = map.getNumberOfRows(); + final int nC = map.getNumberOfColumns(); + + final Network net = map.getNetwork(); + final LocationFinder finder = new LocationFinder(map); + + // Hit bins. + final int[][] hit = new int[nR][nC]; + // Error bins. + final double[][] error = new double[nR][nC]; + + for (double[] sample : data) { + final Pair p = MapUtils.findBestAndSecondBest(sample, map, distance); + final Neuron best = p.getFirst(); + + final LocationFinder.Location loc = finder.getLocation(best); + final int row = loc.getRow(); + final int col = loc.getColumn(); + hit[row][col] += 1; + + if (!net.getNeighbours(best).contains(p.getSecond())) { + // Increment count if first and second best matching units + // are not neighbours. + error[row][col] += 1; + } + } + + if (relativeCount) { + for (int r = 0; r < nR; r++) { + for (int c = 0; c < nC; c++) { + error[r][c] /= hit[r][c]; + } + } + } + + return error; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java new file mode 100644 index 000000000..6a5974c9e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.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 com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; + +import java.util.Collection; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Neuron; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.SquareNeighbourhood; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.Network; +import com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D; +import com.fr.third.org.apache.commons.math3.ml.distance.DistanceMeasure; + +/** + * U-Matrix + * visualization of high-dimensional data projection. + * @since 3.6 + */ +public class UnifiedDistanceMatrix implements MapVisualization { + /** Whether to show distance between each pair of neighbouring units. */ + private final boolean individualDistances; + /** Distance. */ + private final DistanceMeasure distance; + + /** + * Simple constructor. + * + * @param individualDistances If {@code true}, the 8 individual + * inter-units distances will be {@link #computeImage(NeuronSquareMesh2D) + * computed}. They will be stored in additional pixels around each of + * the original units of the 2D-map. The additional pixels that lie + * along a "diagonal" are shared by two pairs of units: their + * value will be set to the average distance between the units belonging + * to each of the pairs. The value zero will be stored in the pixel + * corresponding to the location of a unit of the 2D-map. + *
    + * If {@code false}, only the average distance between a unit and all its + * neighbours will be computed (and stored in the pixel corresponding to + * that unit of the 2D-map). In that case, the number of neighbours taken + * into account depends on the network's + * {@link SquareNeighbourhood + * neighbourhood type}. + * @param distance Distance. + */ + public UnifiedDistanceMatrix(boolean individualDistances, + DistanceMeasure distance) { + this.individualDistances = individualDistances; + this.distance = distance; + } + + /** {@inheritDoc} */ + public double[][] computeImage(NeuronSquareMesh2D map) { + if (individualDistances) { + return individualDistances(map); + } else { + return averageDistances(map); + } + } + + /** + * Computes the distances between a unit of the map and its + * neighbours. + * The image will contain more pixels than the number of neurons + * in the given {@code map} because each neuron has 8 neighbours. + * The value zero will be stored in the pixels corresponding to + * the location of a map unit. + * + * @param map Map. + * @return an image representing the individual distances. + */ + private double[][] individualDistances(NeuronSquareMesh2D map) { + final int numRows = map.getNumberOfRows(); + final int numCols = map.getNumberOfColumns(); + + final double[][] uMatrix = new double[numRows * 2 + 1][numCols * 2 + 1]; + + // 1. + // Fill right and bottom slots of each unit's location with the + // distance between the current unit and each of the two neighbours, + // respectively. + for (int i = 0; i < numRows; i++) { + // Current unit's row index in result image. + final int iR = 2 * i + 1; + + for (int j = 0; j < numCols; j++) { + // Current unit's column index in result image. + final int jR = 2 * j + 1; + + final double[] current = map.getNeuron(i, j).getFeatures(); + Neuron neighbour; + + // Right neighbour. + neighbour = map.getNeuron(i, j, + NeuronSquareMesh2D.HorizontalDirection.RIGHT, + NeuronSquareMesh2D.VerticalDirection.CENTER); + if (neighbour != null) { + uMatrix[iR][jR + 1] = distance.compute(current, + neighbour.getFeatures()); + } + + // Bottom-center neighbour. + neighbour = map.getNeuron(i, j, + NeuronSquareMesh2D.HorizontalDirection.CENTER, + NeuronSquareMesh2D.VerticalDirection.DOWN); + if (neighbour != null) { + uMatrix[iR + 1][jR] = distance.compute(current, + neighbour.getFeatures()); + } + } + } + + // 2. + // Fill the bottom-rigth slot of each unit's location with the average + // of the distances between + // * the current unit and its bottom-right neighbour, and + // * the bottom-center neighbour and the right neighbour. + for (int i = 0; i < numRows; i++) { + // Current unit's row index in result image. + final int iR = 2 * i + 1; + + for (int j = 0; j < numCols; j++) { + // Current unit's column index in result image. + final int jR = 2 * j + 1; + + final Neuron current = map.getNeuron(i, j); + final Neuron right = map.getNeuron(i, j, + NeuronSquareMesh2D.HorizontalDirection.RIGHT, + NeuronSquareMesh2D.VerticalDirection.CENTER); + final Neuron bottom = map.getNeuron(i, j, + NeuronSquareMesh2D.HorizontalDirection.CENTER, + NeuronSquareMesh2D.VerticalDirection.DOWN); + final Neuron bottomRight = map.getNeuron(i, j, + NeuronSquareMesh2D.HorizontalDirection.RIGHT, + NeuronSquareMesh2D.VerticalDirection.DOWN); + + final double current2BottomRight = bottomRight == null ? + 0 : + distance.compute(current.getFeatures(), + bottomRight.getFeatures()); + final double right2Bottom = (right == null || + bottom == null) ? + 0 : + distance.compute(right.getFeatures(), + bottom.getFeatures()); + + // Bottom-right slot. + uMatrix[iR + 1][jR + 1] = 0.5 * (current2BottomRight + right2Bottom); + } + } + + // 3. Copy last row into first row. + final int lastRow = uMatrix.length - 1; + uMatrix[0] = uMatrix[lastRow]; + + // 4. + // Copy last column into first column. + final int lastCol = uMatrix[0].length - 1; + for (int r = 0; r < lastRow; r++) { + uMatrix[r][0] = uMatrix[r][lastCol]; + } + + return uMatrix; + } + + /** + * Computes the distances between a unit of the map and its neighbours. + * + * @param map Map. + * @return an image representing the average distances. + */ + private double[][] averageDistances(NeuronSquareMesh2D map) { + final int numRows = map.getNumberOfRows(); + final int numCols = map.getNumberOfColumns(); + final double[][] uMatrix = new double[numRows][numCols]; + + final Network net = map.getNetwork(); + + for (int i = 0; i < numRows; i++) { + for (int j = 0; j < numCols; j++) { + final Neuron neuron = map.getNeuron(i, j); + final Collection neighbours = net.getNeighbours(neuron); + final double[] features = neuron.getFeatures(); + + double d = 0; + int count = 0; + for (Neuron n : neighbours) { + ++count; + d += distance.compute(features, n.getFeatures()); + } + + uMatrix[i][j] = d / count; + } + } + + return uMatrix; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java new file mode 100644 index 000000000..2709569f6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Utilities to visualize two-dimensional neural networks. + */ + +package com.fr.third.org.apache.commons.math3.ml.neuralnet.twod.util; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/package-info.java new file mode 100644 index 000000000..ca904b17b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ml/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Base package for machine learning algorithms. + */ +package com.fr.third.org.apache.commons.math3.ml; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractFieldIntegrator.java new file mode 100644 index 000000000..6fb46de6d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractFieldIntegrator.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 com.fr.third.org.apache.commons.math3.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 com.fr.third.org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.ode.events.FieldEventHandler; +import com.fr.third.org.apache.commons.math3.ode.events.FieldEventState; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepHandler; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * Base class managing common boilerplate for all integrators. + * @param the type of the field elements + * @since 3.6 + */ +public abstract class AbstractFieldIntegrator> implements FirstOrderFieldIntegrator { + + /** Default relative accuracy. */ + private static final double DEFAULT_RELATIVE_ACCURACY = 1e-14; + + /** Default function value accuracy. */ + private static final double DEFAULT_FUNCTION_VALUE_ACCURACY = 1e-15; + + /** Step handler. */ + private Collection> stepHandlers; + + /** Current step start. */ + private FieldODEStateAndDerivative stepStart; + + /** Current stepsize. */ + private T stepSize; + + /** Indicator for last step. */ + private boolean isLastStep; + + /** Indicator that a state or derivative reset was triggered by some event. */ + private boolean resetOccurred; + + /** Field to which the time and state vector elements belong. */ + private final Field field; + + /** Events states. */ + private Collection> eventsStates; + + /** Initialization indicator of events states. */ + private boolean statesInitialized; + + /** Name of the method. */ + private final String name; + + /** Counter for number of evaluations. */ + private IntegerSequence.Incrementor evaluations; + + /** Differential equations to integrate. */ + private transient FieldExpandableODE equations; + + /** Build an instance. + * @param field field to which the time and state vector elements belong + * @param name name of the method + */ + protected AbstractFieldIntegrator(final Field field, final String name) { + this.field = field; + this.name = name; + stepHandlers = new ArrayList>(); + stepStart = null; + stepSize = null; + eventsStates = new ArrayList>(); + statesInitialized = false; + evaluations = IntegerSequence.Incrementor.create().withMaximalCount(Integer.MAX_VALUE); + } + + /** Get the field to which state vector elements belong. + * @return field to which state vector elements belong + */ + public Field getField() { + return field; + } + + /** {@inheritDoc} */ + public String getName() { + return name; + } + + /** {@inheritDoc} */ + public void addStepHandler(final FieldStepHandler handler) { + stepHandlers.add(handler); + } + + /** {@inheritDoc} */ + public Collection> getStepHandlers() { + return Collections.unmodifiableCollection(stepHandlers); + } + + /** {@inheritDoc} */ + public void clearStepHandlers() { + stepHandlers.clear(); + } + + /** {@inheritDoc} */ + public void addEventHandler(final FieldEventHandler handler, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount) { + addEventHandler(handler, maxCheckInterval, convergence, + maxIterationCount, + new FieldBracketingNthOrderBrentSolver(field.getZero().add(DEFAULT_RELATIVE_ACCURACY), + field.getZero().add(convergence), + field.getZero().add(DEFAULT_FUNCTION_VALUE_ACCURACY), + 5)); + } + + /** {@inheritDoc} */ + public void addEventHandler(final FieldEventHandler handler, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount, + final BracketedRealFieldUnivariateSolver solver) { + eventsStates.add(new FieldEventState(handler, maxCheckInterval, field.getZero().add(convergence), + maxIterationCount, solver)); + } + + /** {@inheritDoc} */ + public Collection> getEventHandlers() { + final List> list = new ArrayList>(eventsStates.size()); + for (FieldEventState state : eventsStates) { + list.add(state.getEventHandler()); + } + return Collections.unmodifiableCollection(list); + } + + /** {@inheritDoc} */ + public void clearEventHandlers() { + eventsStates.clear(); + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative getCurrentStepStart() { + return stepStart; + } + + /** {@inheritDoc} */ + public T getCurrentSignedStepsize() { + return stepSize; + } + + /** {@inheritDoc} */ + public void setMaxEvaluations(int maxEvaluations) { + evaluations = evaluations.withMaximalCount((maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** Prepare the start of an integration. + * @param eqn equations to integrate + * @param t0 start value of the independent time variable + * @param y0 array containing the start value of the state vector + * @param t target time for the integration + * @return initial state with derivatives added + */ + protected FieldODEStateAndDerivative initIntegration(final FieldExpandableODE eqn, + final T t0, final T[] y0, final T t) { + + this.equations = eqn; + evaluations = evaluations.withStart(0); + + // initialize ODE + eqn.init(t0, y0, t); + + // set up derivatives of initial state + final T[] y0Dot = computeDerivatives(t0, y0); + final FieldODEStateAndDerivative state0 = new FieldODEStateAndDerivative(t0, y0, y0Dot); + + // initialize event handlers + for (final FieldEventState state : eventsStates) { + state.getEventHandler().init(state0, t); + } + + // initialize step handlers + for (FieldStepHandler handler : stepHandlers) { + handler.init(state0, t); + } + + setStateInitialized(false); + + return state0; + + } + + /** Get the differential equations to integrate. + * @return differential equations to integrate + */ + protected FieldExpandableODE getEquations() { + return equations; + } + + /** Get the evaluations counter. + * @return evaluations counter + */ + protected IntegerSequence.Incrementor getEvaluationsCounter() { + return evaluations; + } + + /** Compute the derivatives and check the number of evaluations. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @return state completed with derivatives + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NullPointerException if the ODE equations have not been set (i.e. if this method + * is called outside of a call to {@link #integrate(FieldExpandableODE, FieldODEState, + * RealFieldElement) integrate} + */ + public T[] computeDerivatives(final T t, final T[] y) + throws DimensionMismatchException, MaxCountExceededException, NullPointerException { + evaluations.increment(); + return equations.computeDerivatives(t, y); + } + + /** Set the stateInitialized flag. + *

    This method must be called by integrators with the value + * {@code false} before they start integration, so a proper lazy + * initialization is done automatically on the first step.

    + * @param stateInitialized new value for the flag + */ + protected void setStateInitialized(final boolean stateInitialized) { + this.statesInitialized = stateInitialized; + } + + /** Accept a step, triggering events and step handlers. + * @param interpolator step interpolator + * @param tEnd final integration time + * @return state at end of step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + protected FieldODEStateAndDerivative acceptStep(final AbstractFieldStepInterpolator interpolator, + final T tEnd) + throws MaxCountExceededException, DimensionMismatchException, NoBracketingException { + + FieldODEStateAndDerivative previousState = interpolator.getGlobalPreviousState(); + final FieldODEStateAndDerivative currentState = interpolator.getGlobalCurrentState(); + + // initialize the events states if needed + if (! statesInitialized) { + for (FieldEventState 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> occurringEvents = new TreeSet>(new Comparator>() { + + /** {@inheritDoc} */ + public int compare(FieldEventState es0, FieldEventState es1) { + return orderingSign * Double.compare(es0.getEventTime().getReal(), es1.getEventTime().getReal()); + } + + }); + + for (final FieldEventState state : eventsStates) { + if (state.evaluateStep(interpolator)) { + // the event occurs during the current step + occurringEvents.add(state); + } + } + + AbstractFieldStepInterpolator restricted = interpolator; + while (!occurringEvents.isEmpty()) { + + // handle the chronologically first event + final Iterator> iterator = occurringEvents.iterator(); + final FieldEventState currentEvent = iterator.next(); + iterator.remove(); + + // get state at event time + final FieldODEStateAndDerivative eventState = restricted.getInterpolatedState(currentEvent.getEventTime()); + + // restrict the interpolator to the first part of the step, up to the event + restricted = restricted.restrictStep(previousState, eventState); + + // advance all event states to current time + for (final FieldEventState state : eventsStates) { + state.stepAccepted(eventState); + isLastStep = isLastStep || state.stop(); + } + + // handle the first part of the step, up to the event + for (final FieldStepHandler handler : stepHandlers) { + handler.handleStep(restricted, isLastStep); + } + + if (isLastStep) { + // the event asked to stop integration + return eventState; + } + + FieldODEState newState = null; + resetOccurred = false; + for (final FieldEventState state : eventsStates) { + newState = state.reset(eventState); + if (newState != null) { + // some event handler has triggered changes that + // invalidate the derivatives, we need to recompute them + final T[] y = equations.getMapper().mapState(newState); + final T[] yDot = computeDerivatives(newState.getTime(), y); + resetOccurred = true; + return equations.getMapper().mapStateAndDerivative(newState.getTime(), y, yDot); + } + } + + // prepare handling of the remaining part of the step + previousState = eventState; + restricted = restricted.restrictStep(eventState, currentState); + + // check if the same event occurs again in the remaining part of the step + if (currentEvent.evaluateStep(restricted)) { + // the event occurs during the current step + occurringEvents.add(currentEvent); + } + + } + + // last part of the step, after the last event + for (final FieldEventState state : eventsStates) { + state.stepAccepted(currentState); + isLastStep = isLastStep || state.stop(); + } + isLastStep = isLastStep || currentState.getTime().subtract(tEnd).abs().getReal() <= FastMath.ulp(tEnd.getReal()); + + // handle the remaining part of the step, after all events if any + for (FieldStepHandler handler : stepHandlers) { + handler.handleStep(restricted, isLastStep); + } + + return currentState; + + } + + /** Check the integration span. + * @param eqn set of differential equations + * @param t target time for the integration + * @exception NumberIsTooSmallException if integration span is too small + * @exception DimensionMismatchException if adaptive step size integrators + * tolerance arrays dimensions are not compatible with equations settings + */ + protected void sanityChecks(final FieldODEState eqn, final T t) + throws NumberIsTooSmallException, DimensionMismatchException { + + final double threshold = 1000 * FastMath.ulp(FastMath.max(FastMath.abs(eqn.getTime().getReal()), + FastMath.abs(t.getReal()))); + final double dt = eqn.getTime().subtract(t).abs().getReal(); + if (dt <= threshold) { + throw new NumberIsTooSmallException(LocalizedFormats.TOO_SMALL_INTEGRATION_INTERVAL, + dt, threshold, false); + } + + } + + /** Check if a reset occurred while last step was accepted. + * @return true if a reset occurred while last step was accepted + */ + protected boolean resetOccurred() { + return resetOccurred; + } + + /** Set the current step size. + * @param stepSize step size to set + */ + protected void setStepSize(final T stepSize) { + this.stepSize = stepSize; + } + + /** Get the current step size. + * @return current step size + */ + protected T getStepSize() { + return stepSize; + } + /** Set current step start. + * @param stepStart step start + */ + protected void setStepStart(final FieldODEStateAndDerivative stepStart) { + this.stepStart = stepStart; + } + + /** Getcurrent step start. + * @return current step start + */ + protected FieldODEStateAndDerivative getStepStart() { + return stepStart; + } + + /** Set the last state flag. + * @param isLastStep if true, this step is the last one + */ + protected void setIsLastStep(final boolean isLastStep) { + this.isLastStep = isLastStep; + } + + /** Check if this step is the last one. + * @return true if this step is the last one + */ + protected boolean isLastStep() { + return isLastStep; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractIntegrator.java new file mode 100644 index 000000000..4b83852c2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractIntegrator.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 com.fr.third.org.apache.commons.math3.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 com.fr.third.org.apache.commons.math3.analysis.solvers.BracketingNthOrderBrentSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.ode.events.EventHandler; +import com.fr.third.org.apache.commons.math3.ode.events.EventState; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Base class managing common boilerplate for all integrators. + * @since 2.0 + */ +public abstract class AbstractIntegrator implements FirstOrderIntegrator { + + /** Step handler. */ + protected Collection 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 eventsStates; + + /** Initialization indicator of events states. */ + private boolean statesInitialized; + + /** Name of the method. */ + private final String name; + + /** Counter for number of evaluations. */ + private IntegerSequence.Incrementor evaluations; + + /** Differential equations to integrate. */ + private transient ExpandableStatefulODE expandable; + + /** Build an instance. + * @param name name of the method + */ + public AbstractIntegrator(final String name) { + this.name = name; + stepHandlers = new ArrayList(); + stepStart = Double.NaN; + stepSize = Double.NaN; + eventsStates = new ArrayList(); + statesInitialized = false; + evaluations = IntegerSequence.Incrementor.create().withMaximalCount(Integer.MAX_VALUE); + } + + /** Build an instance with a null name. + */ + protected AbstractIntegrator() { + this(null); + } + + /** {@inheritDoc} */ + public String getName() { + return name; + } + + /** {@inheritDoc} */ + public void addStepHandler(final StepHandler handler) { + stepHandlers.add(handler); + } + + /** {@inheritDoc} */ + public Collection getStepHandlers() { + return Collections.unmodifiableCollection(stepHandlers); + } + + /** {@inheritDoc} */ + public void clearStepHandlers() { + stepHandlers.clear(); + } + + /** {@inheritDoc} */ + public void addEventHandler(final EventHandler handler, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount) { + addEventHandler(handler, maxCheckInterval, convergence, + maxIterationCount, + new BracketingNthOrderBrentSolver(convergence, 5)); + } + + /** {@inheritDoc} */ + public void addEventHandler(final EventHandler handler, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount, + final UnivariateSolver solver) { + eventsStates.add(new EventState(handler, maxCheckInterval, convergence, + maxIterationCount, solver)); + } + + /** {@inheritDoc} */ + public Collection getEventHandlers() { + final List list = new ArrayList(eventsStates.size()); + for (EventState state : eventsStates) { + list.add(state.getEventHandler()); + } + return Collections.unmodifiableCollection(list); + } + + /** {@inheritDoc} */ + public void clearEventHandlers() { + eventsStates.clear(); + } + + /** {@inheritDoc} */ + public double getCurrentStepStart() { + return stepStart; + } + + /** {@inheritDoc} */ + public double getCurrentSignedStepsize() { + return stepSize; + } + + /** {@inheritDoc} */ + public void setMaxEvaluations(int maxEvaluations) { + evaluations = evaluations.withMaximalCount((maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** Prepare the start of an integration. + * @param t0 start value of the independent time variable + * @param y0 array containing the start value of the state vector + * @param t target time for the integration + */ + protected void initIntegration(final double t0, final double[] y0, final double t) { + + evaluations = evaluations.withStart(0); + + for (final EventState state : eventsStates) { + state.setExpandable(expandable); + state.getEventHandler().init(t0, y0, t); + } + + for (StepHandler handler : stepHandlers) { + handler.init(t0, y0, t); + } + + setStateInitialized(false); + + } + + /** Set the equations. + * @param equations equations to set + */ + protected void setEquations(final ExpandableStatefulODE equations) { + this.expandable = equations; + } + + /** Get the differential equations to integrate. + * @return differential equations to integrate + * @since 3.2 + */ + protected ExpandableStatefulODE getExpandable() { + return expandable; + } + + /** Get the evaluations counter. + * @return evaluations counter + * @since 3.2 + * @deprecated as of 3.6 replaced with {@link #getCounter()} + */ + @Deprecated + protected Incrementor getEvaluationsCounter() { + return Incrementor.wrap(evaluations); + } + + /** Get the evaluations counter. + * @return evaluations counter + * @since 3.6 + */ + protected IntegerSequence.Incrementor getCounter() { + return evaluations; + } + + /** {@inheritDoc} */ + public double integrate(final FirstOrderDifferentialEquations equations, + final double t0, final double[] y0, final double t, final double[] y) + throws DimensionMismatchException, NumberIsTooSmallException, + MaxCountExceededException, NoBracketingException { + + if (y0.length != equations.getDimension()) { + throw new DimensionMismatchException(y0.length, equations.getDimension()); + } + if (y.length != equations.getDimension()) { + throw new DimensionMismatchException(y.length, equations.getDimension()); + } + + // prepare expandable stateful equations + final ExpandableStatefulODE expandableODE = new ExpandableStatefulODE(equations); + expandableODE.setTime(t0); + expandableODE.setPrimaryState(y0); + + // perform integration + integrate(expandableODE, t); + + // extract results back from the stateful equations + System.arraycopy(expandableODE.getPrimaryState(), 0, y, 0, y.length); + return expandableODE.getTime(); + + } + + /** Integrate a set of differential equations up to the given time. + *

    This method solves an Initial Value Problem (IVP).

    + *

    The set of differential equations is composed of a main set, which + * can be extended by some sets of secondary equations. The set of + * equations must be already set up with initial time and partial states. + * At integration completion, the final time and partial states will be + * available in the same object.

    + *

    Since this method stores some internal state variables made + * available in its public interface during integration ({@link + * #getCurrentSignedStepsize()}), it is not thread-safe.

    + * @param equations complete set of differential equations to integrate + * @param t target time for the integration + * (can be set to a value smaller than t0 for backward integration) + * @exception NumberIsTooSmallException if integration step is too small + * @throws DimensionMismatchException if the dimension of the complete state does not + * match the complete equations sets dimension + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + */ + public abstract void integrate(ExpandableStatefulODE equations, double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** Compute the derivatives and check the number of evaluations. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param yDot placeholder array where to put the time derivative of the state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + * @exception NullPointerException if the ODE equations have not been set (i.e. if this method + * is called outside of a call to {@link #integrate(ExpandableStatefulODE, double)} or {@link + * #integrate(FirstOrderDifferentialEquations, double, double[], double, double[])}) + */ + public void computeDerivatives(final double t, final double[] y, final double[] yDot) + throws MaxCountExceededException, DimensionMismatchException, NullPointerException { + evaluations.increment(); + expandable.computeDerivatives(t, y, yDot); + } + + /** Set the stateInitialized flag. + *

    This method must be called by integrators with the value + * {@code false} before they start integration, so a proper lazy + * initialization is done automatically on the first step.

    + * @param stateInitialized new value for the flag + * @since 2.2 + */ + protected void setStateInitialized(final boolean stateInitialized) { + this.statesInitialized = stateInitialized; + } + + /** Accept a step, triggering events and step handlers. + * @param interpolator step interpolator + * @param y state vector at step end time, must be reset if an event + * asks for resetting or if an events stops integration during the step + * @param yDot placeholder array where to put the time derivative of the state vector + * @param tEnd final integration time + * @return time at end of step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + * @since 2.2 + */ + protected double acceptStep(final AbstractStepInterpolator interpolator, + final double[] y, final double[] yDot, final double tEnd) + throws MaxCountExceededException, DimensionMismatchException, NoBracketingException { + + double previousT = interpolator.getGlobalPreviousTime(); + final double currentT = interpolator.getGlobalCurrentTime(); + + // initialize the events states if needed + if (! statesInitialized) { + for (EventState state : eventsStates) { + state.reinitializeBegin(interpolator); + } + statesInitialized = true; + } + + // search for next events that may occur during the step + final int orderingSign = interpolator.isForward() ? +1 : -1; + SortedSet occurringEvents = new TreeSet(new Comparator() { + + /** {@inheritDoc} */ + public int compare(EventState es0, EventState es1) { + return orderingSign * Double.compare(es0.getEventTime(), es1.getEventTime()); + } + + }); + + for (final EventState state : eventsStates) { + if (state.evaluateStep(interpolator)) { + // the event occurs during the current step + occurringEvents.add(state); + } + } + + while (!occurringEvents.isEmpty()) { + + // handle the chronologically first event + final Iterator iterator = occurringEvents.iterator(); + final EventState currentEvent = iterator.next(); + iterator.remove(); + + // restrict the interpolator to the first part of the step, up to the event + final double eventT = currentEvent.getEventTime(); + interpolator.setSoftPreviousTime(previousT); + interpolator.setSoftCurrentTime(eventT); + + // get state at event time + interpolator.setInterpolatedTime(eventT); + final double[] eventYComplete = new double[y.length]; + expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(), + eventYComplete); + int index = 0; + for (EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++), + eventYComplete); + } + + // advance all event states to current time + for (final EventState state : eventsStates) { + state.stepAccepted(eventT, eventYComplete); + isLastStep = isLastStep || state.stop(); + } + + // handle the first part of the step, up to the event + for (final StepHandler handler : stepHandlers) { + handler.handleStep(interpolator, isLastStep); + } + + if (isLastStep) { + // the event asked to stop integration + System.arraycopy(eventYComplete, 0, y, 0, y.length); + return eventT; + } + + boolean needReset = false; + resetOccurred = false; + needReset = currentEvent.reset(eventT, eventYComplete); + if (needReset) { + // some event handler has triggered changes that + // invalidate the derivatives, we need to recompute them + interpolator.setInterpolatedTime(eventT); + System.arraycopy(eventYComplete, 0, y, 0, y.length); + computeDerivatives(eventT, y, yDot); + resetOccurred = true; + return eventT; + } + + // prepare handling of the remaining part of the step + previousT = eventT; + interpolator.setSoftPreviousTime(eventT); + interpolator.setSoftCurrentTime(currentT); + + // check if the same event occurs again in the remaining part of the step + if (currentEvent.evaluateStep(interpolator)) { + // the event occurs during the current step + occurringEvents.add(currentEvent); + } + + } + + // last part of the step, after the last event + interpolator.setInterpolatedTime(currentT); + final double[] currentY = new double[y.length]; + expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(), + currentY); + int index = 0; + for (EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++), + currentY); + } + for (final EventState state : eventsStates) { + state.stepAccepted(currentT, currentY); + isLastStep = isLastStep || state.stop(); + } + isLastStep = isLastStep || Precision.equals(currentT, tEnd, 1); + + // handle the remaining part of the step, after all events if any + for (StepHandler handler : stepHandlers) { + handler.handleStep(interpolator, isLastStep); + } + + return currentT; + + } + + /** Check the integration span. + * @param equations set of differential equations + * @param t target time for the integration + * @exception NumberIsTooSmallException if integration span is too small + * @exception DimensionMismatchException if adaptive step size integrators + * tolerance arrays dimensions are not compatible with equations settings + */ + protected void sanityChecks(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException { + + final double threshold = 1000 * FastMath.ulp(FastMath.max(FastMath.abs(equations.getTime()), + FastMath.abs(t))); + final double dt = FastMath.abs(equations.getTime() - t); + if (dt <= threshold) { + throw new NumberIsTooSmallException(LocalizedFormats.TOO_SMALL_INTEGRATION_INTERVAL, + dt, threshold, false); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractParameterizable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractParameterizable.java new file mode 100644 index 000000000..e3bc024cb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/AbstractParameterizable.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.ArrayList; +import java.util.Collection; + +/** This abstract class provides boilerplate parameters list. + * + * @since 3.0 + */ + +public abstract class AbstractParameterizable implements Parameterizable { + + /** List of the parameters names. */ + private final Collection parametersNames; + + /** Simple constructor. + * @param names names of the supported parameters + */ + protected AbstractParameterizable(final String ... names) { + parametersNames = new ArrayList(); + for (final String name : names) { + parametersNames.add(name); + } + } + + /** Simple constructor. + * @param names names of the supported parameters + */ + protected AbstractParameterizable(final Collection names) { + parametersNames = new ArrayList(); + parametersNames.addAll(names); + } + + /** {@inheritDoc} */ + public Collection getParametersNames() { + return parametersNames; + } + + /** {@inheritDoc} */ + public boolean isSupported(final String name) { + for (final String supportedName : parametersNames) { + if (supportedName.equals(name)) { + return true; + } + } + return false; + } + + /** Check if a parameter is supported and throw an IllegalArgumentException if not. + * @param name name of the parameter to check + * @exception UnknownParameterException if the parameter is not supported + * @see #isSupported(String) + */ + public void complainIfNotSupported(final String name) + throws UnknownParameterException { + if (!isSupported(name)) { + throw new UnknownParameterException(name); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java new file mode 100644 index 000000000..fd73401d5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * This class stores all information provided by an ODE integrator + * during the integration process and build a continuous model of the + * solution from this. + * + *

    This class act as a step handler from the integrator point of + * view. It is called iteratively during the integration process and + * stores a copy of all steps information in a sorted collection for + * later use. Once the integration process is over, the user can use + * the {@link #getInterpolatedState(RealFieldElement) getInterpolatedState} + * method to retrieve this information at any time. It is important to wait + * for the integration to be over before attempting to call {@link + * #getInterpolatedState(RealFieldElement)} because some internal + * variables are set only once the last step has been handled.

    + * + *

    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).

    + * + *

    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.

    + * + *

    One should be aware that the amount of data stored in a + * ContinuousOutputFieldModel instance can be important if the state vector + * is large, if the integration interval is long or if the steps are + * small (which can result from small tolerance settings in {@link + * AdaptiveStepsizeFieldIntegrator adaptive + * step size integrators}).

    + * + * @see FieldStepHandler + * @see FieldStepInterpolator + * @param the type of the field elements + * @since 3.6 + */ + +public class ContinuousOutputFieldModel> + implements FieldStepHandler { + + /** Initial integration time. */ + private T initialTime; + + /** Final integration time. */ + private T finalTime; + + /** Integration direction indicator. */ + private boolean forward; + + /** Current interpolator index. */ + private int index; + + /** Steps table. */ + private List> steps; + + /** Simple constructor. + * Build an empty continuous output model. + */ + public ContinuousOutputFieldModel() { + steps = new ArrayList>(); + initialTime = null; + finalTime = null; + forward = true; + index = 0; + } + + /** Append another model at the end of the instance. + * @param model model to add at the end of the instance + * @exception MathIllegalArgumentException if the model to append is not + * compatible with the instance (dimension of the state vector, + * propagation direction, hole between the dates) + * @exception DimensionMismatchException if the dimensions of the states or + * the number of secondary states do not match + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * during step finalization + */ + public void append(final ContinuousOutputFieldModel model) + throws MathIllegalArgumentException, MaxCountExceededException { + + if (model.steps.size() == 0) { + return; + } + + if (steps.size() == 0) { + initialTime = model.initialTime; + forward = model.forward; + } else { + + // safety checks + final FieldODEStateAndDerivative s1 = steps.get(0).getPreviousState(); + final FieldODEStateAndDerivative s2 = model.steps.get(0).getPreviousState(); + checkDimensionsEquality(s1.getStateDimension(), s2.getStateDimension()); + checkDimensionsEquality(s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates()); + for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) { + checkDimensionsEquality(s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i)); + } + + if (forward ^ model.forward) { + throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH); + } + + final FieldStepInterpolator lastInterpolator = steps.get(index); + final T current = lastInterpolator.getCurrentState().getTime(); + final T previous = lastInterpolator.getPreviousState().getTime(); + final T step = current.subtract(previous); + final T gap = model.getInitialTime().subtract(current); + if (gap.abs().subtract(step.abs().multiply(1.0e-3)).getReal() > 0) { + throw new MathIllegalArgumentException(LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES, + gap.abs().getReal()); + } + + } + + for (FieldStepInterpolator interpolator : model.steps) { + steps.add(interpolator); + } + + index = steps.size() - 1; + finalTime = (steps.get(index)).getCurrentState().getTime(); + + } + + /** Check dimensions equality. + * @param d1 first dimension + * @param d2 second dimansion + * @exception DimensionMismatchException if dimensions do not match + */ + private void checkDimensionsEquality(final int d1, final int d2) + throws DimensionMismatchException { + if (d1 != d2) { + throw new DimensionMismatchException(d2, d1); + } + } + + /** {@inheritDoc} */ + public void init(final FieldODEStateAndDerivative initialState, final T t) { + initialTime = initialState.getTime(); + finalTime = t; + forward = true; + index = 0; + steps.clear(); + } + + /** Handle the last accepted step. + * A copy of the information provided by the last step is stored in + * the instance for later use. + * @param interpolator interpolator for the last accepted step. + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * during step finalization + */ + public void handleStep(final FieldStepInterpolator interpolator, final boolean isLast) + throws MaxCountExceededException { + + if (steps.size() == 0) { + initialTime = interpolator.getPreviousState().getTime(); + forward = interpolator.isForward(); + } + + steps.add(interpolator); + + if (isLast) { + finalTime = interpolator.getCurrentState().getTime(); + index = steps.size() - 1; + } + + } + + /** + * Get the initial integration time. + * @return initial integration time + */ + public T getInitialTime() { + return initialTime; + } + + /** + * Get the final integration time. + * @return final integration time + */ + public T getFinalTime() { + return finalTime; + } + + /** + * Get the state at interpolated time. + * @param time time of the interpolated point + * @return state at interpolated time + */ + public FieldODEStateAndDerivative getInterpolatedState(final T time) { + + // initialize the search with the complete steps table + int iMin = 0; + final FieldStepInterpolator sMin = steps.get(iMin); + T tMin = sMin.getPreviousState().getTime().add(sMin.getCurrentState().getTime()).multiply(0.5); + + int iMax = steps.size() - 1; + final FieldStepInterpolator sMax = steps.get(iMax); + T tMax = sMax.getPreviousState().getTime().add(sMax.getCurrentState().getTime()).multiply(0.5); + + // handle points outside of the integration interval + // or in the first and last step + if (locatePoint(time, sMin) <= 0) { + index = iMin; + return sMin.getInterpolatedState(time); + } + if (locatePoint(time, sMax) >= 0) { + index = iMax; + return sMax.getInterpolatedState(time); + } + + // reduction of the table slice size + while (iMax - iMin > 5) { + + // use the last estimated index as the splitting index + final FieldStepInterpolator si = steps.get(index); + final int location = locatePoint(time, si); + if (location < 0) { + iMax = index; + tMax = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5); + } else if (location > 0) { + iMin = index; + tMin = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5); + } else { + // we have found the target step, no need to continue searching + return si.getInterpolatedState(time); + } + + // compute a new estimate of the index in the reduced table slice + final int iMed = (iMin + iMax) / 2; + final FieldStepInterpolator sMed = steps.get(iMed); + final T tMed = sMed.getPreviousState().getTime().add(sMed.getCurrentState().getTime()).multiply(0.5); + + if (tMed.subtract(tMin).abs().subtract(1.0e-6).getReal() < 0 || + tMax.subtract(tMed).abs().subtract(1.0e-6).getReal() < 0) { + // too close to the bounds, we estimate using a simple dichotomy + index = iMed; + } else { + // estimate the index using a reverse quadratic polynomial + // (reverse means we have i = P(t), thus allowing to simply + // compute index = P(time) rather than solving a quadratic equation) + final T d12 = tMax.subtract(tMed); + final T d23 = tMed.subtract(tMin); + final T d13 = tMax.subtract(tMin); + final T dt1 = time.subtract(tMax); + final T dt2 = time.subtract(tMed); + final T dt3 = time.subtract(tMin); + final T iLagrange = dt2.multiply(dt3).multiply(d23).multiply(iMax). + subtract(dt1.multiply(dt3).multiply(d13).multiply(iMed)). + add( dt1.multiply(dt2).multiply(d12).multiply(iMin)). + divide(d12.multiply(d23).multiply(d13)); + index = (int) FastMath.rint(iLagrange.getReal()); + } + + // force the next size reduction to be at least one tenth + final int low = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10); + final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10); + if (index < low) { + index = low; + } else if (index > high) { + index = high; + } + + } + + // now the table slice is very small, we perform an iterative search + index = iMin; + while (index <= iMax && locatePoint(time, steps.get(index)) > 0) { + ++index; + } + + return steps.get(index).getInterpolatedState(time); + + } + + /** Compare a step interval and a double. + * @param time point to locate + * @param interval step interval + * @return -1 if the double is before the interval, 0 if it is in + * the interval, and +1 if it is after the interval, according to + * the interval direction + */ + private int locatePoint(final T time, final FieldStepInterpolator interval) { + if (forward) { + if (time.subtract(interval.getPreviousState().getTime()).getReal() < 0) { + return -1; + } else if (time.subtract(interval.getCurrentState().getTime()).getReal() > 0) { + return +1; + } else { + return 0; + } + } + if (time.subtract(interval.getPreviousState().getTime()).getReal() > 0) { + return -1; + } else if (time.subtract(interval.getCurrentState().getTime()).getReal() < 0) { + return +1; + } else { + return 0; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputModel.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputModel.java new file mode 100644 index 000000000..12a422c38 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ContinuousOutputModel.java @@ -0,0 +1,444 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.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. + * + *

    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.

    + * + *

    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).

    + * + *

    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.

    + * + *

    An important feature of this class is that it implements the + * Serializable 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.

    + * + *

    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 + * AdaptiveStepsizeIntegrator adaptive + * step size integrators}).

    + * + * @see StepHandler + * @see StepInterpolator + * @since 1.2 + */ + +public class ContinuousOutputModel + implements StepHandler, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1417964919405031606L; + + /** Initial integration time. */ + private double initialTime; + + /** Final integration time. */ + private double finalTime; + + /** Integration direction indicator. */ + private boolean forward; + + /** Current interpolator index. */ + private int index; + + /** Steps table. */ + private List steps; + + /** Simple constructor. + * Build an empty continuous output model. + */ + public ContinuousOutputModel() { + steps = new ArrayList(); + initialTime = Double.NaN; + finalTime = Double.NaN; + forward = true; + index = 0; + } + + /** Append another model at the end of the instance. + * @param model model to add at the end of the instance + * @exception MathIllegalArgumentException if the model to append is not + * compatible with the instance (dimension of the state vector, + * propagation direction, hole between the dates) + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * during step finalization + */ + public void append(final ContinuousOutputModel model) + throws MathIllegalArgumentException, MaxCountExceededException { + + if (model.steps.size() == 0) { + return; + } + + if (steps.size() == 0) { + initialTime = model.initialTime; + forward = model.forward; + } else { + + if (getInterpolatedState().length != model.getInterpolatedState().length) { + throw new DimensionMismatchException(model.getInterpolatedState().length, + getInterpolatedState().length); + } + + if (forward ^ model.forward) { + throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH); + } + + final StepInterpolator lastInterpolator = steps.get(index); + final double current = lastInterpolator.getCurrentTime(); + final double previous = lastInterpolator.getPreviousTime(); + final double step = current - previous; + final double gap = model.getInitialTime() - current; + if (FastMath.abs(gap) > 1.0e-3 * FastMath.abs(step)) { + throw new MathIllegalArgumentException(LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES, + FastMath.abs(gap)); + } + + } + + for (StepInterpolator interpolator : model.steps) { + steps.add(interpolator.copy()); + } + + index = steps.size() - 1; + finalTime = (steps.get(index)).getCurrentTime(); + + } + + /** {@inheritDoc} */ + public void init(double t0, double[] y0, double t) { + initialTime = Double.NaN; + finalTime = Double.NaN; + forward = true; + index = 0; + steps.clear(); + } + + /** Handle the last accepted step. + * A copy of the information provided by the last step is stored in + * the instance for later use. + * @param interpolator interpolator for the last accepted step. + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * during step finalization + */ + public void handleStep(final StepInterpolator interpolator, final boolean isLast) + throws MaxCountExceededException { + + if (steps.size() == 0) { + initialTime = interpolator.getPreviousTime(); + forward = interpolator.isForward(); + } + + steps.add(interpolator.copy()); + + if (isLast) { + finalTime = interpolator.getCurrentTime(); + index = steps.size() - 1; + } + + } + + /** + * Get the initial integration time. + * @return initial integration time + */ + public double getInitialTime() { + return initialTime; + } + + /** + * Get the final integration time. + * @return final integration time + */ + public double getFinalTime() { + return finalTime; + } + + /** + * Get the time of the interpolated point. + * If {@link #setInterpolatedTime} has not been called, it returns + * the final integration time. + * @return interpolation point time + */ + public double getInterpolatedTime() { + return steps.get(index).getInterpolatedTime(); + } + + /** Set the time of the interpolated point. + *

    This method should not be called before the + * integration is over because some internal variables are set only + * once the last step has been handled.

    + *

    Setting the time outside of the integration interval is now + * allowed, but should be used with care since the accuracy of the + * interpolator will probably be very poor far from this interval. + * This allowance has been added to simplify implementation of search + * algorithms near the interval endpoints.

    + *

    Note that each time this method is called, the internal arrays + * returned in {@link #getInterpolatedState()}, {@link + * #getInterpolatedDerivatives()} and {@link #getInterpolatedSecondaryState(int)} + * will be overwritten. So if their content must be preserved + * across several calls, user must copy them.

    + * @param time time of the interpolated point + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + */ + public void setInterpolatedTime(final double time) { + + // initialize the search with the complete steps table + int iMin = 0; + final StepInterpolator sMin = steps.get(iMin); + double tMin = 0.5 * (sMin.getPreviousTime() + sMin.getCurrentTime()); + + int iMax = steps.size() - 1; + final StepInterpolator sMax = steps.get(iMax); + double tMax = 0.5 * (sMax.getPreviousTime() + sMax.getCurrentTime()); + + // handle points outside of the integration interval + // or in the first and last step + if (locatePoint(time, sMin) <= 0) { + index = iMin; + sMin.setInterpolatedTime(time); + return; + } + if (locatePoint(time, sMax) >= 0) { + index = iMax; + sMax.setInterpolatedTime(time); + return; + } + + // reduction of the table slice size + while (iMax - iMin > 5) { + + // use the last estimated index as the splitting index + final StepInterpolator si = steps.get(index); + final int location = locatePoint(time, si); + if (location < 0) { + iMax = index; + tMax = 0.5 * (si.getPreviousTime() + si.getCurrentTime()); + } else if (location > 0) { + iMin = index; + tMin = 0.5 * (si.getPreviousTime() + si.getCurrentTime()); + } else { + // we have found the target step, no need to continue searching + si.setInterpolatedTime(time); + return; + } + + // compute a new estimate of the index in the reduced table slice + final int iMed = (iMin + iMax) / 2; + final StepInterpolator sMed = steps.get(iMed); + final double tMed = 0.5 * (sMed.getPreviousTime() + sMed.getCurrentTime()); + + if ((FastMath.abs(tMed - tMin) < 1e-6) || (FastMath.abs(tMax - tMed) < 1e-6)) { + // too close to the bounds, we estimate using a simple dichotomy + index = iMed; + } else { + // estimate the index using a reverse quadratic polynom + // (reverse means we have i = P(t), thus allowing to simply + // compute index = P(time) rather than solving a quadratic equation) + final double d12 = tMax - tMed; + final double d23 = tMed - tMin; + final double d13 = tMax - tMin; + final double dt1 = time - tMax; + final double dt2 = time - tMed; + final double dt3 = time - tMin; + final double iLagrange = ((dt2 * dt3 * d23) * iMax - + (dt1 * dt3 * d13) * iMed + + (dt1 * dt2 * d12) * iMin) / + (d12 * d23 * d13); + index = (int) FastMath.rint(iLagrange); + } + + // force the next size reduction to be at least one tenth + final int low = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10); + final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10); + if (index < low) { + index = low; + } else if (index > high) { + index = high; + } + + } + + // now the table slice is very small, we perform an iterative search + index = iMin; + while ((index <= iMax) && (locatePoint(time, steps.get(index)) > 0)) { + ++index; + } + + steps.get(index).setInterpolatedTime(time); + + } + + /** + * Get the state vector of the interpolated point. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @return state vector at time {@link #getInterpolatedTime} + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @see #setInterpolatedTime(double) + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + * @see #getInterpolatedSecondaryDerivatives(int) + */ + public double[] getInterpolatedState() throws MaxCountExceededException { + return steps.get(index).getInterpolatedState(); + } + + /** + * Get the derivatives of the state vector of the interpolated point. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @return derivatives of the state vector at time {@link #getInterpolatedTime} + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @see #setInterpolatedTime(double) + * @see #getInterpolatedState() + * @see #getInterpolatedSecondaryState(int) + * @see #getInterpolatedSecondaryDerivatives(int) + * @since 3.4 + */ + public double[] getInterpolatedDerivatives() throws MaxCountExceededException { + return steps.get(index).getInterpolatedDerivatives(); + } + + /** Get the interpolated secondary state corresponding to the secondary equations. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @param secondaryStateIndex index of the secondary set, as returned by {@link + * ExpandableStatefulODE#addSecondaryEquations( + *SecondaryEquations) + * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)} + * @return interpolated secondary state at the current interpolation date + * @see #setInterpolatedTime(double) + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryDerivatives(int) + * @since 3.2 + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + public double[] getInterpolatedSecondaryState(final int secondaryStateIndex) + throws MaxCountExceededException { + return steps.get(index).getInterpolatedSecondaryState(secondaryStateIndex); + } + + /** Get the interpolated secondary derivatives corresponding to the secondary equations. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @param secondaryStateIndex index of the secondary set, as returned by {@link + * ExpandableStatefulODE#addSecondaryEquations( + *SecondaryEquations) + * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)} + * @return interpolated secondary derivatives at the current interpolation date + * @see #setInterpolatedTime(double) + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + * @since 3.4 + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + public double[] getInterpolatedSecondaryDerivatives(final int secondaryStateIndex) + throws MaxCountExceededException { + return steps.get(index).getInterpolatedSecondaryDerivatives(secondaryStateIndex); + } + + /** Compare a step interval and a double. + * @param time point to locate + * @param interval step interval + * @return -1 if the double is before the interval, 0 if it is in + * the interval, and +1 if it is after the interval, according to + * the interval direction + */ + private int locatePoint(final double time, final StepInterpolator interval) { + if (forward) { + if (time < interval.getPreviousTime()) { + return -1; + } else if (time > interval.getCurrentTime()) { + return +1; + } else { + return 0; + } + } + if (time > interval.getPreviousTime()) { + return -1; + } else if (time < interval.getCurrentTime()) { + return +1; + } else { + return 0; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/EquationsMapper.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/EquationsMapper.java new file mode 100644 index 000000000..5f7fb1bb1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/EquationsMapper.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 com.fr.third.org.apache.commons.math3.ode; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * Class mapping the part of a complete state or derivative that pertains + * to a specific differential equation. + *

    + * Instances of this class are guaranteed to be immutable. + *

    + * @see SecondaryEquations + * @since 3.0 + */ +public class EquationsMapper implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20110925L; + + /** Index of the first equation element in complete state arrays. */ + private final int firstIndex; + + /** Dimension of the secondary state parameters. */ + private final int dimension; + + /** simple constructor. + * @param firstIndex index of the first equation element in complete state arrays + * @param dimension dimension of the secondary state parameters + */ + public EquationsMapper(final int firstIndex, final int dimension) { + this.firstIndex = firstIndex; + this.dimension = dimension; + } + + /** Get the index of the first equation element in complete state arrays. + * @return index of the first equation element in complete state arrays + */ + public int getFirstIndex() { + return firstIndex; + } + + /** Get the dimension of the secondary state parameters. + * @return dimension of the secondary state parameters + */ + public int getDimension() { + return dimension; + } + + /** Extract equation data from a complete state or derivative array. + * @param complete complete state or derivative array from which + * equation data should be retrieved + * @param equationData placeholder where to put equation data + * @throws DimensionMismatchException if the dimension of the equation data does not + * match the mapper dimension + */ + public void extractEquationData(double[] complete, double[] equationData) + throws DimensionMismatchException { + if (equationData.length != dimension) { + throw new DimensionMismatchException(equationData.length, dimension); + } + System.arraycopy(complete, firstIndex, equationData, 0, dimension); + } + + /** Insert equation data into a complete state or derivative array. + * @param equationData equation data to be inserted into the complete array + * @param complete placeholder where to put equation data (only the + * part corresponding to the equation will be overwritten) + * @throws DimensionMismatchException if the dimension of the equation data does not + * match the mapper dimension + */ + public void insertEquationData(double[] equationData, double[] complete) + throws DimensionMismatchException { + if (equationData.length != dimension) { + throw new DimensionMismatchException(equationData.length, dimension); + } + System.arraycopy(equationData, 0, complete, firstIndex, dimension); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ExpandableStatefulODE.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ExpandableStatefulODE.java new file mode 100644 index 000000000..071e9a1ef --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ExpandableStatefulODE.java @@ -0,0 +1,330 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.ode; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + + +/** + * This class represents a combined set of first order differential equations, + * with at least a primary set of equations expandable by some sets of secondary + * equations. + *

    + * One typical use case is the computation of the Jacobian matrix for some ODE. + * In this case, the primary set of equations corresponds to the raw ODE, and we + * add to this set another bunch of secondary equations which represent the Jacobian + * matrix of the primary set. + *

    + *

    + * We want the integrator to use only the primary set to estimate the + * errors and hence the step sizes. It should not use the secondary + * equations in this computation. The {@link AbstractIntegrator integrator} will + * be able to know where the primary set ends and so where the secondary sets begin. + *

    + * + * @see FirstOrderDifferentialEquations + * @see JacobianMatrices + * + * @since 3.0 + */ + +public class ExpandableStatefulODE { + + /** Primary differential equation. */ + private final FirstOrderDifferentialEquations primary; + + /** Mapper for primary equation. */ + private final EquationsMapper primaryMapper; + + /** Time. */ + private double time; + + /** State. */ + private final double[] primaryState; + + /** State derivative. */ + private final double[] primaryStateDot; + + /** Components of the expandable ODE. */ + private List components; + + /** Build an expandable set from its primary ODE set. + * @param primary the primary set of differential equations to be integrated. + */ + public ExpandableStatefulODE(final FirstOrderDifferentialEquations primary) { + final int n = primary.getDimension(); + this.primary = primary; + this.primaryMapper = new EquationsMapper(0, n); + this.time = Double.NaN; + this.primaryState = new double[n]; + this.primaryStateDot = new double[n]; + this.components = new ArrayList(); + } + + /** Get the primary set of differential equations. + * @return primary set of differential equations + */ + public FirstOrderDifferentialEquations getPrimary() { + return primary; + } + + /** Return the dimension of the complete set of equations. + *

    + * The complete set of equations correspond to the primary set plus all secondary sets. + *

    + * @return dimension of the complete set of equations + */ + public int getTotalDimension() { + if (components.isEmpty()) { + // there are no secondary equations, the complete set is limited to the primary set + return primaryMapper.getDimension(); + } else { + // there are secondary equations, the complete set ends after the last set + final EquationsMapper lastMapper = components.get(components.size() - 1).mapper; + return lastMapper.getFirstIndex() + lastMapper.getDimension(); + } + } + + /** Get the current time derivative of the complete state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the complete state vector + * @param yDot placeholder array where to put the time derivative of the complete state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public void computeDerivatives(final double t, final double[] y, final double[] yDot) + throws MaxCountExceededException, DimensionMismatchException { + + // compute derivatives of the primary equations + primaryMapper.extractEquationData(y, primaryState); + primary.computeDerivatives(t, primaryState, primaryStateDot); + + // Add contribution for secondary equations + for (final SecondaryComponent component : components) { + component.mapper.extractEquationData(y, component.state); + component.equation.computeDerivatives(t, primaryState, primaryStateDot, + component.state, component.stateDot); + component.mapper.insertEquationData(component.stateDot, yDot); + } + + primaryMapper.insertEquationData(primaryStateDot, yDot); + + } + + /** Add a set of secondary equations to be integrated along with the primary set. + * @param secondary secondary equations set + * @return index of the secondary equation in the expanded state + */ + public int addSecondaryEquations(final SecondaryEquations secondary) { + + final int firstIndex; + if (components.isEmpty()) { + // lazy creation of the components list + components = new ArrayList(); + firstIndex = primary.getDimension(); + } else { + final SecondaryComponent last = components.get(components.size() - 1); + firstIndex = last.mapper.getFirstIndex() + last.mapper.getDimension(); + } + + components.add(new SecondaryComponent(secondary, firstIndex)); + + return components.size() - 1; + + } + + /** Get an equations mapper for the primary equations set. + * @return mapper for the primary set + * @see #getSecondaryMappers() + */ + public EquationsMapper getPrimaryMapper() { + return primaryMapper; + } + + /** Get the equations mappers for the secondary equations sets. + * @return equations mappers for the secondary equations sets + * @see #getPrimaryMapper() + */ + public EquationsMapper[] getSecondaryMappers() { + final EquationsMapper[] mappers = new EquationsMapper[components.size()]; + for (int i = 0; i < mappers.length; ++i) { + mappers[i] = components.get(i).mapper; + } + return mappers; + } + + /** Set current time. + * @param time current time + */ + public void setTime(final double time) { + this.time = time; + } + + /** Get current time. + * @return current time + */ + public double getTime() { + return time; + } + + /** Set primary part of the current state. + * @param primaryState primary part of the current state + * @throws DimensionMismatchException if the dimension of the array does not + * match the primary set + */ + public void setPrimaryState(final double[] primaryState) throws DimensionMismatchException { + + // safety checks + if (primaryState.length != this.primaryState.length) { + throw new DimensionMismatchException(primaryState.length, this.primaryState.length); + } + + // set the data + System.arraycopy(primaryState, 0, this.primaryState, 0, primaryState.length); + + } + + /** Get primary part of the current state. + * @return primary part of the current state + */ + public double[] getPrimaryState() { + return primaryState.clone(); + } + + /** Get primary part of the current state derivative. + * @return primary part of the current state derivative + */ + public double[] getPrimaryStateDot() { + return primaryStateDot.clone(); + } + + /** Set secondary part of the current state. + * @param index index of the part to set as returned by {@link + * #addSecondaryEquations(SecondaryEquations)} + * @param secondaryState secondary part of the current state + * @throws DimensionMismatchException if the dimension of the partial state does not + * match the selected equations set dimension + */ + public void setSecondaryState(final int index, final double[] secondaryState) + throws DimensionMismatchException { + + // get either the secondary state + double[] localArray = components.get(index).state; + + // safety checks + if (secondaryState.length != localArray.length) { + throw new DimensionMismatchException(secondaryState.length, localArray.length); + } + + // set the data + System.arraycopy(secondaryState, 0, localArray, 0, secondaryState.length); + + } + + /** Get secondary part of the current state. + * @param index index of the part to set as returned by {@link + * #addSecondaryEquations(SecondaryEquations)} + * @return secondary part of the current state + */ + public double[] getSecondaryState(final int index) { + return components.get(index).state.clone(); + } + + /** Get secondary part of the current state derivative. + * @param index index of the part to set as returned by {@link + * #addSecondaryEquations(SecondaryEquations)} + * @return secondary part of the current state derivative + */ + public double[] getSecondaryStateDot(final int index) { + return components.get(index).stateDot.clone(); + } + + /** Set the complete current state. + * @param completeState complete current state to copy data from + * @throws DimensionMismatchException if the dimension of the complete state does not + * match the complete equations sets dimension + */ + public void setCompleteState(final double[] completeState) + throws DimensionMismatchException { + + // safety checks + if (completeState.length != getTotalDimension()) { + throw new DimensionMismatchException(completeState.length, getTotalDimension()); + } + + // set the data + primaryMapper.extractEquationData(completeState, primaryState); + for (final SecondaryComponent component : components) { + component.mapper.extractEquationData(completeState, component.state); + } + + } + + /** Get the complete current state. + * @return complete current state + * @throws DimensionMismatchException if the dimension of the complete state does not + * match the complete equations sets dimension + */ + public double[] getCompleteState() throws DimensionMismatchException { + + // allocate complete array + double[] completeState = new double[getTotalDimension()]; + + // set the data + primaryMapper.insertEquationData(primaryState, completeState); + for (final SecondaryComponent component : components) { + component.mapper.insertEquationData(component.state, completeState); + } + + return completeState; + + } + + /** Components of the compound stateful ODE. */ + private static class SecondaryComponent { + + /** Secondary differential equation. */ + private final SecondaryEquations equation; + + /** Mapper between local and complete arrays. */ + private final EquationsMapper mapper; + + /** State. */ + private final double[] state; + + /** State derivative. */ + private final double[] stateDot; + + /** Simple constructor. + * @param equation secondary differential equation + * @param firstIndex index to use for the first element in the complete arrays + */ + SecondaryComponent(final SecondaryEquations equation, final int firstIndex) { + final int n = equation.getDimension(); + this.equation = equation; + mapper = new EquationsMapper(firstIndex, n); + state = new double[n]; + stateDot = new double[n]; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldEquationsMapper.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldEquationsMapper.java new file mode 100644 index 000000000..35cc59539 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldEquationsMapper.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * Class mapping the part of a complete state or derivative that pertains + * to a set of differential equations. + *

    + * Instances of this class are guaranteed to be immutable. + *

    + * @see FieldExpandableODE + * @param the type of the field elements + * @since 3.6 + */ +public class FieldEquationsMapper> implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20151114L; + + /** Start indices of the components. */ + private final int[] start; + + /** Create a mapper by adding a new equation to another mapper. + *

    + * The new equation will have index {@code mapper.}{@link #getNumberOfEquations()}, + * or 0 if {@code mapper} is null. + *

    + * @param mapper former mapper, with one equation less (null for first equation) + * @param dimension dimension of the equation state vector + */ + FieldEquationsMapper(final FieldEquationsMapper mapper, final int dimension) { + final int index = (mapper == null) ? 0 : mapper.getNumberOfEquations(); + this.start = new int[index + 2]; + if (mapper == null) { + start[0] = 0; + } else { + System.arraycopy(mapper.start, 0, start, 0, index + 1); + } + start[index + 1] = start[index] + dimension; + } + + /** Get the number of equations mapped. + * @return number of equations mapped + */ + public int getNumberOfEquations() { + return start.length - 1; + } + + /** Return the dimension of the complete set of equations. + *

    + * The complete set of equations correspond to the primary set plus all secondary sets. + *

    + * @return dimension of the complete set of equations + */ + public int getTotalDimension() { + return start[start.length - 1]; + } + + /** Map a state to a complete flat array. + * @param state state to map + * @return flat array containing the mapped state, including primary and secondary components + */ + public T[] mapState(final FieldODEState state) { + final T[] y = MathArrays.buildArray(state.getTime().getField(), getTotalDimension()); + int index = 0; + insertEquationData(index, state.getState(), y); + while (++index < getNumberOfEquations()) { + insertEquationData(index, state.getSecondaryState(index), y); + } + return y; + } + + /** Map a state derivative to a complete flat array. + * @param state state to map + * @return flat array containing the mapped state derivative, including primary and secondary components + */ + public T[] mapDerivative(final FieldODEStateAndDerivative state) { + final T[] yDot = MathArrays.buildArray(state.getTime().getField(), getTotalDimension()); + int index = 0; + insertEquationData(index, state.getDerivative(), yDot); + while (++index < getNumberOfEquations()) { + insertEquationData(index, state.getSecondaryDerivative(index), yDot); + } + return yDot; + } + + /** Map flat arrays to a state and derivative. + * @param t time + * @param y state array to map, including primary and secondary components + * @param yDot state derivative array to map, including primary and secondary components + * @return mapped state + * @exception DimensionMismatchException if an array does not match total dimension + */ + public FieldODEStateAndDerivative mapStateAndDerivative(final T t, final T[] y, final T[] yDot) + throws DimensionMismatchException { + + if (y.length != getTotalDimension()) { + throw new DimensionMismatchException(y.length, getTotalDimension()); + } + + if (yDot.length != getTotalDimension()) { + throw new DimensionMismatchException(yDot.length, getTotalDimension()); + } + + final int n = getNumberOfEquations(); + int index = 0; + final T[] state = extractEquationData(index, y); + final T[] derivative = extractEquationData(index, yDot); + if (n < 2) { + return new FieldODEStateAndDerivative(t, state, derivative); + } else { + final T[][] secondaryState = MathArrays.buildArray(t.getField(), n - 1, -1); + final T[][] secondaryDerivative = MathArrays.buildArray(t.getField(), n - 1, -1); + while (++index < getNumberOfEquations()) { + secondaryState[index - 1] = extractEquationData(index, y); + secondaryDerivative[index - 1] = extractEquationData(index, yDot); + } + return new FieldODEStateAndDerivative(t, state, derivative, secondaryState, secondaryDerivative); + } + } + + /** Extract equation data from a complete state or derivative array. + * @param index index of the equation, must be between 0 included and + * {@link #getNumberOfEquations()} (excluded) + * @param complete complete state or derivative array from which + * equation data should be retrieved + * @return equation data + * @exception MathIllegalArgumentException if index is out of range + * @exception DimensionMismatchException if complete state has not enough elements + */ + public T[] extractEquationData(final int index, final T[] complete) + throws MathIllegalArgumentException, DimensionMismatchException { + checkIndex(index); + final int begin = start[index]; + final int end = start[index + 1]; + if (complete.length < end) { + throw new DimensionMismatchException(complete.length, end); + } + final int dimension = end - begin; + final T[] equationData = MathArrays.buildArray(complete[0].getField(), dimension); + System.arraycopy(complete, begin, equationData, 0, dimension); + return equationData; + } + + /** Insert equation data into a complete state or derivative array. + * @param index index of the equation, must be between 0 included and + * {@link #getNumberOfEquations()} (excluded) + * @param equationData equation data to be inserted into the complete array + * @param complete placeholder where to put equation data (only the + * part corresponding to the equation will be overwritten) + * @exception DimensionMismatchException if either array has not enough elements + */ + public void insertEquationData(final int index, T[] equationData, T[] complete) + throws DimensionMismatchException { + checkIndex(index); + final int begin = start[index]; + final int end = start[index + 1]; + final int dimension = end - begin; + if (complete.length < end) { + throw new DimensionMismatchException(complete.length, end); + } + if (equationData.length != dimension) { + throw new DimensionMismatchException(equationData.length, dimension); + } + System.arraycopy(equationData, 0, complete, begin, dimension); + } + + /** Check equation index. + * @param index index of the equation, must be between 0 included and + * {@link #getNumberOfEquations()} (excluded) + * @exception MathIllegalArgumentException if index is out of range + */ + private void checkIndex(final int index) throws MathIllegalArgumentException { + if (index < 0 || index > start.length - 2) { + throw new MathIllegalArgumentException(LocalizedFormats.ARGUMENT_OUTSIDE_DOMAIN, + index, 0, start.length - 2); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldExpandableODE.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldExpandableODE.java new file mode 100644 index 000000000..e36034b75 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldExpandableODE.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + + +/** + * This class represents a combined set of first order differential equations, + * with at least a primary set of equations expandable by some sets of secondary + * equations. + *

    + * One typical use case is the computation of the Jacobian matrix for some ODE. + * In this case, the primary set of equations corresponds to the raw ODE, and we + * add to this set another bunch of secondary equations which represent the Jacobian + * matrix of the primary set. + *

    + *

    + * We want the integrator to use only the primary set to estimate the + * errors and hence the step sizes. It should not use the secondary + * equations in this computation. The {@link FirstOrderFieldIntegrator integrator} will + * be able to know where the primary set ends and so where the secondary sets begin. + *

    + * + * @see FirstOrderFieldDifferentialEquations + * @see FieldSecondaryEquations + * + * @param the type of the field elements + * @since 3.6 + */ + +public class FieldExpandableODE> { + + /** Primary differential equation. */ + private final FirstOrderFieldDifferentialEquations primary; + + /** Components of the expandable ODE. */ + private List> components; + + /** Mapper for all equations. */ + private FieldEquationsMapper mapper; + + /** Build an expandable set from its primary ODE set. + * @param primary the primary set of differential equations to be integrated. + */ + public FieldExpandableODE(final FirstOrderFieldDifferentialEquations primary) { + this.primary = primary; + this.components = new ArrayList>(); + this.mapper = new FieldEquationsMapper(null, primary.getDimension()); + } + + /** Get the mapper for the set of equations. + * @return mapper for the set of equations + */ + public FieldEquationsMapper getMapper() { + return mapper; + } + + /** Add a set of secondary equations to be integrated along with the primary set. + * @param secondary secondary equations set + * @return index of the secondary equation in the expanded state, to be used + * as the parameter to {@link FieldODEState#getSecondaryState(int)} and + * {@link FieldODEStateAndDerivative#getSecondaryDerivative(int)} (beware index + * 0 corresponds to main state, additional states start at 1) + */ + public int addSecondaryEquations(final FieldSecondaryEquations secondary) { + + components.add(secondary); + mapper = new FieldEquationsMapper(mapper, secondary.getDimension()); + + return components.size(); + + } + + /** Initialize equations at the start of an ODE integration. + * @param t0 value of the independent time variable at integration start + * @param y0 array containing the value of the state vector at integration start + * @param finalTime target time for the integration + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public void init(final T t0, final T[] y0, final T finalTime) { + + // initialize primary equations + int index = 0; + final T[] primary0 = mapper.extractEquationData(index, y0); + primary.init(t0, primary0, finalTime); + + // initialize secondary equations + while (++index < mapper.getNumberOfEquations()) { + final T[] secondary0 = mapper.extractEquationData(index, y0); + components.get(index - 1).init(t0, primary0, secondary0, finalTime); + } + + } + + /** Get the current time derivative of the complete state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the complete state vector + * @return time derivative of the complete state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public T[] computeDerivatives(final T t, final T[] y) + throws MaxCountExceededException, DimensionMismatchException { + + final T[] yDot = MathArrays.buildArray(t.getField(), mapper.getTotalDimension()); + + // compute derivatives of the primary equations + int index = 0; + final T[] primaryState = mapper.extractEquationData(index, y); + final T[] primaryStateDot = primary.computeDerivatives(t, primaryState); + mapper.insertEquationData(index, primaryStateDot, yDot); + + // Add contribution for secondary equations + while (++index < mapper.getNumberOfEquations()) { + final T[] componentState = mapper.extractEquationData(index, y); + final T[] componentStateDot = components.get(index - 1).computeDerivatives(t, primaryState, primaryStateDot, + componentState); + mapper.insertEquationData(index, componentStateDot, yDot); + } + + return yDot; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEState.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEState.java new file mode 100644 index 000000000..9586464b1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEState.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** Container for time, main and secondary state vectors. + + * @see FirstOrderFieldDifferentialEquations + * @see FieldSecondaryEquations + * @see FirstOrderFieldIntegrator + * @see FieldODEStateAndDerivative + * @param the type of the field elements + * @since 3.6 + */ + +public class FieldODEState> { + + /** Time. */ + private final T time; + + /** Main state at time. */ + private final T[] state; + + /** Secondary state at time. */ + private final T[][] secondaryState; + + /** Simple constructor. + *

    Calling this constructor is equivalent to call {@link + * #FieldODEState(RealFieldElement, RealFieldElement[], RealFieldElement[][]) + * FieldODEState(time, state, null)}.

    + * @param time time + * @param state state at time + */ + public FieldODEState(T time, T[] state) { + this(time, state, null); + } + + /** Simple constructor. + * @param time time + * @param state state at time + * @param secondaryState state at time (may be null) + */ + public FieldODEState(T time, T[] state, T[][] secondaryState) { + this.time = time; + this.state = state.clone(); + this.secondaryState = copy(time.getField(), secondaryState); + } + + /** Copy a two-dimensions array. + * @param field field to which elements belong + * @param original original array (may be null) + * @return copied array or null if original array was null + */ + protected T[][] copy(final Field field, final T[][] original) { + + // special handling of null arrays + if (original == null) { + return null; + } + + // allocate the array + final T[][] copied = MathArrays.buildArray(field, original.length, -1); + + // copy content + for (int i = 0; i < original.length; ++i) { + copied[i] = original[i].clone(); + } + + return copied; + + } + + /** Get time. + * @return time + */ + public T getTime() { + return time; + } + + /** Get main state dimension. + * @return main state dimension + */ + public int getStateDimension() { + return state.length; + } + + /** Get main state at time. + * @return main state at time + */ + public T[] getState() { + return state.clone(); + } + + /** Get the number of secondary states. + * @return number of secondary states. + */ + public int getNumberOfSecondaryStates() { + return secondaryState == null ? 0 : secondaryState.length; + } + + /** Get secondary state dimension. + * @param index index of the secondary set as returned + * by {@link FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} + * (beware index 0 corresponds to main state, additional states start at 1) + * @return secondary state dimension + */ + public int getSecondaryStateDimension(final int index) { + return index == 0 ? state.length : secondaryState[index - 1].length; + } + + /** Get secondary state at time. + * @param index index of the secondary set as returned + * by {@link FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} + * (beware index 0 corresponds to main state, additional states start at 1) + * @return secondary state at time + */ + public T[] getSecondaryState(final int index) { + return index == 0 ? state.clone() : secondaryState[index - 1].clone(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java new file mode 100644 index 000000000..4a09872f5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** Container for time, main and secondary state vectors as well as their derivatives. + + * @see FirstOrderFieldDifferentialEquations + * @see FieldSecondaryEquations + * @see FirstOrderFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class FieldODEStateAndDerivative> extends FieldODEState { + + /** Derivative of the main state at time. */ + private final T[] derivative; + + /** Derivative of the secondary state at time. */ + private final T[][] secondaryDerivative; + + /** Simple constructor. + *

    Calling this constructor is equivalent to call {@link + * #FieldODEStateAndDerivative(RealFieldElement, RealFieldElement[], RealFieldElement[], + * RealFieldElement[][], RealFieldElement[][]) FieldODEStateAndDerivative(time, state, + * derivative, null, null)}.

    + * @param time time + * @param state state at time + * @param derivative derivative of the state at time + */ + public FieldODEStateAndDerivative(T time, T[] state, T[] derivative) { + this(time, state, derivative, null, null); + } + + /** Simple constructor. + * @param time time + * @param state state at time + * @param derivative derivative of the state at time + * @param secondaryState state at time (may be null) + * @param secondaryDerivative derivative of the state at time (may be null) + */ + public FieldODEStateAndDerivative(T time, T[] state, T[] derivative, T[][] secondaryState, T[][] secondaryDerivative) { + super(time, state, secondaryState); + this.derivative = derivative.clone(); + this.secondaryDerivative = copy(time.getField(), secondaryDerivative); + } + + /** Get derivative of the main state at time. + * @return derivative of the main state at time + */ + public T[] getDerivative() { + return derivative.clone(); + } + + /** Get derivative of the secondary state at time. + * @param index index of the secondary set as returned + * by {@link FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} + * (beware index 0 corresponds to main state, additional states start at 1) + * @return derivative of the secondary state at time + */ + public T[] getSecondaryDerivative(final int index) { + return index == 0 ? derivative.clone() : secondaryDerivative[index - 1].clone(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldSecondaryEquations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldSecondaryEquations.java new file mode 100644 index 000000000..c181b7282 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FieldSecondaryEquations.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * This interface allows users to add secondary differential equations to a primary + * set of differential equations. + *

    + * In some cases users may need to integrate some problem-specific equations along + * with a primary set of differential equations. One example is optimal control where + * adjoined parameters linked to the minimized Hamiltonian must be integrated. + *

    + *

    + * This interface allows users to add such equations to a primary set of {@link + * FirstOrderFieldDifferentialEquations first order differential equations} + * thanks to the {@link FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} + * method. + *

    + * @see FirstOrderFieldDifferentialEquations + * @see FieldExpandableODE + * @param the type of the field elements + * @since 3.6 + */ +public interface FieldSecondaryEquations> { + + /** Get the dimension of the secondary state parameters. + * @return dimension of the secondary state parameters + */ + int getDimension(); + + /** Initialize equations at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the equations to initialize some internal data + * if needed. + *

    + * @param t0 value of the independent time variable at integration start + * @param primary0 array containing the value of the primary state vector at integration start + * @param secondary0 array containing the value of the secondary state vector at integration start + * @param finalTime target time for the integration + */ + void init(T t0, T[] primary0, T[] secondary0, T finalTime); + + /** Compute the derivatives related to the secondary state parameters. + * @param t current value of the independent time variable + * @param primary array containing the current value of the primary state vector + * @param primaryDot array containing the derivative of the primary state vector + * @param secondary array containing the current value of the secondary state vector + * @return derivative of the secondary state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + T[] computeDerivatives(T t, T[] primary, T[] primaryDot, T[] secondary) + throws MaxCountExceededException, DimensionMismatchException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderConverter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderConverter.java new file mode 100644 index 000000000..7245d84ca --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderConverter.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 com.fr.third.org.apache.commons.math3.ode; + + +/** This class converts second order differential equations to first + * order ones. + * + *

    This class is a wrapper around a {@link + * SecondOrderDifferentialEquations} which allow to use a {@link + * FirstOrderIntegrator} to integrate it.

    + * + *

    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.

    + * + *

    One should be aware that the data is duplicated during the + * transformation process and that for each call to {@link + * #computeDerivatives computeDerivatives}, this wrapper does copy 4n + * scalars : 2n before the call to {@link + * SecondOrderDifferentialEquations#computeSecondDerivatives + * computeSecondDerivatives} in order to dispatch the y state vector + * into z and zDot, and 2n after the call to gather zDot and zDDot + * into yDot. Since the underlying problem by itself perhaps also + * needs to copy data and dispatch the arrays into domain objects, + * this has an impact on both memory and CPU usage. The only way to + * avoid this duplication is to perform the transformation at the + * problem level, i.e. to implement the problem as a first order one + * and then avoid using this class.

    + * + * @see FirstOrderIntegrator + * @see FirstOrderDifferentialEquations + * @see SecondOrderDifferentialEquations + * @since 1.2 + */ + +public class FirstOrderConverter implements FirstOrderDifferentialEquations { + + /** Underlying second order equations set. */ + private final SecondOrderDifferentialEquations equations; + + /** second order problem dimension. */ + private final int dimension; + + /** state vector. */ + private final double[] z; + + /** first time derivative of the state vector. */ + private final double[] zDot; + + /** second time derivative of the state vector. */ + private final double[] zDDot; + + /** Simple constructor. + * Build a converter around a second order equations set. + * @param equations second order equations set to convert + */ + public FirstOrderConverter (final SecondOrderDifferentialEquations equations) { + this.equations = equations; + dimension = equations.getDimension(); + z = new double[dimension]; + zDot = new double[dimension]; + zDDot = new double[dimension]; + } + + /** Get the dimension of the problem. + *

    The dimension of the first order problem is twice the + * dimension of the underlying second order problem.

    + * @return dimension of the problem + */ + public int getDimension() { + return 2 * dimension; + } + + /** Get the current time derivative of the state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param yDot placeholder array where to put the time derivative of the state vector + */ + public void computeDerivatives(final double t, final double[] y, final double[] yDot) { + + // split the state vector in two + System.arraycopy(y, 0, z, 0, dimension); + System.arraycopy(y, dimension, zDot, 0, dimension); + + // apply the underlying equations set + equations.computeSecondDerivatives(t, z, zDot, zDDot); + + // build the result state derivative + System.arraycopy(zDot, 0, yDot, 0, dimension); + System.arraycopy(zDDot, 0, yDot, dimension, dimension); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.java new file mode 100644 index 000000000..8eeaee2ad --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + + +/** This interface represents a first order differential equations set. + * + *

    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.

    + * + *

    A first order differential equations problem, as seen by an + * integrator is the time derivative dY/dt of a state + * vector Y, both being one dimensional arrays. From the + * integrator point of view, this derivative depends only on the + * current time t and on the state vector + * Y.

    + * + *

    For real problems, the derivative depends also on parameters + * that do not belong to the state vector (dynamical model constants + * for example). These constants are completely outside of the scope + * of this interface, the classes that implement it are allowed to + * handle them as they want.

    + * + * @see FirstOrderIntegrator + * @see FirstOrderConverter + * @see SecondOrderDifferentialEquations + * + * @since 1.2 + */ + +public interface FirstOrderDifferentialEquations { + + /** Get the dimension of the problem. + * @return dimension of the problem + */ + int getDimension(); + + /** Get the current time derivative of the state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param yDot placeholder array where to put the time derivative of the state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + void computeDerivatives(double t, double[] y, double[] yDot) + throws MaxCountExceededException, DimensionMismatchException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.java new file mode 100644 index 000000000..63b2a40bb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** This interface represents a first order differential equations set. + * + *

    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.

    + * + *

    A first order differential equations problem, as seen by an + * integrator is the time derivative dY/dt of a state + * vector Y, both being one dimensional arrays. From the + * integrator point of view, this derivative depends only on the + * current time t and on the state vector + * Y.

    + * + *

    For real problems, the derivative depends also on parameters + * that do not belong to the state vector (dynamical model constants + * for example). These constants are completely outside of the scope + * of this interface, the classes that implement it are allowed to + * handle them as they want.

    + * + * @see FirstOrderFieldIntegrator + * + * @param the type of the field elements + * @since 3.6 + */ + +public interface FirstOrderFieldDifferentialEquations> { + + /** Get the dimension of the problem. + * @return dimension of the problem + */ + int getDimension(); + + /** Initialize equations at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the equations to initialize some internal data + * if needed. + *

    + * @param t0 value of the independent time variable at integration start + * @param y0 array containing the value of the state vector at integration start + * @param finalTime target time for the integration + */ + void init(T t0, T[] y0, T finalTime); + + /** Get the current time derivative of the state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @return time derivative of the state vector + */ + T[] computeDerivatives(T t, T[] y); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.java new file mode 100644 index 000000000..f8e00b4e1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.ode.events.FieldEventHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepHandler; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** This interface represents a first order integrator for + * differential equations. + + *

    The classes which are devoted to solve first order differential + * equations should implement this interface. The problems which can + * be handled should implement the {@link + * FirstOrderDifferentialEquations} interface.

    + * + * @see FirstOrderFieldDifferentialEquations + * @param the type of the field elements + * @since 3.6 + */ + +public interface FirstOrderFieldIntegrator> { + + /** Get the name of the method. + * @return name of the method + */ + String getName(); + + /** Add a step handler to this integrator. + *

    The handler will be called by the integrator for each accepted + * step.

    + * @param handler handler for the accepted steps + * @see #getStepHandlers() + * @see #clearStepHandlers() + */ + void addStepHandler(FieldStepHandler handler); + + /** Get all the step handlers that have been added to the integrator. + * @return an unmodifiable collection of the added events handlers + * @see #addStepHandler(FieldStepHandler) + * @see #clearStepHandlers() + */ + Collection> getStepHandlers(); + + /** Remove all the step handlers that have been added to the integrator. + * @see #addStepHandler(FieldStepHandler) + * @see #getStepHandlers() + */ + void clearStepHandlers(); + + /** Add an event handler to the integrator. + *

    + * The default solver is a 5th order {@link + * FieldBracketingNthOrderBrentSolver}. + *

    + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search events. + * @see #addEventHandler(FieldEventHandler, double, double, int, + * BracketedRealFieldUnivariateSolver) + * @see #getEventHandlers() + * @see #clearEventHandlers() + */ + void addEventHandler(FieldEventHandler handler, double maxCheckInterval, + double convergence, int maxIterationCount); + + /** Add an event handler to the integrator. + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search events. + * @param solver solver to use to locate the event + * @see #addEventHandler(FieldEventHandler, double, double, int) + * @see #getEventHandlers() + * @see #clearEventHandlers() + */ + void addEventHandler(FieldEventHandler handler, double maxCheckInterval, + double convergence, int maxIterationCount, + BracketedRealFieldUnivariateSolver solver); + + /** Get all the event handlers that have been added to the integrator. + * @return an unmodifiable collection of the added events handlers + * @see #addEventHandler(FieldEventHandler, double, double, int) + * @see #clearEventHandlers() + */ + Collection > getEventHandlers(); + + /** Remove all the event handlers that have been added to the integrator. + * @see #addEventHandler(FieldEventHandler, double, double, int) + * @see #getEventHandlers() + */ + void clearEventHandlers(); + + /** Get the current value of the step start time ti. + *

    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.

    + *

    The result is undefined if the method is called outside of + * calls to integrate.

    + * @return current value of the state at step start time ti + */ + FieldODEStateAndDerivative getCurrentStepStart(); + + /** Get the current signed value of the integration stepsize. + *

    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.

    + *

    The result is undefined if the method is called outside of + * calls to integrate.

    + * @return current signed value of the stepsize + */ + T getCurrentSignedStepsize(); + + /** Set the maximal number of differential equations function evaluations. + *

    The purpose of this method is to avoid infinite loops which can occur + * for example when stringent error constraints are set or when lots of + * discrete events are triggered, thus leading to many rejected steps.

    + * @param maxEvaluations maximal number of function evaluations (negative + * values are silently converted to maximal integer value, thus representing + * almost unlimited evaluations) + */ + void setMaxEvaluations(int maxEvaluations); + + /** Get the maximal number of functions evaluations. + * @return maximal number of functions evaluations + */ + int getMaxEvaluations(); + + /** Get the number of evaluations of the differential equations function. + *

    + * The number of evaluations corresponds to the last call to the + * integrate method. It is 0 if the method has not been called yet. + *

    + * @return number of evaluations of the differential equations function + */ + int getEvaluations(); + + /** Integrate the differential equations up to the given time. + *

    This method solves an Initial Value Problem (IVP).

    + *

    Since this method stores some internal state variables made + * available in its public interface during integration ({@link + * #getCurrentSignedStepsize()}), it is not thread-safe.

    + * @param equations differential equations to integrate + * @param initialState initial state (time, primary and secondary state vectors) + * @param finalTime target time for the integration + * (can be set to a value smaller than {@code t0} for backward integration) + * @return final state, its time will be the same as {@code finalTime} if + * integration reached its target, but may be different if some {@link + * FieldEventHandler} stops it at some point. + * @exception NumberIsTooSmallException if integration step is too small + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + */ + FieldODEStateAndDerivative integrate(FieldExpandableODE equations, + FieldODEState initialState, T finalTime) + throws NumberIsTooSmallException, MaxCountExceededException, NoBracketingException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderIntegrator.java new file mode 100644 index 000000000..c4ab242a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/FirstOrderIntegrator.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.ode.events.EventHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; + +/** This interface represents a first order integrator for + * differential equations. + + *

    The classes which are devoted to solve first order differential + * equations should implement this interface. The problems which can + * be handled should implement the {@link + * FirstOrderDifferentialEquations} interface.

    + * + * @see FirstOrderDifferentialEquations + * @see StepHandler + * @see EventHandler + * @since 1.2 + */ + +public interface FirstOrderIntegrator extends ODEIntegrator { + + /** Integrate the differential equations up to the given time. + *

    This method solves an Initial Value Problem (IVP).

    + *

    Since this method stores some internal state variables made + * available in its public interface during integration ({@link + * #getCurrentSignedStepsize()}), it is not thread-safe.

    + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than t0 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 + * EventHandler} stops it at some point. + * @exception DimensionMismatchException if arrays dimension do not match equations settings + * @exception NumberIsTooSmallException if integration step is too small + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + */ + double integrate (FirstOrderDifferentialEquations equations, + double t0, double[] y0, double t, double[] y) + throws DimensionMismatchException, NumberIsTooSmallException, + MaxCountExceededException, NoBracketingException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/JacobianMatrices.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/JacobianMatrices.java new file mode 100644 index 000000000..09b18aa05 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/JacobianMatrices.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 com.fr.third.org.apache.commons.math3.ode; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * This class defines a set of {@link SecondaryEquations secondary equations} to + * compute the Jacobian matrices with respect to the initial state vector and, if + * any, to some parameters of the primary ODE set. + *

    + * It is intended to be packed into an {@link ExpandableStatefulODE} + * in conjunction with a primary set of ODE, which may be: + *

      + *
    • a {@link FirstOrderDifferentialEquations}
    • + *
    • a {@link MainStateJacobianProvider}
    • + *
    + * In order to compute Jacobian matrices with respect to some parameters of the + * primary ODE set, the following parameter Jacobian providers may be set: + *
      + *
    • a {@link ParameterJacobianProvider}
    • + *
    • a {@link ParameterizedODE}
    • + *
    + *

    + * + * @see ExpandableStatefulODE + * @see FirstOrderDifferentialEquations + * @see MainStateJacobianProvider + * @see ParameterJacobianProvider + * @see ParameterizedODE + * + * @since 3.0 + */ +public class JacobianMatrices { + + /** Expandable first order differential equation. */ + private ExpandableStatefulODE efode; + + /** Index of the instance in the expandable set. */ + private int index; + + /** FODE with exact primary Jacobian computation skill. */ + private MainStateJacobianProvider jode; + + /** FODE without exact parameter Jacobian computation skill. */ + private ParameterizedODE pode; + + /** Main state vector dimension. */ + private int stateDim; + + /** Selected parameters for parameter Jacobian computation. */ + private ParameterConfiguration[] selectedParameters; + + /** FODE with exact parameter Jacobian computation skill. */ + private List jacobianProviders; + + /** Parameters dimension. */ + private int paramDim; + + /** Boolean for selected parameters consistency. */ + private boolean dirtyParameter; + + /** State and parameters Jacobian matrices in a row. */ + private double[] matricesData; + + /** Simple constructor for a secondary equations set computing Jacobian matrices. + *

    + * Parameters must belong to the supported ones given by {@link + * Parameterizable#getParametersNames()}, so the primary set of differential + * equations must be {@link Parameterizable}. + *

    + *

    Note that each selection clears the previous selected parameters.

    + * + * @param fode the primary first order differential equations set to extend + * @param hY step used for finite difference computation with respect to state vector + * @param parameters parameters to consider for Jacobian matrices processing + * (may be null if parameters Jacobians is not desired) + * @exception DimensionMismatchException if there is a dimension mismatch between + * the steps array {@code hY} and the equation dimension + */ + public JacobianMatrices(final FirstOrderDifferentialEquations fode, final double[] hY, + final String... parameters) + throws DimensionMismatchException { + this(new MainStateJacobianWrapper(fode, hY), parameters); + } + + /** Simple constructor for a secondary equations set computing Jacobian matrices. + *

    + * Parameters must belong to the supported ones given by {@link + * Parameterizable#getParametersNames()}, so the primary set of differential + * equations must be {@link Parameterizable}. + *

    + *

    Note that each selection clears the previous selected parameters.

    + * + * @param jode the primary first order differential equations set to extend + * @param parameters parameters to consider for Jacobian matrices processing + * (may be null if parameters Jacobians is not desired) + */ + public JacobianMatrices(final MainStateJacobianProvider jode, + final String... parameters) { + + this.efode = null; + this.index = -1; + + this.jode = jode; + this.pode = null; + + this.stateDim = jode.getDimension(); + + if (parameters == null) { + selectedParameters = null; + paramDim = 0; + } else { + this.selectedParameters = new ParameterConfiguration[parameters.length]; + for (int i = 0; i < parameters.length; ++i) { + selectedParameters[i] = new ParameterConfiguration(parameters[i], Double.NaN); + } + paramDim = parameters.length; + } + this.dirtyParameter = false; + + this.jacobianProviders = new ArrayList(); + + // set the default initial state Jacobian to the identity + // and the default initial parameters Jacobian to the null matrix + matricesData = new double[(stateDim + paramDim) * stateDim]; + for (int i = 0; i < stateDim; ++i) { + matricesData[i * (stateDim + 1)] = 1.0; + } + + } + + /** Register the variational equations for the Jacobians matrices to the expandable set. + * @param expandable expandable set into which variational equations should be registered + * @throws DimensionMismatchException if the dimension of the partial state does not + * match the selected equations set dimension + * @exception MismatchedEquations if the primary set of the expandable set does + * not match the one used to build the instance + * @see ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations) + */ + public void registerVariationalEquations(final ExpandableStatefulODE expandable) + throws DimensionMismatchException, MismatchedEquations { + + // safety checks + final FirstOrderDifferentialEquations ode = (jode instanceof MainStateJacobianWrapper) ? + ((MainStateJacobianWrapper) jode).ode : + jode; + if (expandable.getPrimary() != ode) { + throw new MismatchedEquations(); + } + + efode = expandable; + index = efode.addSecondaryEquations(new JacobiansSecondaryEquations()); + efode.setSecondaryState(index, matricesData); + + } + + /** Add a parameter Jacobian provider. + * @param provider the parameter Jacobian provider to compute exactly the parameter Jacobian matrix + */ + public void addParameterJacobianProvider(final ParameterJacobianProvider provider) { + jacobianProviders.add(provider); + } + + /** Set a parameter Jacobian provider. + * @param parameterizedOde the parameterized ODE to compute the parameter Jacobian matrix using finite differences + */ + public void setParameterizedODE(final ParameterizedODE parameterizedOde) { + this.pode = parameterizedOde; + dirtyParameter = true; + } + + /** Set the step associated to a parameter in order to compute by finite + * difference the Jacobian matrix. + *

    + * Needed if and only if the primary ODE set is a {@link ParameterizedODE}. + *

    + *

    + * Given a non zero parameter value pval for the parameter, a reasonable value + * for such a step is {@code pval * FastMath.sqrt(Precision.EPSILON)}. + *

    + *

    + * A zero value for such a step doesn't enable to compute the parameter Jacobian matrix. + *

    + * @param parameter parameter to consider for Jacobian processing + * @param hP step for Jacobian finite difference computation w.r.t. the specified parameter + * @see ParameterizedODE + * @exception UnknownParameterException if the parameter is not supported + */ + public void setParameterStep(final String parameter, final double hP) + throws UnknownParameterException { + + for (ParameterConfiguration param: selectedParameters) { + if (parameter.equals(param.getParameterName())) { + param.setHP(hP); + dirtyParameter = true; + return; + } + } + + throw new UnknownParameterException(parameter); + + } + + /** Set the initial value of the Jacobian matrix with respect to state. + *

    + * If this method is not called, the initial value of the Jacobian + * matrix with respect to state is set to identity. + *

    + * @param dYdY0 initial Jacobian matrix w.r.t. state + * @exception DimensionMismatchException if matrix dimensions are incorrect + */ + public void setInitialMainStateJacobian(final double[][] dYdY0) + throws DimensionMismatchException { + + // Check dimensions + checkDimension(stateDim, dYdY0); + checkDimension(stateDim, dYdY0[0]); + + // store the matrix in row major order as a single dimension array + int i = 0; + for (final double[] row : dYdY0) { + System.arraycopy(row, 0, matricesData, i, stateDim); + i += stateDim; + } + + if (efode != null) { + efode.setSecondaryState(index, matricesData); + } + + } + + /** Set the initial value of a column of the Jacobian matrix with respect to one parameter. + *

    + * If this method is not called for some parameter, the initial value of + * the column of the Jacobian matrix with respect to this parameter is set to zero. + *

    + * @param pName parameter name + * @param dYdP initial Jacobian column vector with respect to the parameter + * @exception UnknownParameterException if a parameter is not supported + * @throws DimensionMismatchException if the column vector does not match state dimension + */ + public void setInitialParameterJacobian(final String pName, final double[] dYdP) + throws UnknownParameterException, DimensionMismatchException { + + // Check dimensions + checkDimension(stateDim, dYdP); + + // store the column in a global single dimension array + int i = stateDim * stateDim; + for (ParameterConfiguration param: selectedParameters) { + if (pName.equals(param.getParameterName())) { + System.arraycopy(dYdP, 0, matricesData, i, stateDim); + if (efode != null) { + efode.setSecondaryState(index, matricesData); + } + return; + } + i += stateDim; + } + + throw new UnknownParameterException(pName); + + } + + /** Get the current value of the Jacobian matrix with respect to state. + * @param dYdY0 current Jacobian matrix with respect to state. + */ + public void getCurrentMainSetJacobian(final double[][] dYdY0) { + + // get current state for this set of equations from the expandable fode + double[] p = efode.getSecondaryState(index); + + int j = 0; + for (int i = 0; i < stateDim; i++) { + System.arraycopy(p, j, dYdY0[i], 0, stateDim); + j += stateDim; + } + + } + + /** Get the current value of the Jacobian matrix with respect to one parameter. + * @param pName name of the parameter for the computed Jacobian matrix + * @param dYdP current Jacobian matrix with respect to the named parameter + */ + public void getCurrentParameterJacobian(String pName, final double[] dYdP) { + + // get current state for this set of equations from the expandable fode + double[] p = efode.getSecondaryState(index); + + int i = stateDim * stateDim; + for (ParameterConfiguration param: selectedParameters) { + if (param.getParameterName().equals(pName)) { + System.arraycopy(p, i, dYdP, 0, stateDim); + return; + } + i += stateDim; + } + + } + + /** Check array dimensions. + * @param expected expected dimension + * @param array (may be null if expected is 0) + * @throws DimensionMismatchException if the array dimension does not match the expected one + */ + private void checkDimension(final int expected, final Object array) + throws DimensionMismatchException { + int arrayDimension = (array == null) ? 0 : Array.getLength(array); + if (arrayDimension != expected) { + throw new DimensionMismatchException(arrayDimension, expected); + } + } + + /** Local implementation of secondary equations. + *

    + * This class is an inner class to ensure proper scheduling of calls + * by forcing the use of {@link JacobianMatrices#registerVariationalEquations(ExpandableStatefulODE)}. + *

    + */ + private class JacobiansSecondaryEquations implements SecondaryEquations { + + /** {@inheritDoc} */ + public int getDimension() { + return stateDim * (stateDim + paramDim); + } + + /** {@inheritDoc} */ + public void computeDerivatives(final double t, final double[] y, final double[] yDot, + final double[] z, final double[] zDot) + throws MaxCountExceededException, DimensionMismatchException { + + // Lazy initialization + if (dirtyParameter && (paramDim != 0)) { + jacobianProviders.add(new ParameterJacobianWrapper(jode, pode, selectedParameters)); + dirtyParameter = false; + } + + // variational equations: + // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt + + // compute Jacobian matrix with respect to primary state + double[][] dFdY = new double[stateDim][stateDim]; + jode.computeMainStateJacobian(t, y, yDot, dFdY); + + // Dispatch Jacobian matrix in the compound secondary state vector + for (int i = 0; i < stateDim; ++i) { + final double[] dFdYi = dFdY[i]; + for (int j = 0; j < stateDim; ++j) { + double s = 0; + final int startIndex = j; + int zIndex = startIndex; + for (int l = 0; l < stateDim; ++l) { + s += dFdYi[l] * z[zIndex]; + zIndex += stateDim; + } + zDot[startIndex + i * stateDim] = s; + } + } + + if (paramDim != 0) { + // compute Jacobian matrices with respect to parameters + double[] dFdP = new double[stateDim]; + int startIndex = stateDim * stateDim; + for (ParameterConfiguration param: selectedParameters) { + boolean found = false; + for (int k = 0 ; (!found) && (k < jacobianProviders.size()); ++k) { + final ParameterJacobianProvider provider = jacobianProviders.get(k); + if (provider.isSupported(param.getParameterName())) { + provider.computeParameterJacobian(t, y, yDot, + param.getParameterName(), dFdP); + for (int i = 0; i < stateDim; ++i) { + final double[] dFdYi = dFdY[i]; + int zIndex = startIndex; + double s = dFdP[i]; + for (int l = 0; l < stateDim; ++l) { + s += dFdYi[l] * z[zIndex]; + zIndex++; + } + zDot[startIndex + i] = s; + } + found = true; + } + } + if (! found) { + Arrays.fill(zDot, startIndex, startIndex + stateDim, 0.0); + } + startIndex += stateDim; + } + } + + } + } + + /** Wrapper class to compute jacobian matrices by finite differences for ODE + * which do not compute them by themselves. + */ + private static class MainStateJacobianWrapper implements MainStateJacobianProvider { + + /** Raw ODE without jacobians computation skill to be wrapped into a MainStateJacobianProvider. */ + private final FirstOrderDifferentialEquations ode; + + /** Steps for finite difference computation of the jacobian df/dy w.r.t. state. */ + private final double[] hY; + + /** Wrap a {@link FirstOrderDifferentialEquations} into a {@link MainStateJacobianProvider}. + * @param ode original ODE problem, without jacobians computation skill + * @param hY step sizes to compute the jacobian df/dy + * @exception DimensionMismatchException if there is a dimension mismatch between + * the steps array {@code hY} and the equation dimension + */ + MainStateJacobianWrapper(final FirstOrderDifferentialEquations ode, + final double[] hY) + throws DimensionMismatchException { + this.ode = ode; + this.hY = hY.clone(); + if (hY.length != ode.getDimension()) { + throw new DimensionMismatchException(ode.getDimension(), hY.length); + } + } + + /** {@inheritDoc} */ + public int getDimension() { + return ode.getDimension(); + } + + /** {@inheritDoc} */ + public void computeDerivatives(double t, double[] y, double[] yDot) + throws MaxCountExceededException, DimensionMismatchException { + ode.computeDerivatives(t, y, yDot); + } + + /** {@inheritDoc} */ + public void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY) + throws MaxCountExceededException, DimensionMismatchException { + + final int n = ode.getDimension(); + final double[] tmpDot = new double[n]; + + for (int j = 0; j < n; ++j) { + final double savedYj = y[j]; + y[j] += hY[j]; + ode.computeDerivatives(t, y, tmpDot); + for (int i = 0; i < n; ++i) { + dFdY[i][j] = (tmpDot[i] - yDot[i]) / hY[j]; + } + y[j] = savedYj; + } + } + + } + + /** + * Special exception for equations mismatch. + * @since 3.1 + */ + public static class MismatchedEquations extends MathIllegalArgumentException { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120902L; + + /** Simple constructor. */ + public MismatchedEquations() { + super(LocalizedFormats.UNMATCHED_ODE_IN_EXPANDED_SET); + } + + } + +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MainStateJacobianProvider.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MainStateJacobianProvider.java new file mode 100644 index 000000000..8863ef928 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MainStateJacobianProvider.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + +/** Interface expanding {@link FirstOrderDifferentialEquations first order + * differential equations} in order to compute exactly the main state jacobian + * matrix for {@link JacobianMatrices partial derivatives equations}. + * + * @since 3.0 + */ +public interface MainStateJacobianProvider extends FirstOrderDifferentialEquations { + + /** Compute the jacobian matrix of ODE with respect to main state. + * @param t current value of the independent time variable + * @param y array containing the current value of the main state vector + * @param yDot array containing the current value of the time derivative of the main state vector + * @param dFdY placeholder array where to put the jacobian matrix of the ODE w.r.t. the main state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY) + throws MaxCountExceededException, DimensionMismatchException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepFieldIntegrator.java new file mode 100644 index 000000000..8eb6c5e45 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepFieldIntegrator.java @@ -0,0 +1,456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsBashforthFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsMoultonFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.DormandPrince853FieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * This class is the base class for multistep integrators for Ordinary + * Differential Equations. + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + *

    Rather than storing several previous steps separately, this implementation uses + * the Nordsieck vector with higher degrees scaled derivatives all taken at the same + * step (yn, s1(n) and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (we omit the k index in the notation for clarity)

    + *

    + * Multistep integrators with Nordsieck representation are highly sensitive to + * large step changes because when the step is multiplied by factor a, the + * kth component of the Nordsieck vector is multiplied by ak + * and the last components are the least accurate ones. The default max growth + * factor is therefore set to a quite low value: 21/order. + *

    + * + * @see AdamsBashforthFieldIntegrator + * @see AdamsMoultonFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ +public abstract class MultistepFieldIntegrator> + extends AdaptiveStepsizeFieldIntegrator { + + /** First scaled derivative (h y'). */ + protected T[] scaled; + + /** Nordsieck matrix of the higher scaled derivatives. + *

    (h2/2 y'', h3/6 y''' ..., hk/k! y(k))

    + */ + protected Array2DRowFieldMatrix nordsieck; + + /** Starter integrator. */ + private FirstOrderFieldIntegrator 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. + *

    The default starter integrator is set to the {@link + * DormandPrince853FieldIntegrator Dormand-Prince 8(5,3)} integrator with + * some defaults settings.

    + *

    + * The default max growth factor is set to a quite low value: 21/order. + *

    + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @param order order of the method + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if number of steps is smaller than 2 + */ + protected MultistepFieldIntegrator(final Field field, final String name, + final int nSteps, final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + + super(field, name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + if (nSteps < 2) { + throw new NumberIsTooSmallException( + LocalizedFormats.INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS, + nSteps, 2, true); + } + + starter = new DormandPrince853FieldIntegrator(field, minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + this.nSteps = nSteps; + + exp = -1.0 / order; + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(FastMath.pow(2.0, -exp)); + + } + + /** + * Build a multistep integrator with the given stepsize bounds. + *

    The default starter integrator is set to the {@link + * DormandPrince853FieldIntegrator Dormand-Prince 8(5,3)} integrator with + * some defaults settings.

    + *

    + * The default max growth factor is set to a quite low value: 21/order. + *

    + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @param order order of the method + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected MultistepFieldIntegrator(final Field field, final String name, final int nSteps, + final int order, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + starter = new DormandPrince853FieldIntegrator(field, minStep, maxStep, + vecAbsoluteTolerance, + vecRelativeTolerance); + this.nSteps = nSteps; + + exp = -1.0 / order; + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(FastMath.pow(2.0, -exp)); + + } + + /** + * Get the starter integrator. + * @return starter integrator + */ + public FirstOrderFieldIntegrator getStarterIntegrator() { + return starter; + } + + /** + * Set the starter integrator. + *

    The various step and event handlers for this starter integrator + * will be managed automatically by the multi-step integrator. Any + * user configuration for these elements will be cleared before use.

    + * @param starterIntegrator starter integrator + */ + public void setStarterIntegrator(FirstOrderFieldIntegrator starterIntegrator) { + this.starter = starterIntegrator; + } + + /** Start the integration. + *

    This method computes one step using the underlying starter integrator, + * and initializes the Nordsieck vector at step start. The starter integrator + * purpose is only to establish initial conditions, it does not really change + * time by itself. The top level multistep integrator remains in charge of + * handling time propagation and events handling as it will starts its own + * computation right from the beginning. In a sense, the starter integrator + * can be seen as a dummy one and so it will never trigger any user event nor + * call any user step handler.

    + * @param equations complete set of differential equations to integrate + * @param initialState initial state (time, primary and secondary state vectors) + * @param t target time for the integration + * (can be set to a value smaller than t0 for backward integration) + * @exception DimensionMismatchException if arrays dimension do not match equations settings + * @exception NumberIsTooSmallException if integration step is too small + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + */ + protected void start(final FieldExpandableODE equations, final FieldODEState initialState, final T t) + throws DimensionMismatchException, NumberIsTooSmallException, + MaxCountExceededException, NoBracketingException { + + // make sure NO user event nor user step handler is triggered, + // this is the task of the top level integrator, not the task + // of the starter integrator + starter.clearEventHandlers(); + starter.clearStepHandlers(); + + // set up one specific step handler to extract initial Nordsieck vector + starter.addStepHandler(new FieldNordsieckInitializer(equations.getMapper(), (nSteps + 3) / 2)); + + // start integration, expecting a InitializationCompletedMarkerException + try { + + starter.integrate(equations, initialState, t); + + // we should not reach this step + throw new MathIllegalStateException(LocalizedFormats.MULTISTEP_STARTER_STOPPED_EARLY); + + } catch (InitializationCompletedMarkerException icme) { // NOPMD + // this is the expected nominal interruption of the start integrator + + // count the evaluations used by the starter + getEvaluationsCounter().increment(starter.getEvaluations()); + + } + + // remove the specific step handler + starter.clearStepHandlers(); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at first step (h2/2 y''n, + * h3/6 y'''n ... hk/k! y(k)n) + */ + protected abstract Array2DRowFieldMatrix initializeHighOrderDerivatives(final T h, final T[] t, + final T[][] y, + final T[][] yDot); + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public double getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final double minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public double getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final double maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public double getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final double safety) { + this.safety = safety; + } + + /** Get the number of steps of the multistep method (excluding the one being computed). + * @return number of steps of the multistep method (excluding the one being computed) + */ + public int getNSteps() { + return nSteps; + } + + /** Rescale the instance. + *

    Since the scaled and Nordsieck arrays are shared with the caller, + * this method has the side effect of rescaling this arrays in the caller too.

    + * @param newStepSize new step size to use in the scaled and Nordsieck arrays + */ + protected void rescale(final T newStepSize) { + + final T ratio = newStepSize.divide(getStepSize()); + for (int i = 0; i < scaled.length; ++i) { + scaled[i] = scaled[i].multiply(ratio); + } + + final T[][] nData = nordsieck.getDataRef(); + T power = ratio; + for (int i = 0; i < nData.length; ++i) { + power = power.multiply(ratio); + final T[] nDataI = nData[i]; + for (int j = 0; j < nDataI.length; ++j) { + nDataI[j] = nDataI[j].multiply(power); + } + } + + setStepSize(newStepSize); + + } + + + /** Compute step grow/shrink factor according to normalized error. + * @param error normalized error of the current step + * @return grow/shrink factor for next step + */ + protected T computeStepGrowShrinkFactor(final T error) { + return MathUtils.min(error.getField().getZero().add(maxGrowth), + MathUtils.max(error.getField().getZero().add(minReduction), + error.pow(exp).multiply(safety))); + } + + /** Specialized step handler storing the first step. + */ + private class FieldNordsieckInitializer implements FieldStepHandler { + + /** Equation mapper. */ + private final FieldEquationsMapper mapper; + + /** Steps counter. */ + private int count; + + /** Saved start. */ + private FieldODEStateAndDerivative savedStart; + + /** First steps times. */ + private final T[] t; + + /** First steps states. */ + private final T[][] y; + + /** First steps derivatives. */ + private final T[][] yDot; + + /** Simple constructor. + * @param mapper equation mapper + * @param nbStartPoints number of start points (including the initial point) + */ + FieldNordsieckInitializer(final FieldEquationsMapper mapper, final int nbStartPoints) { + this.mapper = mapper; + this.count = 0; + this.t = MathArrays.buildArray(getField(), nbStartPoints); + this.y = MathArrays.buildArray(getField(), nbStartPoints, -1); + this.yDot = MathArrays.buildArray(getField(), nbStartPoints, -1); + } + + /** {@inheritDoc} */ + public void handleStep(FieldStepInterpolator interpolator, boolean isLast) + throws MaxCountExceededException { + + + if (count == 0) { + // first step, we need to store also the point at the beginning of the step + final FieldODEStateAndDerivative prev = interpolator.getPreviousState(); + savedStart = prev; + t[count] = prev.getTime(); + y[count] = mapper.mapState(prev); + yDot[count] = mapper.mapDerivative(prev); + } + + // store the point at the end of the step + ++count; + final FieldODEStateAndDerivative curr = interpolator.getCurrentState(); + t[count] = curr.getTime(); + y[count] = mapper.mapState(curr); + yDot[count] = mapper.mapDerivative(curr); + + if (count == t.length - 1) { + + // this was the last point we needed, we can compute the derivatives + setStepSize(t[t.length - 1].subtract(t[0]).divide(t.length - 1)); + + // first scaled derivative + scaled = MathArrays.buildArray(getField(), yDot[0].length); + for (int j = 0; j < scaled.length; ++j) { + scaled[j] = yDot[0][j].multiply(getStepSize()); + } + + // higher order derivatives + nordsieck = initializeHighOrderDerivatives(getStepSize(), t, y, yDot); + + // stop the integrator now that all needed steps have been handled + setStepStart(savedStart); + throw new InitializationCompletedMarkerException(); + + } + + } + + /** {@inheritDoc} */ + public void init(final FieldODEStateAndDerivative initialState, T finalTime) { + // nothing to do + } + + } + + /** Marker exception used ONLY to stop the starter integrator after first step. */ + private static class InitializationCompletedMarkerException + extends RuntimeException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1914085471038046418L; + + /** Simple constructor. */ + InitializationCompletedMarkerException() { + super((Throwable) null); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepIntegrator.java new file mode 100644 index 000000000..f3399b50a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/MultistepIntegrator.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator; + +/** + * This class is the base class for multistep integrators for Ordinary + * Differential Equations. + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + *

    Rather than storing several previous steps separately, this implementation uses + * the Nordsieck vector with higher degrees scaled derivatives all taken at the same + * step (yn, s1(n) and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (we omit the k index in the notation for clarity)

    + *

    + * Multistep integrators with Nordsieck representation are highly sensitive to + * large step changes because when the step is multiplied by factor a, the + * kth component of the Nordsieck vector is multiplied by ak + * and the last components are the least accurate ones. The default max growth + * factor is therefore set to a quite low value: 21/order. + *

    + * + * @see AdamsBashforthIntegrator + * @see AdamsMoultonIntegrator + * @since 2.0 + */ +public abstract class MultistepIntegrator extends AdaptiveStepsizeIntegrator { + + /** First scaled derivative (h y'). */ + protected double[] scaled; + + /** Nordsieck matrix of the higher scaled derivatives. + *

    (h2/2 y'', h3/6 y''' ..., hk/k! y(k))

    + */ + 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. + *

    The default starter integrator is set to the {@link + * DormandPrince853Integrator Dormand-Prince 8(5,3)} integrator with + * some defaults settings.

    + *

    + * The default max growth factor is set to a quite low value: 21/order. + *

    + * @param name name of the method + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @param order order of the method + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if number of steps is smaller than 2 + */ + protected MultistepIntegrator(final String name, final int nSteps, + final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + + super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + if (nSteps < 2) { + throw new NumberIsTooSmallException( + LocalizedFormats.INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS, + nSteps, 2, true); + } + + starter = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + this.nSteps = nSteps; + + exp = -1.0 / order; + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(FastMath.pow(2.0, -exp)); + + } + + /** + * Build a multistep integrator with the given stepsize bounds. + *

    The default starter integrator is set to the {@link + * DormandPrince853Integrator Dormand-Prince 8(5,3)} integrator with + * some defaults settings.

    + *

    + * The default max growth factor is set to a quite low value: 21/order. + *

    + * @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. + *

    The various step and event handlers for this starter integrator + * will be managed automatically by the multi-step integrator. Any + * user configuration for these elements will be cleared before use.

    + * @param starterIntegrator starter integrator + */ + public void setStarterIntegrator(FirstOrderIntegrator starterIntegrator) { + this.starter = starterIntegrator; + } + + /** Start the integration. + *

    This method computes one step using the underlying starter integrator, + * and initializes the Nordsieck vector at step start. The starter integrator + * purpose is only to establish initial conditions, it does not really change + * time by itself. The top level multistep integrator remains in charge of + * handling time propagation and events handling as it will starts its own + * computation right from the beginning. In a sense, the starter integrator + * can be seen as a dummy one and so it will never trigger any user event nor + * call any user step handler.

    + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than t0 for backward integration) + * @exception DimensionMismatchException if arrays dimension do not match equations settings + * @exception NumberIsTooSmallException if integration step is too small + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception NoBracketingException if the location of an event cannot be bracketed + */ + protected void start(final double t0, final double[] y0, final double t) + throws DimensionMismatchException, NumberIsTooSmallException, + MaxCountExceededException, NoBracketingException { + + // make sure NO user event nor user step handler is triggered, + // this is the task of the top level integrator, not the task + // of the starter integrator + starter.clearEventHandlers(); + starter.clearStepHandlers(); + + // set up one specific step handler to extract initial Nordsieck vector + starter.addStepHandler(new NordsieckInitializer((nSteps + 3) / 2, y0.length)); + + // start integration, expecting a InitializationCompletedMarkerException + try { + + if (starter instanceof AbstractIntegrator) { + ((AbstractIntegrator) starter).integrate(getExpandable(), t); + } else { + starter.integrate(new FirstOrderDifferentialEquations() { + + /** {@inheritDoc} */ + public int getDimension() { + return getExpandable().getTotalDimension(); + } + + /** {@inheritDoc} */ + public void computeDerivatives(double t, double[] y, double[] yDot) { + getExpandable().computeDerivatives(t, y, yDot); + } + + }, t0, y0, t, new double[y0.length]); + } + + // we should not reach this step + throw new MathIllegalStateException(LocalizedFormats.MULTISTEP_STARTER_STOPPED_EARLY); + + } catch (InitializationCompletedMarkerException icme) { // NOPMD + // this is the expected nominal interruption of the start integrator + + // count the evaluations used by the starter + getCounter().increment(starter.getEvaluations()); + + } + + // remove the specific step handler + starter.clearStepHandlers(); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at first step (h2/2 y''n, + * h3/6 y'''n ... hk/k! y(k)n) + */ + protected abstract Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot); + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public double getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final double minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public double getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final double maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public double getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final double safety) { + this.safety = safety; + } + + /** Get the number of steps of the multistep method (excluding the one being computed). + * @return number of steps of the multistep method (excluding the one being computed) + */ + public int getNSteps() { + return nSteps; + } + + /** Compute step grow/shrink factor according to normalized error. + * @param error normalized error of the current step + * @return grow/shrink factor for next step + */ + protected double computeStepGrowShrinkFactor(final double error) { + return FastMath.min(maxGrowth, FastMath.max(minReduction, safety * FastMath.pow(error, exp))); + } + + /** Transformer used to convert the first step to Nordsieck representation. + * @deprecated as of 3.6 this unused interface is deprecated + */ + @Deprecated + public interface NordsieckTransformer { + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at first step (h2/2 y''n, + * h3/6 y'''n ... hk/k! y(k)n) + */ + Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot); + } + + /** Specialized step handler storing the first step. */ + private class NordsieckInitializer implements StepHandler { + + /** Steps counter. */ + private int count; + + /** First steps times. */ + private final double[] t; + + /** First steps states. */ + private final double[][] y; + + /** First steps derivatives. */ + private final double[][] yDot; + + /** Simple constructor. + * @param nbStartPoints number of start points (including the initial point) + * @param n problem dimension + */ + NordsieckInitializer(final int nbStartPoints, final int n) { + this.count = 0; + this.t = new double[nbStartPoints]; + this.y = new double[nbStartPoints][n]; + this.yDot = new double[nbStartPoints][n]; + } + + /** {@inheritDoc} */ + public void handleStep(StepInterpolator interpolator, boolean isLast) + throws MaxCountExceededException { + + final double prev = interpolator.getPreviousTime(); + final double curr = interpolator.getCurrentTime(); + + if (count == 0) { + // first step, we need to store also the point at the beginning of the step + interpolator.setInterpolatedTime(prev); + t[0] = prev; + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), y[count]); + primary.insertEquationData(interpolator.getInterpolatedDerivatives(), yDot[count]); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), y[count]); + secondary.insertEquationData(interpolator.getInterpolatedSecondaryDerivatives(index), yDot[count]); + ++index; + } + } + + // store the point at the end of the step + ++count; + interpolator.setInterpolatedTime(curr); + t[count] = curr; + + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), y[count]); + primary.insertEquationData(interpolator.getInterpolatedDerivatives(), yDot[count]); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), y[count]); + secondary.insertEquationData(interpolator.getInterpolatedSecondaryDerivatives(index), yDot[count]); + ++index; + } + + if (count == t.length - 1) { + + // this was the last point we needed, we can compute the derivatives + stepStart = t[0]; + stepSize = (t[t.length - 1] - t[0]) / (t.length - 1); + + // first scaled derivative + scaled = yDot[0].clone(); + for (int j = 0; j < scaled.length; ++j) { + scaled[j] *= stepSize; + } + + // higher order derivatives + nordsieck = initializeHighOrderDerivatives(stepSize, t, y, yDot); + + // stop the integrator now that all needed steps have been handled + throw new InitializationCompletedMarkerException(); + + } + + } + + /** {@inheritDoc} */ + public void init(double t0, double[] y0, double time) { + // nothing to do + } + + } + + /** Marker exception used ONLY to stop the starter integrator after first step. */ + private static class InitializationCompletedMarkerException + extends RuntimeException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1914085471038046418L; + + /** Simple constructor. */ + InitializationCompletedMarkerException() { + super((Throwable) null); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ODEIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ODEIntegrator.java new file mode 100644 index 000000000..8300268a6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ODEIntegrator.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.ode.events.EventHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; + +/** + * This interface defines the common parts shared by integrators + * for first and second order differential equations. + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @since 2.0 + */ +public interface ODEIntegrator { + + /** Get the name of the method. + * @return name of the method + */ + String getName(); + + /** Add a step handler to this integrator. + *

    The handler will be called by the integrator for each accepted + * step.

    + * @param handler handler for the accepted steps + * @see #getStepHandlers() + * @see #clearStepHandlers() + * @since 2.0 + */ + void addStepHandler(StepHandler handler); + + /** Get all the step handlers that have been added to the integrator. + * @return an unmodifiable collection of the added events handlers + * @see #addStepHandler(StepHandler) + * @see #clearStepHandlers() + * @since 2.0 + */ + Collection getStepHandlers(); + + /** Remove all the step handlers that have been added to the integrator. + * @see #addStepHandler(StepHandler) + * @see #getStepHandlers() + * @since 2.0 + */ + void clearStepHandlers(); + + /** Add an event handler to the integrator. + * Uses a default {@link UnivariateSolver} + * with an absolute accuracy equal to the given convergence threshold, + * as root-finding algorithm to detect the state events. + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search + * @see #getEventHandlers() + * @see #clearEventHandlers() + */ + void addEventHandler(EventHandler handler, double maxCheckInterval, + double convergence, int maxIterationCount); + + /** Add an event handler to the integrator. + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search + * @param solver The root-finding algorithm to use to detect the state + * events. + * @see #getEventHandlers() + * @see #clearEventHandlers() + */ + void addEventHandler(EventHandler handler, double maxCheckInterval, + double convergence, int maxIterationCount, + UnivariateSolver solver); + + /** Get all the event handlers that have been added to the integrator. + * @return an unmodifiable collection of the added events handlers + * @see #addEventHandler(EventHandler, double, double, int) + * @see #clearEventHandlers() + */ + Collection 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 ti. + *

    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.

    + *

    The result is undefined if the method is called outside of + * calls to integrate.

    + * @return current value of the step start time ti + */ + double getCurrentStepStart(); + + /** Get the current signed value of the integration stepsize. + *

    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.

    + *

    The result is undefined if the method is called outside of + * calls to integrate.

    + * @return current signed value of the stepsize + */ + double getCurrentSignedStepsize(); + + /** Set the maximal number of differential equations function evaluations. + *

    The purpose of this method is to avoid infinite loops which can occur + * for example when stringent error constraints are set or when lots of + * discrete events are triggered, thus leading to many rejected steps.

    + * @param maxEvaluations maximal number of function evaluations (negative + * values are silently converted to maximal integer value, thus representing + * almost unlimited evaluations) + */ + void setMaxEvaluations(int maxEvaluations); + + /** Get the maximal number of functions evaluations. + * @return maximal number of functions evaluations + */ + int getMaxEvaluations(); + + /** Get the number of evaluations of the differential equations function. + *

    + * The number of evaluations corresponds to the last call to the + * integrate method. It is 0 if the method has not been called yet. + *

    + * @return number of evaluations of the differential equations function + */ + int getEvaluations(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterConfiguration.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterConfiguration.java new file mode 100644 index 000000000..9a5c5b1a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterConfiguration.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 com.fr.third.org.apache.commons.math3.ode; + +import java.io.Serializable; + +/** Simple container pairing a parameter name with a step in order to compute + * the associated Jacobian matrix by finite difference. + * + * @since 3.0 + */ +class ParameterConfiguration implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 2247518849090889379L; + + /** Parameter name. */ + private String parameterName; + + /** Parameter step for finite difference computation. */ + private double hP; + + /** Parameter name and step pair constructor. + * @param parameterName parameter name + * @param hP parameter step + */ + ParameterConfiguration(final String parameterName, final double hP) { + this.parameterName = parameterName; + this.hP = hP; + } + + /** Get parameter name. + * @return parameterName parameter name + */ + public String getParameterName() { + return parameterName; + } + + /** Get parameter step. + * @return hP parameter step + */ + public double getHP() { + return hP; + } + + /** Set parameter step. + * @param hParam parameter step + */ + public void setHP(final double hParam) { + this.hP = hParam; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianProvider.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianProvider.java new file mode 100644 index 000000000..c570d6ad7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianProvider.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + +/** Interface to compute exactly Jacobian matrix for some parameter + * when computing {@link JacobianMatrices partial derivatives equations}. + * + * @since 3.0 + */ +public interface ParameterJacobianProvider extends Parameterizable { + + /** Compute the Jacobian matrix of ODE with respect to one parameter. + *

    If the parameter does not belong to the collection returned by + * {@link #getParametersNames()}, the Jacobian will be set to 0, + * but no errors will be triggered.

    + * @param t current value of the independent time variable + * @param y array containing the current value of the main state vector + * @param yDot array containing the current value of the time derivative + * of the main state vector + * @param paramName name of the parameter to consider + * @param dFdP placeholder array where to put the Jacobian matrix of the + * ODE with respect to the parameter + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + * @exception UnknownParameterException if the parameter is not supported + */ + void computeParameterJacobian(double t, double[] y, double[] yDot, + String paramName, double[] dFdP) + throws DimensionMismatchException, MaxCountExceededException, UnknownParameterException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianWrapper.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianWrapper.java new file mode 100644 index 000000000..276a3484a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterJacobianWrapper.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + +/** Wrapper class to compute Jacobian matrices by finite differences for ODE + * which do not compute them by themselves. + * + * @since 3.0 + */ +class ParameterJacobianWrapper implements ParameterJacobianProvider { + + /** Main ODE set. */ + private final FirstOrderDifferentialEquations fode; + + /** Raw ODE without Jacobian computation skill to be wrapped into a ParameterJacobianProvider. */ + private final ParameterizedODE pode; + + /** Steps for finite difference computation of the Jacobian df/dp w.r.t. parameters. */ + private final Map hParam; + + /** Wrap a {@link ParameterizedODE} into a {@link ParameterJacobianProvider}. + * @param fode main first order differential equations set + * @param pode secondary problem, without parameter Jacobian computation skill + * @param paramsAndSteps parameters and steps to compute the Jacobians df/dp + * @see JacobianMatrices#setParameterStep(String, double) + */ + ParameterJacobianWrapper(final FirstOrderDifferentialEquations fode, + final ParameterizedODE pode, + final ParameterConfiguration[] paramsAndSteps) { + this.fode = fode; + this.pode = pode; + this.hParam = new HashMap(); + + // set up parameters for jacobian computation + for (final ParameterConfiguration param : paramsAndSteps) { + final String name = param.getParameterName(); + if (pode.isSupported(name)) { + hParam.put(name, param.getHP()); + } + } + } + + /** {@inheritDoc} */ + public Collection getParametersNames() { + return pode.getParametersNames(); + } + + /** {@inheritDoc} */ + public boolean isSupported(String name) { + return pode.isSupported(name); + } + + /** {@inheritDoc} */ + public void computeParameterJacobian(double t, double[] y, double[] yDot, + String paramName, double[] dFdP) + throws DimensionMismatchException, MaxCountExceededException { + + final int n = fode.getDimension(); + if (pode.isSupported(paramName)) { + final double[] tmpDot = new double[n]; + + // compute the jacobian df/dp w.r.t. parameter + final double p = pode.getParameter(paramName); + final double hP = hParam.get(paramName); + pode.setParameter(paramName, p + hP); + fode.computeDerivatives(t, y, tmpDot); + for (int i = 0; i < n; ++i) { + dFdP[i] = (tmpDot[i] - yDot[i]) / hP; + } + pode.setParameter(paramName, p); + } else { + Arrays.fill(dFdP, 0, n, 0.0); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/Parameterizable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/Parameterizable.java new file mode 100644 index 000000000..3535e6c28 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/Parameterizable.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.Collection; + +/** This interface enables to process any parameterizable object. + * + * @since 3.0 + */ + +public interface Parameterizable { + + /** Get the names of the supported parameters. + * @return parameters names + * @see #isSupported(String) + */ + Collection getParametersNames(); + + /** Check if a parameter is supported. + *

    Supported parameters are those listed by {@link #getParametersNames()}.

    + * @param name parameter name to check + * @return true if the parameter is supported + * @see #getParametersNames() + */ + boolean isSupported(String name); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedODE.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedODE.java new file mode 100644 index 000000000..dbcc0386c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedODE.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 com.fr.third.org.apache.commons.math3.ode; + + +/** Interface to compute by finite difference Jacobian matrix for some parameter + * when computing {@link JacobianMatrices partial derivatives equations}. + * + * @since 3.0 + */ + +public interface ParameterizedODE extends Parameterizable { + + /** Get parameter value from its name. + * @param name parameter name + * @return parameter value + * @exception UnknownParameterException if parameter is not supported + */ + double getParameter(String name) throws UnknownParameterException; + + /** Set the value for a given parameter. + * @param name parameter name + * @param value parameter value + * @exception UnknownParameterException if parameter is not supported + */ + void setParameter(String name, double value) throws UnknownParameterException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedWrapper.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedWrapper.java new file mode 100644 index 000000000..35b408763 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/ParameterizedWrapper.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 com.fr.third.org.apache.commons.math3.ode; + +import java.util.ArrayList; +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + +/** Wrapper class enabling {@link FirstOrderDifferentialEquations basic simple} + * ODE instances to be used when processing {@link JacobianMatrices}. + * + * @since 3.0 + */ +class ParameterizedWrapper implements ParameterizedODE { + + /** Basic FODE without parameter. */ + private final FirstOrderDifferentialEquations fode; + + /** Simple constructor. + * @param ode original first order differential equations + */ + ParameterizedWrapper(final FirstOrderDifferentialEquations ode) { + this.fode = ode; + } + + /** Get the dimension of the underlying FODE. + * @return dimension of the underlying FODE + */ + public int getDimension() { + return fode.getDimension(); + } + + /** Get the current time derivative of the state vector of the underlying FODE. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param yDot placeholder array where to put the time derivative of the state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public void computeDerivatives(double t, double[] y, double[] yDot) + throws MaxCountExceededException, DimensionMismatchException { + fode.computeDerivatives(t, y, yDot); + } + + /** {@inheritDoc} */ + public Collection getParametersNames() { + return new ArrayList(); + } + + /** {@inheritDoc} */ + public boolean isSupported(String name) { + return false; + } + + /** {@inheritDoc} */ + public double getParameter(String name) + throws UnknownParameterException { + if (!isSupported(name)) { + throw new UnknownParameterException(name); + } + return Double.NaN; + } + + /** {@inheritDoc} */ + public void setParameter(String name, double value) { + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.java new file mode 100644 index 000000000..6177cd85a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.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 com.fr.third.org.apache.commons.math3.ode; + + +/** This interface represents a second order differential equations set. + + *

    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.

    + * + *

    A second order differential equations problem, as seen by an + * integrator is the second time derivative d2Y/dt^2 of a + * state vector Y, both being one dimensional + * arrays. From the integrator point of view, this derivative depends + * only on the current time t, on the state vector + * Y and on the first time derivative of the state + * vector.

    + * + *

    For real problems, the derivative depends also on parameters + * that do not belong to the state vector (dynamical model constants + * for example). These constants are completely outside of the scope + * of this interface, the classes that implement it are allowed to + * handle them as they want.

    + * + * @see SecondOrderIntegrator + * @see FirstOrderConverter + * @see FirstOrderDifferentialEquations + * @since 1.2 + */ + +public interface SecondOrderDifferentialEquations { + + /** Get the dimension of the problem. + * @return dimension of the problem + */ + int getDimension(); + + /** Get the current time derivative of the state vector. + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param yDot array containing the current value of the first derivative + * of the state vector + * @param yDDot placeholder array where to put the second time derivative + * of the state vector + */ + void computeSecondDerivatives(double t, double[] y, double[] yDot, double[] yDDot); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondOrderIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondOrderIntegrator.java new file mode 100644 index 000000000..f828474f3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; + + +/** This interface represents a second order integrator for + * differential equations. + * + *

    The classes which are devoted to solve second order differential + * equations should implement this interface. The problems which can + * be handled should implement the {@link + * SecondOrderDifferentialEquations} interface.

    + * + * @see SecondOrderDifferentialEquations + * @since 1.2 + */ + +public interface SecondOrderIntegrator extends ODEIntegrator { + + /** Integrate the differential equations up to the given time + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param yDot0 initial value of the first derivative of the state + * vector at t0 + * @param t target time for the integration + * (can be set to a value smaller thant t0 for backward integration) + * @param y placeholder where to put the state vector at each + * successful step (and hence at the end of integration), can be the + * same object as y0 + * @param yDot placeholder where to put the first derivative of + * the state vector at time t, can be the same object as yDot0 + * @throws MathIllegalStateException if the integrator cannot perform integration + * @throws MathIllegalArgumentException if integration parameters are wrong (typically + * too small integration span) + */ + void integrate(SecondOrderDifferentialEquations equations, + double t0, double[] y0, double[] yDot0, + double t, double[] y, double[] yDot) + throws MathIllegalStateException, MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondaryEquations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondaryEquations.java new file mode 100644 index 000000000..0d39b0792 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/SecondaryEquations.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; + +/** + * This interface allows users to add secondary differential equations to a primary + * set of differential equations. + *

    + * In some cases users may need to integrate some problem-specific equations along + * with a primary set of differential equations. One example is optimal control where + * adjoined parameters linked to the minimized hamiltonian must be integrated. + *

    + *

    + * This interface allows users to add such equations to a primary set of {@link + * FirstOrderDifferentialEquations first order differential equations} + * thanks to the {@link + * ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations)} + * method. + *

    + * @see ExpandableStatefulODE + * @since 3.0 + */ +public interface SecondaryEquations { + + /** Get the dimension of the secondary state parameters. + * @return dimension of the secondary state parameters + */ + int getDimension(); + + /** Compute the derivatives related to the secondary state parameters. + * @param t current value of the independent time variable + * @param primary array containing the current value of the primary state vector + * @param primaryDot array containing the derivative of the primary state vector + * @param secondary array containing the current value of the secondary state vector + * @param secondaryDot placeholder array where to put the derivative of the secondary state vector + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + void computeDerivatives(double t, double[] primary, double[] primaryDot, + double[] secondary, double[] secondaryDot) + throws MaxCountExceededException, DimensionMismatchException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/UnknownParameterException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/UnknownParameterException.java new file mode 100644 index 000000000..d6c883cd0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/UnknownParameterException.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 com.fr.third.org.apache.commons.math3.ode; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Exception to be thrown when a parameter is unknown. + * + * @since 3.1 + */ +public class UnknownParameterException extends MathIllegalArgumentException { + + /** Serializable version Id. */ + private static final long serialVersionUID = 20120902L; + + /** Parameter name. */ + private final String name; + + /** + * Construct an exception from the unknown parameter. + * + * @param name parameter name. + */ + public UnknownParameterException(final String name) { + super(LocalizedFormats.UNKNOWN_PARAMETER, name); + this.name = name; + } + + /** + * @return the name of the unknown parameter. + */ + public String getName() { + return name; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Action.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Action.java new file mode 100644 index 000000000..cc193061e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Action.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.events; + +/** Enumerate for actions to be performed when an event occurs during ODE integration. + * @since 3.6 + */ +public enum Action { + + /** Stop indicator. + *

    This value should be used as the return value of the {@code + * eventOccurred} method when the integration should be + * stopped after the event ending the current step.

    + */ + STOP, + + /** Reset state indicator. + *

    This value should be used as the return value of the {@code + * eventOccurred}} method when the integration should + * go on after the event ending the current step, with a new state + * vector (which will be retrieved thanks to the {@code resetState} + * method).

    + */ + RESET_STATE, + + /** Reset derivatives indicator. + *

    This value should be used as the return value of the {@code + * eventOccurred} method when the integration should + * go on after the event ending the current step, with a new derivatives + * vector.

    + */ + RESET_DERIVATIVES, + + /** Continue indicator. + *

    This value should be used as the return value of the {@code + * eventOccurred} method when the integration should go + * on after the event ending the current step.

    + */ + CONTINUE; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventFilter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventFilter.java new file mode 100644 index 000000000..3a613af9d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventFilter.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator; + +import java.util.Arrays; + +/** Wrapper used to detect only increasing or decreasing events. + * + *

    General {@link EventHandler events} are defined implicitly + * by a {@link EventHandler#g(double, double[]) g function} crossing + * zero. This function needs to be continuous in the event neighborhood, + * and its sign must remain consistent between events. This implies that + * during an ODE integration, events triggered are alternately events + * for which the function increases from negative to positive values, + * and events for which the function decreases from positive to + * negative values. + *

    + * + *

    Sometimes, users are only interested in one type of event (say + * increasing events for example) and not in the other type. In these + * cases, looking precisely for all events location and triggering + * events that will later be ignored is a waste of computing time.

    + * + *

    Users can wrap a regular {@link EventHandler event handler} in + * an instance of this class and provide this wrapping instance to + * the {@link FirstOrderIntegrator ODE solver} + * in order to avoid wasting time looking for uninteresting events. + * The wrapper will intercept the calls to the {@link + * EventHandler#g(double, double[]) g function} and to the {@link + * EventHandler#eventOccurred(double, double[], boolean) + * eventOccurred} method in order to ignore uninteresting events. The + * wrapped regular {@link EventHandler event handler} will the see only + * the interesting events, i.e. either only {@code increasing} events or + * {@code decreasing} events. the number of calls to the {@link + * EventHandler#g(double, double[]) g function} will also be reduced.

    + * + * @since 3.2 + */ + +public class EventFilter implements EventHandler { + + /** Number of past transformers updates stored. */ + private static final int HISTORY_SIZE = 100; + + /** Wrapped event handler. */ + private final EventHandler rawHandler; + + /** Filter to use. */ + private final FilterType filter; + + /** Transformers of the g function. */ + private final Transformer[] transformers; + + /** Update time of the transformers. */ + private final double[] updates; + + /** Indicator for forward integration. */ + private boolean forward; + + /** Extreme time encountered so far. */ + private double extremeT; + + /** Wrap an {@link EventHandler event handler}. + * @param rawHandler event handler to wrap + * @param filter filter to use + */ + public EventFilter(final EventHandler rawHandler, final FilterType filter) { + this.rawHandler = rawHandler; + this.filter = filter; + this.transformers = new Transformer[HISTORY_SIZE]; + this.updates = new double[HISTORY_SIZE]; + } + + /** {@inheritDoc} */ + public void init(double t0, double[] y0, double t) { + + // delegate to raw handler + rawHandler.init(t0, y0, t); + + // initialize events triggering logic + forward = t >= t0; + extremeT = forward ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + Arrays.fill(transformers, Transformer.UNINITIALIZED); + Arrays.fill(updates, extremeT); + + } + + /** {@inheritDoc} */ + public double g(double t, double[] y) { + + final double rawG = rawHandler.g(t, y); + + // search which transformer should be applied to g + if (forward) { + final int last = transformers.length - 1; + if (extremeT < t) { + // we are at the forward end of the history + + // check if a new rough root has been crossed + final Transformer previous = transformers[last]; + final Transformer next = filter.selectTransformer(previous, rawG, forward); + if (next != previous) { + // there is a root somewhere between extremeT and t. + // the new transformer is valid for t (this is how we have just computed + // it above), but it is in fact valid on both sides of the root, so + // it was already valid before t and even up to previous time. We store + // the switch at extremeT for safety, to ensure the previous transformer + // is not applied too close of the root + System.arraycopy(updates, 1, updates, 0, last); + System.arraycopy(transformers, 1, transformers, 0, last); + updates[last] = extremeT; + transformers[last] = next; + } + + extremeT = t; + + // apply the transform + return next.transformed(rawG); + + } else { + // we are in the middle of the history + + // select the transformer + for (int i = last; i > 0; --i) { + if (updates[i] <= t) { + // apply the transform + return transformers[i].transformed(rawG); + } + } + + return transformers[0].transformed(rawG); + + } + } else { + if (t < extremeT) { + // we are at the backward end of the history + + // check if a new rough root has been crossed + final Transformer previous = transformers[0]; + final Transformer next = filter.selectTransformer(previous, rawG, forward); + if (next != previous) { + // there is a root somewhere between extremeT and t. + // the new transformer is valid for t (this is how we have just computed + // it above), but it is in fact valid on both sides of the root, so + // it was already valid before t and even up to previous time. We store + // the switch at extremeT for safety, to ensure the previous transformer + // is not applied too close of the root + System.arraycopy(updates, 0, updates, 1, updates.length - 1); + System.arraycopy(transformers, 0, transformers, 1, transformers.length - 1); + updates[0] = extremeT; + transformers[0] = next; + } + + extremeT = t; + + // apply the transform + return next.transformed(rawG); + + } else { + // we are in the middle of the history + + // select the transformer + for (int i = 0; i < updates.length - 1; ++i) { + if (t <= updates[i]) { + // apply the transform + return transformers[i].transformed(rawG); + } + } + + return transformers[updates.length - 1].transformed(rawG); + + } + } + + } + + /** {@inheritDoc} */ + public Action eventOccurred(double t, double[] y, boolean increasing) { + // delegate to raw handler, fixing increasing status on the fly + return rawHandler.eventOccurred(t, y, filter.getTriggeredIncreasing()); + } + + /** {@inheritDoc} */ + public void resetState(double t, double[] y) { + // delegate to raw handler + rawHandler.resetState(t, y); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventHandler.java new file mode 100644 index 000000000..6c1601a3b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventHandler.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 com.fr.third.org.apache.commons.math3.ode.events; + + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderDifferentialEquations; +import com.fr.third.org.apache.commons.math3.ode.sampling.FixedStepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepNormalizer; + +/** This interface represents a handler for discrete events triggered + * during ODE integration. + * + *

    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. + *

    + * + *

    These events are defined as occurring when a g + * switching function sign changes.

    + * + *

    Since events are only problem-dependent and are triggered by the + * independent time 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).

    + * + * @since 1.2 + */ + +public interface EventHandler { + + /** Enumerate for actions to be performed when an event occurs. */ + enum Action { + + /** Stop indicator. + *

    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.

    + */ + STOP, + + /** Reset state indicator. + *

    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).

    + */ + RESET_STATE, + + /** Reset derivatives indicator. + *

    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 + * FirstOrderDifferentialEquations#computeDerivatives} + * method).

    + */ + RESET_DERIVATIVES, + + /** Continue indicator. + *

    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.

    + */ + CONTINUE; + + } + + /** Initialize event handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the event handler to initialize some internal data + * if needed. + *

    + * @param t0 start value of the independent time variable + * @param y0 array containing the start value of the state vector + * @param t target time for the integration + */ + void init(double t0, double[] y0, double t); + + /** Compute the value of the switching function. + + *

    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.

    + *

    Also note that the integrator expect that once an event has occurred, + * the sign of the switching function at the start of the next step (i.e. + * just after the event) is the opposite of the sign just before the event. + * This consistency between the steps must be preserved, + * otherwise {@link NoBracketingException + * exceptions} related to root not being bracketed will occur.

    + *

    This need for consistency is sometimes tricky to achieve. A typical + * example is using an event to model a ball bouncing on the floor. The first + * idea to represent this would be to have {@code g(t) = h(t)} where h is the + * height above the floor at time {@code t}. When {@code g(t)} reaches 0, the + * ball is on the floor, so it should bounce and the typical way to do this is + * to reverse its vertical velocity. However, this would mean that before the + * event {@code g(t)} was decreasing from positive values to 0, and after the + * event {@code g(t)} would be increasing from 0 to positive values again. + * Consistency is broken here! The solution here is to have {@code g(t) = sign + * * h(t)}, where sign is a variable with initial value set to {@code +1}. Each + * time {@link #eventOccurred(double, double[], boolean) eventOccurred} is called, + * {@code sign} is reset to {@code -sign}. This allows the {@code g(t)} + * function to remain continuous (and even smooth) even across events, despite + * {@code h(t)} is not. Basically, the event is used to fold {@code h(t)} + * at bounce points, and {@code sign} is used to unfold it back, so the + * solvers sees a {@code g(t)} function which behaves smoothly even across events.

    + + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @return value of the g switching function + */ + double g(double t, double[] y); + + /** Handle an event and choose what to do next. + + *

    This method is called when the integrator has accepted a step + * ending exactly on a sign change of the function, just before + * 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 + * 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.

    + + *
      + *
    • if {@link Action#STOP} is returned, the step handler will be called + * with the isLast flag of the {@link + * StepHandler#handleStep handleStep} + * method set to true and the integration will be stopped,
    • + *
    • if {@link Action#RESET_STATE} is returned, the {@link #resetState + * resetState} method will be called once the step handler has + * finished its task, and the integrator will also recompute the + * derivatives,
    • + *
    • if {@link Action#RESET_DERIVATIVES} is returned, the integrator + * will recompute the derivatives, + *
    • if {@link Action#CONTINUE} is returned, no specific action will + * be taken (apart from having called this method) and integration + * will continue.
    • + *
    + + *

    The scheduling between this method and the {@link + * StepHandler StepHandler} method {@link + * StepHandler#handleStep( + * StepInterpolator, boolean) + * handleStep(interpolator, isLast)} is to call this method first and + * handleStep afterwards. This scheduling allows the integrator to + * pass true as the isLast parameter to the step + * handler to make it aware the step will be the last one if this method + * returns {@link Action#STOP}. As the interpolator may be used to navigate back + * throughout the last step (as {@link + * 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 FixedStepHandler FixedStepHandler} + * interface, a forward integration may call its + * eventOccurred method with t = 10 first and call its + * handleStep method with t = 9 afterwards. Such out of order + * calls are limited to the size of the integration step for {@link + * StepHandler variable step handlers} and + * to the size of the fixed step for {@link + * FixedStepHandler fixed step handlers}.

    + + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * @param increasing if true, the value of the switching function increases + * when times increases around event (note that increase is measured with respect + * to physical time, not with respect to integration which may go backward in time) + * @return indication of what the integrator should do next, this + * value must be one of {@link Action#STOP}, {@link Action#RESET_STATE}, + * {@link Action#RESET_DERIVATIVES} or {@link Action#CONTINUE} + */ + Action eventOccurred(double t, double[] y, boolean increasing); + + /** Reset the state prior to continue the integration. + + *

    This method is called after the step handler has returned and + * before the next step is started, but only when {@link + * #eventOccurred} has itself returned the {@link Action#RESET_STATE} + * indicator. It allows the user to reset the state vector for the + * next step, without perturbing the step handler of the finishing + * step. If the {@link #eventOccurred} never returns the {@link + * Action#RESET_STATE} indicator, this function will never be called, and it is + * safe to leave its body empty.

    + + * @param t current value of the independent time variable + * @param y array containing the current value of the state vector + * the new state should be put in the same array + */ + void resetState(double t, double[] y); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventState.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventState.java new file mode 100644 index 000000000..3b398b1ea --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/EventState.java @@ -0,0 +1,431 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.AllowedSolution; +import com.fr.third.org.apache.commons.math3.analysis.solvers.BracketedUnivariateSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.PegasusSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + +/** This class handles the state for one {@link EventHandler + * event handler} during integration steps. + * + *

    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.

    + * + * @since 1.2 + */ +public class EventState { + + /** Event handler. */ + private final EventHandler handler; + + /** Maximal time interval between events handler checks. */ + private final double maxCheckInterval; + + /** Convergence threshold for event localization. */ + private final double convergence; + + /** Upper limit in the iteration count for event localization. */ + private final int maxIterationCount; + + /** Equation being integrated. */ + private ExpandableStatefulODE expandable; + + /** Time at the beginning of the step. */ + private double t0; + + /** Value of the events handler at the beginning of the step. */ + private double g0; + + /** Simulated sign of g0 (we cheat when crossing events). */ + private boolean g0Positive; + + /** Indicator of event expected during the step. */ + private boolean pendingEvent; + + /** Occurrence time of the pending event. */ + private double pendingEventTime; + + /** Occurrence time of the previous event. */ + private double previousEventTime; + + /** Integration direction. */ + private boolean forward; + + /** Variation direction around pending event. + * (this is considered with respect to the integration direction) + */ + private boolean increasing; + + /** Next action indicator. */ + private EventHandler.Action nextAction; + + /** Root-finding algorithm to use to detect state events. */ + private final UnivariateSolver solver; + + /** Simple constructor. + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search + * @param solver Root-finding algorithm to use to detect state events + */ + public EventState(final EventHandler handler, final double maxCheckInterval, + final double convergence, final int maxIterationCount, + final UnivariateSolver solver) { + this.handler = handler; + this.maxCheckInterval = maxCheckInterval; + this.convergence = FastMath.abs(convergence); + this.maxIterationCount = maxIterationCount; + this.solver = solver; + + // some dummy values ... + expandable = null; + t0 = Double.NaN; + g0 = Double.NaN; + g0Positive = true; + pendingEvent = false; + pendingEventTime = Double.NaN; + previousEventTime = Double.NaN; + increasing = true; + nextAction = EventHandler.Action.CONTINUE; + + } + + /** Get the underlying event handler. + * @return underlying event handler + */ + public EventHandler getEventHandler() { + return handler; + } + + /** Set the equation. + * @param expandable equation being integrated + */ + public void setExpandable(final ExpandableStatefulODE expandable) { + this.expandable = expandable; + } + + /** Get the maximal time interval between events handler checks. + * @return maximal time interval between events handler checks + */ + public double getMaxCheckInterval() { + return maxCheckInterval; + } + + /** Get the convergence threshold for event localization. + * @return convergence threshold for event localization + */ + public double getConvergence() { + return convergence; + } + + /** Get the upper limit in the iteration count for event localization. + * @return upper limit in the iteration count for event localization + */ + public int getMaxIterationCount() { + return maxIterationCount; + } + + /** Reinitialize the beginning of the step. + * @param interpolator valid for the current step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + public void reinitializeBegin(final StepInterpolator interpolator) + throws MaxCountExceededException { + + t0 = interpolator.getPreviousTime(); + interpolator.setInterpolatedTime(t0); + g0 = handler.g(t0, getCompleteState(interpolator)); + if (g0 == 0) { + // excerpt from MATH-421 issue: + // If an ODE solver is setup with an EventHandler that return STOP + // when the even is triggered, the integrator stops (which is exactly + // the expected behavior). If however the user wants to restart the + // solver from the final state reached at the event with the same + // configuration (expecting the event to be triggered again at a + // later time), then the integrator may fail to start. It can get stuck + // at the previous event. The use case for the bug MATH-421 is fairly + // general, so events occurring exactly at start in the first step should + // be ignored. + + // extremely rare case: there is a zero EXACTLY at interval start + // we will use the sign slightly after step beginning to force ignoring this zero + final double epsilon = FastMath.max(solver.getAbsoluteAccuracy(), + FastMath.abs(solver.getRelativeAccuracy() * t0)); + final double tStart = t0 + 0.5 * epsilon; + interpolator.setInterpolatedTime(tStart); + g0 = handler.g(tStart, getCompleteState(interpolator)); + } + g0Positive = g0 >= 0; + + } + + /** Get the complete state (primary and secondary). + * @param interpolator interpolator to use + * @return complete state + */ + private double[] getCompleteState(final StepInterpolator interpolator) { + + final double[] complete = new double[expandable.getTotalDimension()]; + + expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(), + complete); + int index = 0; + for (EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++), + complete); + } + + return complete; + + } + + /** Evaluate the impact of the proposed step on the event handler. + * @param interpolator step interpolator for the proposed step + * @return true if the event handler triggers an event before + * the end of the proposed step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + * @exception NoBracketingException if the event cannot be bracketed + */ + public boolean evaluateStep(final StepInterpolator interpolator) + throws MaxCountExceededException, NoBracketingException { + + try { + forward = interpolator.isForward(); + final double t1 = interpolator.getCurrentTime(); + final double dt = t1 - t0; + if (FastMath.abs(dt) < convergence) { + // we cannot do anything on such a small step, don't trigger any events + return false; + } + final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval)); + final double h = dt / n; + + final UnivariateFunction f = new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(final double t) throws LocalMaxCountExceededException { + try { + interpolator.setInterpolatedTime(t); + return handler.g(t, getCompleteState(interpolator)); + } catch (MaxCountExceededException mcee) { + throw new LocalMaxCountExceededException(mcee); + } + } + }; + + double ta = t0; + double ga = g0; + for (int i = 0; i < n; ++i) { + + // evaluate handler value at the end of the substep + final double tb = (i == n - 1) ? t1 : t0 + (i + 1) * h; + interpolator.setInterpolatedTime(tb); + final double gb = handler.g(tb, getCompleteState(interpolator)); + + // check events occurrence + if (g0Positive ^ (gb >= 0)) { + // there is a sign change: an event is expected during this step + + // variation direction, with respect to the integration direction + increasing = gb >= ga; + + // find the event time making sure we select a solution just at or past the exact root + final double root; + if (solver instanceof BracketedUnivariateSolver) { + @SuppressWarnings("unchecked") + BracketedUnivariateSolver bracketing = + (BracketedUnivariateSolver) solver; + root = forward ? + bracketing.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) : + bracketing.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE); + } else { + final double baseRoot = forward ? + solver.solve(maxIterationCount, f, ta, tb) : + solver.solve(maxIterationCount, f, tb, ta); + final int remainingEval = maxIterationCount - solver.getEvaluations(); + BracketedUnivariateSolver bracketing = + new PegasusSolver(solver.getRelativeAccuracy(), solver.getAbsoluteAccuracy()); + root = forward ? + UnivariateSolverUtils.forceSide(remainingEval, f, bracketing, + baseRoot, ta, tb, AllowedSolution.RIGHT_SIDE) : + UnivariateSolverUtils.forceSide(remainingEval, f, bracketing, + baseRoot, tb, ta, AllowedSolution.LEFT_SIDE); + } + + if ((!Double.isNaN(previousEventTime)) && + (FastMath.abs(root - ta) <= convergence) && + (FastMath.abs(root - previousEventTime) <= convergence)) { + // we have either found nothing or found (again ?) a past event, + // retry the substep excluding this value, and taking care to have the + // required sign in case the g function is noisy around its zero and + // crosses the axis several times + do { + ta = forward ? ta + convergence : ta - convergence; + ga = f.value(ta); + } while ((g0Positive ^ (ga >= 0)) && (forward ^ (ta >= tb))); + + if (forward ^ (ta >= tb)) { + // we were able to skip this spurious root + --i; + } else { + // we can't avoid this root before the end of the step, + // we have to handle it despite it is close to the former one + // maybe we have two very close roots + pendingEventTime = root; + pendingEvent = true; + return true; + } + } else if (Double.isNaN(previousEventTime) || + (FastMath.abs(previousEventTime - root) > convergence)) { + pendingEventTime = root; + pendingEvent = true; + return true; + } else { + // no sign change: there is no event for now + ta = tb; + ga = gb; + } + + } else { + // no sign change: there is no event for now + ta = tb; + ga = gb; + } + + } + + // no event during the whole step + pendingEvent = false; + pendingEventTime = Double.NaN; + return false; + + } catch (LocalMaxCountExceededException lmcee) { + throw lmcee.getException(); + } + + } + + /** Get the occurrence time of the event triggered in the current step. + * @return occurrence time of the event triggered in the current + * step or infinity if no events are triggered + */ + public double getEventTime() { + return pendingEvent ? + pendingEventTime : + (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY); + } + + /** Acknowledge the fact the step has been accepted by the integrator. + * @param t value of the independent time variable at the + * end of the step + * @param y array containing the current value of the state vector + * at the end of the step + */ + public void stepAccepted(final double t, final double[] y) { + + t0 = t; + g0 = handler.g(t, y); + + if (pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence)) { + // force the sign to its value "just after the event" + previousEventTime = t; + g0Positive = increasing; + nextAction = handler.eventOccurred(t, y, !(increasing ^ forward)); + } else { + g0Positive = g0 >= 0; + nextAction = EventHandler.Action.CONTINUE; + } + } + + /** Check if the integration should be stopped at the end of the + * current step. + * @return true if the integration should be stopped + */ + public boolean stop() { + return nextAction == EventHandler.Action.STOP; + } + + /** Let the event handler reset the state if it wants. + * @param t value of the independent time variable at the + * beginning of the next step + * @param y array were to put the desired state vector at the beginning + * of the next step + * @return true if the integrator should reset the derivatives too + */ + public boolean reset(final double t, final double[] y) { + + if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) { + return false; + } + + if (nextAction == EventHandler.Action.RESET_STATE) { + handler.resetState(t, y); + } + pendingEvent = false; + pendingEventTime = Double.NaN; + + return (nextAction == EventHandler.Action.RESET_STATE) || + (nextAction == EventHandler.Action.RESET_DERIVATIVES); + + } + + /** Local wrapper to propagate exceptions. */ + private static class LocalMaxCountExceededException extends RuntimeException { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120901L; + + /** Wrapped exception. */ + private final MaxCountExceededException wrapped; + + /** Simple constructor. + * @param exception exception to wrap + */ + LocalMaxCountExceededException(final MaxCountExceededException exception) { + wrapped = exception; + } + + /** Get the wrapped exception. + * @return wrapped exception + */ + public MaxCountExceededException getException() { + return wrapped; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventHandler.java new file mode 100644 index 000000000..cc2a68616 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventHandler.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 com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderDifferentialEquations; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepHandler; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** This interface represents a handler for discrete events triggered + * during ODE integration. + * + *

    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. + *

    + * + *

    These events are defined as occurring when a g + * switching function sign changes.

    + * + *

    Since events are only problem-dependent and are triggered by the + * independent time 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).

    + * + * @param the type of the field elements + * @since 3.6 + */ +public interface FieldEventHandler> { + + /** Initialize event handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the event handler to initialize some internal data + * if needed. + *

    + * @param initialState initial time, state vector and derivative + * @param finalTime target time for the integration + */ + void init(FieldODEStateAndDerivative initialState, T finalTime); + + /** Compute the value of the switching function. + + *

    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.

    + *

    Also note that the integrator expect that once an event has occurred, + * the sign of the switching function at the start of the next step (i.e. + * just after the event) is the opposite of the sign just before the event. + * This consistency between the steps must be preserved, + * otherwise {@link NoBracketingException + * exceptions} related to root not being bracketed will occur.

    + *

    This need for consistency is sometimes tricky to achieve. A typical + * example is using an event to model a ball bouncing on the floor. The first + * idea to represent this would be to have {@code g(t) = h(t)} where h is the + * height above the floor at time {@code t}. When {@code g(t)} reaches 0, the + * ball is on the floor, so it should bounce and the typical way to do this is + * to reverse its vertical velocity. However, this would mean that before the + * event {@code g(t)} was decreasing from positive values to 0, and after the + * event {@code g(t)} would be increasing from 0 to positive values again. + * Consistency is broken here! The solution here is to have {@code g(t) = sign + * * h(t)}, where sign is a variable with initial value set to {@code +1}. Each + * time {@link #eventOccurred(FieldODEStateAndDerivative, boolean) eventOccurred} + * method is called, {@code sign} is reset to {@code -sign}. This allows the + * {@code g(t)} function to remain continuous (and even smooth) even across events, + * despite {@code h(t)} is not. Basically, the event is used to fold + * {@code h(t)} at bounce points, and {@code sign} is used to unfold it + * back, so the solvers sees a {@code g(t)} function which behaves smoothly even + * across events.

    + + * @param state current value of the independent time variable, state vector + * and derivative + * @return value of the g switching function + */ + T g(FieldODEStateAndDerivative state); + + /** Handle an event and choose what to do next. + + *

    This method is called when the integrator has accepted a step + * ending exactly on a sign change of the function, just before + * 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 + * 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.

    + + *
      + *
    • if {@link Action#STOP} is returned, the step handler will be called + * with the isLast flag of the {@link + * StepHandler#handleStep handleStep} + * method set to true and the integration will be stopped,
    • + *
    • if {@link Action#RESET_STATE} is returned, the {@link #resetState + * resetState} method will be called once the step handler has + * finished its task, and the integrator will also recompute the + * derivatives,
    • + *
    • if {@link Action#RESET_DERIVATIVES} is returned, the integrator + * will recompute the derivatives, + *
    • if {@link Action#CONTINUE} is returned, no specific action will + * be taken (apart from having called this method) and integration + * will continue.
    • + *
    + + *

    The scheduling between this method and the {@link + * FieldStepHandler FieldStepHandler} method {@link + * FieldStepHandler#handleStep( + * FieldStepInterpolator, boolean) + * handleStep(interpolator, isLast)} is to call this method first and + * handleStep afterwards. This scheduling allows the integrator to + * pass true as the isLast parameter to the step + * handler to make it aware the step will be the last one if this method + * returns {@link Action#STOP}. As the interpolator may be used to navigate back + * throughout the last step, user code called by this method and user + * code called by step handlers may experience apparently out of order values + * of the independent time variable. As an example, if the same user object + * implements both this {@link FieldEventHandler FieldEventHandler} interface and the + * {@link FieldStepHandler FieldStepHandler} + * interface, a forward integration may call its + * {code eventOccurred} method with t = 10 first and call its + * {code handleStep} method with t = 9 afterwards. Such out of order + * calls are limited to the size of the integration step for {@link + * FieldStepHandler variable step handlers}.

    + + * @param state current value of the independent time variable, state vector + * and derivative + * @param increasing if true, the value of the switching function increases + * when times increases around event (note that increase is measured with respect + * to physical time, not with respect to integration which may go backward in time) + * @return indication of what the integrator should do next, this + * value must be one of {@link Action#STOP}, {@link Action#RESET_STATE}, + * {@link Action#RESET_DERIVATIVES} or {@link Action#CONTINUE} + */ + Action eventOccurred(FieldODEStateAndDerivative state, boolean increasing); + + /** Reset the state prior to continue the integration. + + *

    This method is called after the step handler has returned and + * before the next step is started, but only when {@link + * #eventOccurred(FieldODEStateAndDerivative, boolean) eventOccurred} has itself + * returned the {@link Action#RESET_STATE} indicator. It allows the user to reset + * the state vector for the next step, without perturbing the step handler of the + * finishing step. If the {@link #eventOccurred(FieldODEStateAndDerivative, boolean) + * eventOccurred} never returns the {@link Action#RESET_STATE} indicator, this + * function will never be called, and it is safe to leave its body empty.

    + * @param state current value of the independent time variable, state vector + * and derivative + * @return reset state (note that it does not include the derivatives, they will + * be added automatically by the integrator afterwards) + */ + FieldODEState resetState(FieldODEStateAndDerivative state); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventState.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventState.java new file mode 100644 index 000000000..b48f9f7ca --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FieldEventState.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.analysis.RealFieldUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.AllowedSolution; +import com.fr.third.org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.ode.sampling.FieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** This class handles the state for one {@link EventHandler + * event handler} during integration steps. + * + *

    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.

    + * + * @param the type of the field elements + * @since 3.6 + */ +public class FieldEventState> { + + /** Event handler. */ + private final FieldEventHandler handler; + + /** Maximal time interval between events handler checks. */ + private final double maxCheckInterval; + + /** Convergence threshold for event localization. */ + private final T convergence; + + /** Upper limit in the iteration count for event localization. */ + private final int maxIterationCount; + + /** Time at the beginning of the step. */ + private T t0; + + /** Value of the events handler at the beginning of the step. */ + private T g0; + + /** Simulated sign of g0 (we cheat when crossing events). */ + private boolean g0Positive; + + /** Indicator of event expected during the step. */ + private boolean pendingEvent; + + /** Occurrence time of the pending event. */ + private T pendingEventTime; + + /** Occurrence time of the previous event. */ + private T previousEventTime; + + /** Integration direction. */ + private boolean forward; + + /** Variation direction around pending event. + * (this is considered with respect to the integration direction) + */ + private boolean increasing; + + /** Next action indicator. */ + private Action nextAction; + + /** Root-finding algorithm to use to detect state events. */ + private final BracketedRealFieldUnivariateSolver solver; + + /** Simple constructor. + * @param handler event handler + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + * @param maxIterationCount upper limit of the iteration count in + * the event time search + * @param solver Root-finding algorithm to use to detect state events + */ + public FieldEventState(final FieldEventHandler handler, final double maxCheckInterval, + final T convergence, final int maxIterationCount, + final BracketedRealFieldUnivariateSolver solver) { + this.handler = handler; + this.maxCheckInterval = maxCheckInterval; + this.convergence = convergence.abs(); + this.maxIterationCount = maxIterationCount; + this.solver = solver; + + // some dummy values ... + t0 = null; + g0 = null; + g0Positive = true; + pendingEvent = false; + pendingEventTime = null; + previousEventTime = null; + increasing = true; + nextAction = Action.CONTINUE; + + } + + /** Get the underlying event handler. + * @return underlying event handler + */ + public FieldEventHandler getEventHandler() { + return handler; + } + + /** Get the maximal time interval between events handler checks. + * @return maximal time interval between events handler checks + */ + public double getMaxCheckInterval() { + return maxCheckInterval; + } + + /** Get the convergence threshold for event localization. + * @return convergence threshold for event localization + */ + public T getConvergence() { + return convergence; + } + + /** Get the upper limit in the iteration count for event localization. + * @return upper limit in the iteration count for event localization + */ + public int getMaxIterationCount() { + return maxIterationCount; + } + + /** Reinitialize the beginning of the step. + * @param interpolator valid for the current step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + public void reinitializeBegin(final FieldStepInterpolator interpolator) + throws MaxCountExceededException { + + final FieldODEStateAndDerivative s0 = interpolator.getPreviousState(); + t0 = s0.getTime(); + g0 = handler.g(s0); + if (g0.getReal() == 0) { + // excerpt from MATH-421 issue: + // If an ODE solver is setup with an EventHandler that return STOP + // when the even is triggered, the integrator stops (which is exactly + // the expected behavior). If however the user wants to restart the + // solver from the final state reached at the event with the same + // configuration (expecting the event to be triggered again at a + // later time), then the integrator may fail to start. It can get stuck + // at the previous event. The use case for the bug MATH-421 is fairly + // general, so events occurring exactly at start in the first step should + // be ignored. + + // extremely rare case: there is a zero EXACTLY at interval start + // we will use the sign slightly after step beginning to force ignoring this zero + final double epsilon = FastMath.max(solver.getAbsoluteAccuracy().getReal(), + FastMath.abs(solver.getRelativeAccuracy().multiply(t0).getReal())); + final T tStart = t0.add(0.5 * epsilon); + g0 = handler.g(interpolator.getInterpolatedState(tStart)); + } + g0Positive = g0.getReal() >= 0; + + } + + /** Evaluate the impact of the proposed step on the event handler. + * @param interpolator step interpolator for the proposed step + * @return true if the event handler triggers an event before + * the end of the proposed step + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + * @exception NoBracketingException if the event cannot be bracketed + */ + public boolean evaluateStep(final FieldStepInterpolator interpolator) + throws MaxCountExceededException, NoBracketingException { + + forward = interpolator.isForward(); + final FieldODEStateAndDerivative s1 = interpolator.getCurrentState(); + final T t1 = s1.getTime(); + final T dt = t1.subtract(t0); + if (dt.abs().subtract(convergence).getReal() < 0) { + // we cannot do anything on such a small step, don't trigger any events + return false; + } + final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt.getReal()) / maxCheckInterval)); + final T h = dt.divide(n); + + final RealFieldUnivariateFunction f = new RealFieldUnivariateFunction() { + /** {@inheritDoc} */ + public T value(final T t) { + return handler.g(interpolator.getInterpolatedState(t)); + } + }; + + T ta = t0; + T ga = g0; + for (int i = 0; i < n; ++i) { + + // evaluate handler value at the end of the substep + final T tb = (i == n - 1) ? t1 : t0.add(h.multiply(i + 1)); + final T gb = handler.g(interpolator.getInterpolatedState(tb)); + + // check events occurrence + if (g0Positive ^ (gb.getReal() >= 0)) { + // there is a sign change: an event is expected during this step + + // variation direction, with respect to the integration direction + increasing = gb.subtract(ga).getReal() >= 0; + + // find the event time making sure we select a solution just at or past the exact root + final T root = forward ? + solver.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) : + solver.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE); + + if (previousEventTime != null && + root.subtract(ta).abs().subtract(convergence).getReal() <= 0 && + root.subtract(previousEventTime).abs().subtract(convergence).getReal() <= 0) { + // we have either found nothing or found (again ?) a past event, + // retry the substep excluding this value, and taking care to have the + // required sign in case the g function is noisy around its zero and + // crosses the axis several times + do { + ta = forward ? ta.add(convergence) : ta.subtract(convergence); + ga = f.value(ta); + } while ((g0Positive ^ (ga.getReal() >= 0)) && (forward ^ (ta.subtract(tb).getReal() >= 0))); + + if (forward ^ (ta.subtract(tb).getReal() >= 0)) { + // we were able to skip this spurious root + --i; + } else { + // we can't avoid this root before the end of the step, + // we have to handle it despite it is close to the former one + // maybe we have two very close roots + pendingEventTime = root; + pendingEvent = true; + return true; + } + } else if (previousEventTime == null || + previousEventTime.subtract(root).abs().subtract(convergence).getReal() > 0) { + pendingEventTime = root; + pendingEvent = true; + return true; + } else { + // no sign change: there is no event for now + ta = tb; + ga = gb; + } + + } else { + // no sign change: there is no event for now + ta = tb; + ga = gb; + } + + } + + // no event during the whole step + pendingEvent = false; + pendingEventTime = null; + return false; + + } + + /** Get the occurrence time of the event triggered in the current step. + * @return occurrence time of the event triggered in the current + * step or infinity if no events are triggered + */ + public T getEventTime() { + return pendingEvent ? + pendingEventTime : + t0.getField().getZero().add(forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY); + } + + /** Acknowledge the fact the step has been accepted by the integrator. + * @param state state at the end of the step + */ + public void stepAccepted(final FieldODEStateAndDerivative state) { + + t0 = state.getTime(); + g0 = handler.g(state); + + if (pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0) { + // force the sign to its value "just after the event" + previousEventTime = state.getTime(); + g0Positive = increasing; + nextAction = handler.eventOccurred(state, !(increasing ^ forward)); + } else { + g0Positive = g0.getReal() >= 0; + nextAction = Action.CONTINUE; + } + } + + /** Check if the integration should be stopped at the end of the + * current step. + * @return true if the integration should be stopped + */ + public boolean stop() { + return nextAction == Action.STOP; + } + + /** Let the event handler reset the state if it wants. + * @param state state at the beginning of the next step + * @return reset state (may by the same as initial state if only + * derivatives should be reset), or null if nothing is reset + */ + public FieldODEState reset(final FieldODEStateAndDerivative state) { + + if (!(pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0)) { + return null; + } + + final FieldODEState newState; + if (nextAction == Action.RESET_STATE) { + newState = handler.resetState(state); + } else if (nextAction == Action.RESET_DERIVATIVES) { + newState = state; + } else { + newState = null; + } + pendingEvent = false; + pendingEventTime = null; + + return newState; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FilterType.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FilterType.java new file mode 100644 index 000000000..7b694fc54 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/FilterType.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 com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** Enumerate for {@link EventFilter filtering events}. + * + * @since 3.2 + */ + +public enum FilterType { + + /** Constant for triggering only decreasing events. + *

    When this filter is used, the wrapped {@link EventHandler + * event handler} {@link EventHandler#eventOccurred(double, double[], + * boolean) eventOccurred} method will be called only with + * its {@code increasing} argument set to false.

    + */ + TRIGGER_ONLY_DECREASING_EVENTS { + + /** {@inheritDoc} */ + @Override + protected boolean getTriggeredIncreasing() { + return false; + } + + /** {@inheritDoc} + *

    + * states scheduling for computing h(t,y) as an altered version of g(t, y) + *

      + *
    • 0 are triggered events for which a zero is produced (here decreasing events)
    • + *
    • X are ignored events for which zero is masked (here increasing events)
    • + *
    + *

    + *
    +         *  g(t)
    +         *             ___                     ___                     ___
    +         *            /   \                   /   \                   /   \
    +         *           /     \                 /     \                 /     \
    +         *          /  g>0  \               /  g>0  \               /  g>0  \
    +         *         /         \             /         \             /         \
    +         *  ----- X --------- 0 --------- X --------- 0 --------- X --------- 0 ---
    +         *       /             \         /             \         /             \
    +         *      /               \ g<0   /               \  g<0  /               \ g<0
    +         *     /                 \     /                 \     /                 \     /
    +         * ___/                   \___/                   \___/                   \___/
    +         * 
    + *
    +         *  h(t,y)) as an alteration of g(t,y)
    +         *             ___                                 ___         ___
    +         *    \       /   \                               /   \       /   \
    +         *     \     /     \ h=+g                        /     \     /     \
    +         *      \   /       \      h=min(-s,-g,+g)      /       \   /       \
    +         *       \_/         \                         /         \_/         \
    +         *  ------ ---------- 0 ----------_---------- 0 --------------------- 0 ---
    +         *                     \         / \         /                         \
    +         *   h=max(+s,-g,+g)    \       /   \       /       h=max(+s,-g,+g)     \
    +         *                       \     /     \     / h=-g                        \     /
    +         *                        \___/       \___/                               \___/
    +         * 
    + *

    + * As shown by the figure above, several expressions are used to compute h, + * depending on the current state: + *

      + *
    • h = max(+s,-g,+g)
    • + *
    • h = +g
    • + *
    • h = min(-s,-g,+g)
    • + *
    • h = -g
    • + *
    + * where s is a tiny positive value: {@link Precision#SAFE_MIN}. + *

    + */ + @Override + protected Transformer selectTransformer(final Transformer previous, + final double g, final boolean forward) { + if (forward) { + switch (previous) { + case UNINITIALIZED : + // we are initializing the first point + if (g > 0) { + // initialize as if previous root (i.e. backward one) was an ignored increasing event + return Transformer.MAX; + } else if (g < 0) { + // initialize as if previous root (i.e. backward one) was a triggered decreasing event + return Transformer.PLUS; + } else { + // we are exactly at a root, we don't know if it is an increasing + // or a decreasing event, we remain in uninitialized state + return Transformer.UNINITIALIZED; + } + case PLUS : + if (g >= 0) { + // we have crossed the zero line on an ignored increasing event, + // we must change the transformer + return Transformer.MIN; + } else { + // we are still in the same status + return previous; + } + case MINUS : + if (g >= 0) { + // we have crossed the zero line on an ignored increasing event, + // we must change the transformer + return Transformer.MAX; + } else { + // we are still in the same status + return previous; + } + case MIN : + if (g <= 0) { + // we have crossed the zero line on a triggered decreasing event, + // we must change the transformer + return Transformer.MINUS; + } else { + // we are still in the same status + return previous; + } + case MAX : + if (g <= 0) { + // we have crossed the zero line on a triggered decreasing event, + // we must change the transformer + return Transformer.PLUS; + } else { + // we are still in the same status + return previous; + } + default : + // this should never happen + throw new MathInternalError(); + } + } else { + switch (previous) { + case UNINITIALIZED : + // we are initializing the first point + if (g > 0) { + // initialize as if previous root (i.e. forward one) was a triggered decreasing event + return Transformer.MINUS; + } else if (g < 0) { + // initialize as if previous root (i.e. forward one) was an ignored increasing event + return Transformer.MIN; + } else { + // we are exactly at a root, we don't know if it is an increasing + // or a decreasing event, we remain in uninitialized state + return Transformer.UNINITIALIZED; + } + case PLUS : + if (g <= 0) { + // we have crossed the zero line on an ignored increasing event, + // we must change the transformer + return Transformer.MAX; + } else { + // we are still in the same status + return previous; + } + case MINUS : + if (g <= 0) { + // we have crossed the zero line on an ignored increasing event, + // we must change the transformer + return Transformer.MIN; + } else { + // we are still in the same status + return previous; + } + case MIN : + if (g >= 0) { + // we have crossed the zero line on a triggered decreasing event, + // we must change the transformer + return Transformer.PLUS; + } else { + // we are still in the same status + return previous; + } + case MAX : + if (g >= 0) { + // we have crossed the zero line on a triggered decreasing event, + // we must change the transformer + return Transformer.MINUS; + } else { + // we are still in the same status + return previous; + } + default : + // this should never happen + throw new MathInternalError(); + } + } + } + + }, + + /** Constant for triggering only increasing events. + *

    When this filter is used, the wrapped {@link EventHandler + * event handler} {@link EventHandler#eventOccurred(double, double[], + * boolean) eventOccurred} method will be called only with + * its {@code increasing} argument set to true.

    + */ + TRIGGER_ONLY_INCREASING_EVENTS { + + /** {@inheritDoc} */ + @Override + protected boolean getTriggeredIncreasing() { + return true; + } + + /** {@inheritDoc} + *

    + * states scheduling for computing h(t,y) as an altered version of g(t, y) + *

      + *
    • 0 are triggered events for which a zero is produced (here increasing events)
    • + *
    • X are ignored events for which zero is masked (here decreasing events)
    • + *
    + *

    + *
    +         *  g(t)
    +         *             ___                     ___                     ___
    +         *            /   \                   /   \                   /   \
    +         *           /     \                 /     \                 /     \
    +         *          /  g>0  \               /  g>0  \               /  g>0  \
    +         *         /         \             /         \             /         \
    +         *  ----- 0 --------- X --------- 0 --------- X --------- 0 --------- X ---
    +         *       /             \         /             \         /             \
    +         *      /               \ g<0   /               \  g<0  /               \ g<0
    +         *     /                 \     /                 \     /                 \     /
    +         * ___/                   \___/                   \___/                   \___/
    +         * 
    + *
    +         *  h(t,y)) as an alteration of g(t,y)
    +         *                                     ___         ___
    +         *    \                               /   \       /   \
    +         *     \ h=-g                        /     \     /     \ h=-g
    +         *      \      h=min(-s,-g,+g)      /       \   /       \      h=min(-s,-g,+g)
    +         *       \                         /         \_/         \
    +         *  ------0 ----------_---------- 0 --------------------- 0 --------- _ ---
    +         *         \         / \         /                         \         / \
    +         *          \       /   \       /       h=max(+s,-g,+g)     \       /   \
    +         *           \     /     \     / h=+g                        \     /     \     /
    +         *            \___/       \___/                               \___/       \___/
    +         * 
    + *

    + * As shown by the figure above, several expressions are used to compute h, + * depending on the current state: + *

      + *
    • h = max(+s,-g,+g)
    • + *
    • h = +g
    • + *
    • h = min(-s,-g,+g)
    • + *
    • h = -g
    • + *
    + * where s is a tiny positive value: {@link Precision#SAFE_MIN}. + *

    + */ + @Override + protected Transformer selectTransformer(final Transformer previous, + final double g, final boolean forward) { + if (forward) { + switch (previous) { + case UNINITIALIZED : + // we are initializing the first point + if (g > 0) { + // initialize as if previous root (i.e. backward one) was a triggered increasing event + return Transformer.PLUS; + } else if (g < 0) { + // initialize as if previous root (i.e. backward one) was an ignored decreasing event + return Transformer.MIN; + } else { + // we are exactly at a root, we don't know if it is an increasing + // or a decreasing event, we remain in uninitialized state + return Transformer.UNINITIALIZED; + } + case PLUS : + if (g <= 0) { + // we have crossed the zero line on an ignored decreasing event, + // we must change the transformer + return Transformer.MAX; + } else { + // we are still in the same status + return previous; + } + case MINUS : + if (g <= 0) { + // we have crossed the zero line on an ignored decreasing event, + // we must change the transformer + return Transformer.MIN; + } else { + // we are still in the same status + return previous; + } + case MIN : + if (g >= 0) { + // we have crossed the zero line on a triggered increasing event, + // we must change the transformer + return Transformer.PLUS; + } else { + // we are still in the same status + return previous; + } + case MAX : + if (g >= 0) { + // we have crossed the zero line on a triggered increasing event, + // we must change the transformer + return Transformer.MINUS; + } else { + // we are still in the same status + return previous; + } + default : + // this should never happen + throw new MathInternalError(); + } + } else { + switch (previous) { + case UNINITIALIZED : + // we are initializing the first point + if (g > 0) { + // initialize as if previous root (i.e. forward one) was an ignored decreasing event + return Transformer.MAX; + } else if (g < 0) { + // initialize as if previous root (i.e. forward one) was a triggered increasing event + return Transformer.MINUS; + } else { + // we are exactly at a root, we don't know if it is an increasing + // or a decreasing event, we remain in uninitialized state + return Transformer.UNINITIALIZED; + } + case PLUS : + if (g >= 0) { + // we have crossed the zero line on an ignored decreasing event, + // we must change the transformer + return Transformer.MIN; + } else { + // we are still in the same status + return previous; + } + case MINUS : + if (g >= 0) { + // we have crossed the zero line on an ignored decreasing event, + // we must change the transformer + return Transformer.MAX; + } else { + // we are still in the same status + return previous; + } + case MIN : + if (g <= 0) { + // we have crossed the zero line on a triggered increasing event, + // we must change the transformer + return Transformer.MINUS; + } else { + // we are still in the same status + return previous; + } + case MAX : + if (g <= 0) { + // we have crossed the zero line on a triggered increasing event, + // we must change the transformer + return Transformer.PLUS; + } else { + // we are still in the same status + return previous; + } + default : + // this should never happen + throw new MathInternalError(); + } + } + } + + }; + + /** Get the increasing status of triggered events. + * @return true if triggered events are increasing events + */ + protected abstract boolean getTriggeredIncreasing(); + + /** Get next function transformer in the specified direction. + * @param previous transformer active on the previous point with respect + * to integration direction (may be null if no previous point is known) + * @param g current value of the g function + * @param forward true if integration goes forward + * @return next transformer transformer + */ + protected abstract Transformer selectTransformer(Transformer previous, + double g, boolean forward); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Transformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Transformer.java new file mode 100644 index 000000000..487cc69a1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/Transformer.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.events; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + + +/** Transformer for {@link EventHandler#g(double, double[]) g functions}. + * @see EventFilter + * @see FilterType + * @since 3.2 + */ +enum Transformer { + + /** Transformer computing transformed = 0. + *

    + * This transformer is used when we initialize the filter, until we get at + * least one non-zero value to select the proper transformer. + *

    + */ + UNINITIALIZED { + /** {@inheritDoc} */ + @Override + protected double transformed(final double g) { + return 0; + } + }, + + /** Transformer computing transformed = g. + *

    + * When this transformer is applied, the roots of the original function + * are preserved, with the same {@code increasing/decreasing} status. + *

    + */ + PLUS { + /** {@inheritDoc} */ + @Override + protected double transformed(final double g) { + return g; + } + }, + + /** Transformer computing transformed = -g. + *

    + * When this transformer is applied, the roots of the original function + * are preserved, with reversed {@code increasing/decreasing} status. + *

    + */ + MINUS { + /** {@inheritDoc} */ + @Override + protected double transformed(final double g) { + return -g; + } + }, + + /** Transformer computing transformed = min(-{@link Precision#SAFE_MIN}, -g, +g). + *

    + * When this transformer is applied, the transformed function is + * guaranteed to be always strictly negative (i.e. there are no roots). + *

    + */ + MIN { + /** {@inheritDoc} */ + @Override + protected double transformed(final double g) { + return FastMath.min(-Precision.SAFE_MIN, FastMath.min(-g, +g)); + } + }, + + /** Transformer computing transformed = max(+{@link Precision#SAFE_MIN}, -g, +g). + *

    + * When this transformer is applied, the transformed function is + * guaranteed to be always strictly positive (i.e. there are no roots). + *

    + */ + MAX { + /** {@inheritDoc} */ + @Override + protected double transformed(final double g) { + return FastMath.max(+Precision.SAFE_MIN, FastMath.max(-g, +g)); + } + }; + + /** Transform value of function g. + * @param g raw value of function g + * @return transformed value of function g + */ + protected abstract double transformed(double g); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/package-info.java new file mode 100644 index 000000000..ca1ef24f4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/events/package-info.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides classes to handle discrete events occurring during + * Ordinary Differential Equations integration. + *

    + * + *

    + * Discrete events detection is based on switching functions. The user provides + * a simple {@link com.fr.third.org.apache.commons.math3.ode.events.EventHandler#g g(t, y)} + * function depending on the current time and state. The integrator will monitor + * the value of the function throughout integration range and will trigger the + * event when its sign changes. The magnitude of the value is almost irrelevant, + * it should however be continuous (but not necessarily smooth) for the sake of + * root finding. The steps are shortened as needed to ensure the events occur + * at step boundaries (even if the integrator is a fixed-step integrator). + *

    + * + *

    + * When an event is triggered, several different options are available: + *

    + *
      + *
    • integration can be stopped (this is called a G-stop facility),
    • + *
    • the state vector or the derivatives can be changed,
    • + *
    • or integration can simply go on.
    • + *
    + * + *

    + * 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: + *

    + *  public double g(double t, double[] y) {
    + *    return y[0] - targetConcentration;
    + *  }
    + *
    + *  public int eventOccurred(double t, double[] y) {
    + *    return STOP;
    + *  }
    + * 
    + *

    + * + *

    + * 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: + *

    + *  public double g(double t, double[] y) {
    + *    return (t - tManeuverStart) ∗ (t - tManeuverStop);
    + *  }
    + *
    + *  public int eventOccurred(double t, double[] y) {
    + *    return RESET_DERIVATIVES;
    + *  }
    + * 
    + *

    + * + *

    + * The third case is useful mainly for monitoring purposes, a simple example is: + *

    + *  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;
    + *  }
    + * 
    + *

    + * + * + */ +package com.fr.third.org.apache.commons.math3.ode.events; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java new file mode 100644 index 000000000..2fe70e9b1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrix; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldExpandableODE; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements explicit Adams-Bashforth integrators for Ordinary + * Differential Equations. + * + *

    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:

    + *
      + *
    • k = 1: yn+1 = yn + h y'n
    • + *
    • k = 2: yn+1 = yn + h (3y'n-y'n-1)/2
    • + *
    • k = 3: yn+1 = yn + h (23y'n-16y'n-1+5y'n-2)/12
    • + *
    • k = 4: yn+1 = yn + h (55y'n-59y'n-1+37y'n-2-9y'n-3)/24
    • + *
    • ...
    • + *
    + * + *

    A k-steps Adams-Bashforth method is of order k.

    + * + *

    Implementation details

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    The definitions above use the classical representation with several previous first + * derivatives. Lets define + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Bashforth methods can be written: + *
      + *
    • k = 1: yn+1 = yn + s1(n)
    • + *
    • k = 2: yn+1 = yn + 3/2 s1(n) + [ -1/2 ] qn
    • + *
    • k = 3: yn+1 = yn + 23/12 s1(n) + [ -16/12 5/12 ] qn
    • + *
    • k = 4: yn+1 = yn + 55/24 s1(n) + [ -59/24 37/24 -9/24 ] qn
    • + *
    • ...
    • + *

    + * + *

    Instead of using the classical representation with first derivatives only (yn, + * s1(n) and qn), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (yn, s1(n) + * and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    Using the Nordsieck vector has several advantages: + *

      + *
    • it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,
    • + *
    • it simplifies step changes that occur when discrete events that truncate + * the step are triggered,
    • + *
    • it allows to extend the methods in order to support adaptive stepsize.
    • + *

    + * + *

    The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows: + *

      + *
    • yn+1 = yn + s1(n) + uT rn
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 

    + * + *

    The P-1u vector and the P-1 A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.

    + * + * @param the type of the field elements + * @since 3.6 + */ +public class AdamsBashforthFieldIntegrator> extends AdamsFieldIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Bashforth"; + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsBashforthFieldIntegrator(final Field field, final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsBashforthFieldIntegrator(final Field field, final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Estimate error. + *

    + * Error is estimated by interpolating back to previous state using + * the state Taylor expansion and comparing to real previous state. + *

    + * @param previousState state vector at step start + * @param predictedState predicted state vector at step end + * @param predictedScaled predicted value of the scaled derivatives at step end + * @param predictedNordsieck predicted value of the Nordsieck vector at step end + * @return estimated normalized local discretization error + */ + private T errorEstimation(final T[] previousState, + final T[] predictedState, + final T[] predictedScaled, + final FieldMatrix predictedNordsieck) { + + T error = getField().getZero(); + for (int i = 0; i < mainSetDimension; ++i) { + final T yScale = predictedState[i].abs(); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + T variation = getField().getZero(); + int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1; + for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) { + variation = variation.add(predictedNordsieck.getEntry(k, i).multiply(sign)); + sign = -sign; + } + variation = variation.subtract(predictedScaled[i]); + + final T ratio = predictedState[i].subtract(previousState[i]).add(variation).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + + /** {@inheritDoc} */ + @Override + public FieldODEStateAndDerivative integrate(final FieldExpandableODE equations, + final FieldODEState initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations, getStepStart(), finalTime); + + // reuse the step that was chosen by the starter integrator + FieldODEStateAndDerivative stepStart = getStepStart(); + FieldODEStateAndDerivative stepEnd = + AdamsFieldStepInterpolator.taylor(stepStart, + stepStart.getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + // main integration loop + setIsLastStep(false); + do { + + T[] predictedY = null; + final T[] predictedScaled = MathArrays.buildArray(getField(), y.length); + Array2DRowFieldMatrix predictedNordsieck = null; + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0.0) { + + // predict a first estimate of the state at step end + predictedY = stepEnd.getState(); + + // evaluate the derivative + final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // predict Nordsieck vector at step end + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = getStepSize().multiply(yDot[j]); + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // evaluate error + error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck); + + if (error.subtract(1.0).getReal() >= 0.0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = computeStepGrowShrinkFactor(error); + rescale(filterStep(getStepSize().multiply(factor), forward, false)); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), + getStepStart().getTime().add(getStepSize()), + getStepSize(), + scaled, + nordsieck); + + } + } + + // discrete events handling + setStepStart(acceptStep(new AdamsFieldStepInterpolator(getStepSize(), stepEnd, + predictedScaled, predictedNordsieck, forward, + getStepStart(), stepEnd, + equations.getMapper()), + finalTime)); + scaled = predictedScaled; + nordsieck = predictedNordsieck; + + if (!isLastStep()) { + + System.arraycopy(predictedY, 0, y, 0, y.length); + + if (resetOccurred()) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(equations, getStepStart(), finalTime); + } + + // stepsize control for next step + final T factor = computeStepGrowShrinkFactor(error); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + T hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + rescale(hNew); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java new file mode 100644 index 000000000..2a42b968c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + + +/** + * This class implements explicit Adams-Bashforth integrators for Ordinary + * Differential Equations. + * + *

    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:

    + *
      + *
    • k = 1: yn+1 = yn + h y'n
    • + *
    • k = 2: yn+1 = yn + h (3y'n-y'n-1)/2
    • + *
    • k = 3: yn+1 = yn + h (23y'n-16y'n-1+5y'n-2)/12
    • + *
    • k = 4: yn+1 = yn + h (55y'n-59y'n-1+37y'n-2-9y'n-3)/24
    • + *
    • ...
    • + *
    + * + *

    A k-steps Adams-Bashforth method is of order k.

    + * + *

    Implementation details

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    The definitions above use the classical representation with several previous first + * derivatives. Lets define + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Bashforth methods can be written: + *
      + *
    • k = 1: yn+1 = yn + s1(n)
    • + *
    • k = 2: yn+1 = yn + 3/2 s1(n) + [ -1/2 ] qn
    • + *
    • k = 3: yn+1 = yn + 23/12 s1(n) + [ -16/12 5/12 ] qn
    • + *
    • k = 4: yn+1 = yn + 55/24 s1(n) + [ -59/24 37/24 -9/24 ] qn
    • + *
    • ...
    • + *

    + * + *

    Instead of using the classical representation with first derivatives only (yn, + * s1(n) and qn), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (yn, s1(n) + * and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    Using the Nordsieck vector has several advantages: + *

      + *
    • it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,
    • + *
    • it simplifies step changes that occur when discrete events that truncate + * the step are triggered,
    • + *
    • it allows to extend the methods in order to support adaptive stepsize.
    • + *

    + * + *

    The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows: + *

      + *
    • yn+1 = yn + s1(n) + uT rn
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 

    + * + *

    The P-1u vector and the P-1 A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.

    + * + * @since 2.0 + */ +public class AdamsBashforthIntegrator extends AdamsIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Bashforth"; + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsBashforthIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(METHOD_NAME, nSteps, nSteps, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsBashforthIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(METHOD_NAME, nSteps, nSteps, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Estimate error. + *

    + * Error is estimated by interpolating back to previous state using + * the state Taylor expansion and comparing to real previous state. + *

    + * @param previousState state vector at step start + * @param predictedState predicted state vector at step end + * @param predictedScaled predicted value of the scaled derivatives at step end + * @param predictedNordsieck predicted value of the Nordsieck vector at step end + * @return estimated normalized local discretization error + */ + private double errorEstimation(final double[] previousState, + final double[] predictedState, + final double[] predictedScaled, + final RealMatrix predictedNordsieck) { + + double error = 0; + for (int i = 0; i < mainSetDimension; ++i) { + final double yScale = FastMath.abs(predictedState[i]); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + double variation = 0; + int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1; + for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) { + variation += sign * predictedNordsieck.getEntry(k, i); + sign = -sign; + } + variation -= predictedScaled[i]; + + final double ratio = (predictedState[i] - previousState[i] + variation) / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // initialize working arrays + final double[] y = equations.getCompleteState(); + final double[] yDot = new double[y.length]; + + // set up an interpolator sharing the integrator arrays + final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator(); + interpolator.reinitialize(y, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + + // set up integration control objects + initIntegration(equations.getTime(), y, t); + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations.getTime(), y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + interpolator.storeTime(stepStart); + + // reuse the step that was chosen by the starter integrator + double hNew = stepSize; + interpolator.rescale(hNew); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + final double[] predictedY = new double[y.length]; + final double[] predictedScaled = new double[y.length]; + Array2DRowRealMatrix predictedNordsieck = null; + double error = 10; + while (error >= 1.0) { + + // predict a first estimate of the state at step end + final double stepEnd = stepStart + hNew; + interpolator.storeTime(stepEnd); + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), predictedY); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), predictedY); + ++index; + } + + // evaluate the derivative + computeDerivatives(stepEnd, predictedY, yDot); + + // predict Nordsieck vector at step end + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = hNew * yDot[j]; + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // evaluate error + error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck); + + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = computeStepGrowShrinkFactor(error); + hNew = filterStep(hNew * factor, forward, false); + interpolator.rescale(hNew); + + } + } + + stepSize = hNew; + final double stepEnd = stepStart + stepSize; + interpolator.reinitialize(stepEnd, stepSize, predictedScaled, predictedNordsieck); + + // discrete events handling + interpolator.storeTime(stepEnd); + System.arraycopy(predictedY, 0, y, 0, y.length); + stepStart = acceptStep(interpolator, y, yDot, t); + scaled = predictedScaled; + nordsieck = predictedNordsieck; + interpolator.reinitialize(stepEnd, stepSize, scaled, nordsieck); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (resetOccurred) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(stepStart, y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + } + + // stepsize control for next step + final double factor = computeStepGrowShrinkFactor(error); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + interpolator.rescale(hNew); + + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java new file mode 100644 index 000000000..89175861b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldExpandableODE; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import com.fr.third.org.apache.commons.math3.ode.MultistepFieldIntegrator; + + +/** Base class for {@link AdamsBashforthFieldIntegrator Adams-Bashforth} and + * {@link AdamsMoultonFieldIntegrator Adams-Moulton} integrators. + * @param the type of the field elements + * @since 3.6 + */ +public abstract class AdamsFieldIntegrator> extends MultistepFieldIntegrator { + + /** Transformer. */ + private final AdamsNordsieckFieldTransformer transformer; + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsFieldIntegrator(final Field field, final String name, + final int nSteps, final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, name, nSteps, order, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps); + } + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsFieldIntegrator(final Field field, final String name, + final int nSteps, final int order, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, name, nSteps, order, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps); + } + + /** {@inheritDoc} */ + public abstract FieldODEStateAndDerivative integrate(final FieldExpandableODE equations, + final FieldODEState initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + protected Array2DRowFieldMatrix initializeHighOrderDerivatives(final T h, final T[] t, + final T[][] y, + final T[][] yDot) { + return transformer.initializeHighOrderDerivatives(h, t, y, yDot); + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the P-1 A P rn part.

    + * @param highOrder high order scaled derivatives + * (h2/2 y'', ... hk/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix) + */ + public Array2DRowFieldMatrix updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix highOrder) { + return transformer.updateHighOrderDerivativesPhase1(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the (s1(n) - s1(n+1)) P-1 u part.

    + *

    Phase 1 of the update must already have been performed.

    + * @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 + * (h2/2 y'', ... hk/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix) + */ + public void updateHighOrderDerivativesPhase2(final T[] start, final T[] end, + final Array2DRowFieldMatrix highOrder) { + transformer.updateHighOrderDerivativesPhase2(start, end, highOrder); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java new file mode 100644 index 000000000..de0d33c1c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements an interpolator for Adams integrators using Nordsieck representation. + * + *

    This interpolator computes dense output around the current point. + * The interpolation equation is based on Taylor series formulas. + * + * @see AdamsBashforthFieldIntegrator + * @see AdamsMoultonFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class AdamsFieldStepInterpolator> extends AbstractFieldStepInterpolator { + + /** Step size used in the first scaled derivative and Nordsieck vector. */ + private T scalingH; + + /** Reference state. + *

    Sometimes, the reference state is the same as globalPreviousState, + * sometimes it is the same as globalCurrentState, so we use a separate + * field to avoid any confusion. + *

    + */ + private final FieldODEStateAndDerivative reference; + + /** First scaled derivative. */ + private final T[] scaled; + + /** Nordsieck vector. */ + private final Array2DRowFieldMatrix nordsieck; + + /** Simple constructor. + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param reference reference state from which Taylor expansion are estimated + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @param isForward integration direction indicator + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param equationsMapper mapper for ODE equations primary and secondary components + */ + AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative reference, + final T[] scaled, final Array2DRowFieldMatrix nordsieck, + final boolean isForward, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper equationsMapper) { + this(stepSize, reference, scaled, nordsieck, + isForward, globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, equationsMapper); + } + + /** Simple constructor. + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param reference reference state from which Taylor expansion are estimated + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @param isForward integration direction indicator + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param equationsMapper mapper for ODE equations primary and secondary components + */ + private AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative reference, + final T[] scaled, final Array2DRowFieldMatrix nordsieck, + final boolean isForward, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper equationsMapper) { + super(isForward, globalPreviousState, globalCurrentState, + softPreviousState, softCurrentState, equationsMapper); + this.scalingH = stepSize; + this.reference = reference; + this.scaled = scaled.clone(); + this.nordsieck = new Array2DRowFieldMatrix(nordsieck.getData(), false); + } + + /** Create a new instance. + * @param newForward integration direction indicator + * @param newGlobalPreviousState start of the global step + * @param newGlobalCurrentState end of the global step + * @param newSoftPreviousState start of the restricted step + * @param newSoftCurrentState end of the restricted step + * @param newMapper equations mapper for the all equations + * @return a new instance + */ + @Override + protected AdamsFieldStepInterpolator create(boolean newForward, + FieldODEStateAndDerivative newGlobalPreviousState, + FieldODEStateAndDerivative newGlobalCurrentState, + FieldODEStateAndDerivative newSoftPreviousState, + FieldODEStateAndDerivative newSoftCurrentState, + FieldEquationsMapper newMapper) { + return new AdamsFieldStepInterpolator(scalingH, reference, scaled, nordsieck, + newForward, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + + } + + /** {@inheritDoc} */ + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper equationsMapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + return taylor(reference, time, scalingH, scaled, nordsieck); + } + + /** Estimate state by applying Taylor formula. + * @param reference reference state + * @param time time at which state must be estimated + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @return estimated state + * @param the type of the field elements + */ + public static > FieldODEStateAndDerivative taylor(final FieldODEStateAndDerivative reference, + final S time, final S stepSize, + final S[] scaled, + final Array2DRowFieldMatrix nordsieck) { + + final S x = time.subtract(reference.getTime()); + final S normalizedAbscissa = x.divide(stepSize); + + S[] stateVariation = MathArrays.buildArray(time.getField(), scaled.length); + Arrays.fill(stateVariation, time.getField().getZero()); + S[] estimatedDerivatives = MathArrays.buildArray(time.getField(), scaled.length); + Arrays.fill(estimatedDerivatives, time.getField().getZero()); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + final S[][] nData = nordsieck.getDataRef(); + for (int i = nData.length - 1; i >= 0; --i) { + final int order = i + 2; + final S[] nDataI = nData[i]; + final S power = normalizedAbscissa.pow(order); + for (int j = 0; j < nDataI.length; ++j) { + final S d = nDataI[j].multiply(power); + stateVariation[j] = stateVariation[j].add(d); + estimatedDerivatives[j] = estimatedDerivatives[j].add(d.multiply(order)); + } + } + + S[] estimatedState = reference.getState(); + for (int j = 0; j < stateVariation.length; ++j) { + stateVariation[j] = stateVariation[j].add(scaled[j].multiply(normalizedAbscissa)); + estimatedState[j] = estimatedState[j].add(stateVariation[j]); + estimatedDerivatives[j] = + estimatedDerivatives[j].add(scaled[j].multiply(normalizedAbscissa)).divide(x); + } + + return new FieldODEStateAndDerivative(time, estimatedState, estimatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java new file mode 100644 index 000000000..11c654630 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.ode.MultistepIntegrator; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + + +/** Base class for {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators. + * @since 2.0 + */ +public abstract class AdamsIntegrator extends MultistepIntegrator { + + /** Transformer. */ + private final AdamsNordsieckTransformer transformer; + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsIntegrator(final String name, final int nSteps, final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(name, nSteps, order, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + transformer = AdamsNordsieckTransformer.getInstance(nSteps); + } + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsIntegrator(final String name, final int nSteps, final int order, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(name, nSteps, order, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + transformer = AdamsNordsieckTransformer.getInstance(nSteps); + } + + /** {@inheritDoc} */ + @Override + public abstract void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + protected Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot) { + return transformer.initializeHighOrderDerivatives(h, t, y, yDot); + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the P-1 A P rn part.

    + * @param highOrder high order scaled derivatives + * (h2/2 y'', ... hk/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). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the (s1(n) - s1(n+1)) P-1 u part.

    + *

    Phase 1 of the update must already have been performed.

    + * @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 + * (h2/2 y'', ... hk/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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java new file mode 100644 index 000000000..fd6d9a79b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrixPreservingVisitor; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldExpandableODE; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements implicit Adams-Moulton integrators for Ordinary + * Differential Equations. + * + *

    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'n+1 is needed to + * compute yn+1, another method must be used to compute a first + * estimate of yn+1, then compute y'n+1, then compute + * a final estimate of yn+1 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:

    + *
      + *
    • k = 1: yn+1 = yn + h y'n+1
    • + *
    • k = 2: yn+1 = yn + h (y'n+1+y'n)/2
    • + *
    • k = 3: yn+1 = yn + h (5y'n+1+8y'n-y'n-1)/12
    • + *
    • k = 4: yn+1 = yn + h (9y'n+1+19y'n-5y'n-1+y'n-2)/24
    • + *
    • ...
    • + *
    + * + *

    A k-steps Adams-Moulton method is of order k+1.

    + * + *

    Implementation details

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    The definitions above use the classical representation with several previous first + * derivatives. Lets define + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Moulton methods can be written: + *
      + *
    • k = 1: yn+1 = yn + s1(n+1)
    • + *
    • k = 2: yn+1 = yn + 1/2 s1(n+1) + [ 1/2 ] qn+1
    • + *
    • k = 3: yn+1 = yn + 5/12 s1(n+1) + [ 8/12 -1/12 ] qn+1
    • + *
    • k = 4: yn+1 = yn + 9/24 s1(n+1) + [ 19/24 -5/24 1/24 ] qn+1
    • + *
    • ...
    • + *

    + * + *

    Instead of using the classical representation with first derivatives only (yn, + * s1(n+1) and qn+1), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (yn, s1(n) + * and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    Using the Nordsieck vector has several advantages: + *

      + *
    • it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,
    • + *
    • it simplifies step changes that occur when discrete events that truncate + * the step are triggered,
    • + *
    • it allows to extend the methods in order to support adaptive stepsize.
    • + *

    + * + *

    The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step + * n as follows: + *

      + *
    • Yn+1 = yn + s1(n) + uT rn
    • + *
    • S1(n+1) = h f(tn+1, Yn+1)
    • + *
    • Rn+1 = (s1(n) - S1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 
    + * From this predicted vector, the corrected vector is computed as follows: + *
      + *
    • yn+1 = yn + S1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = Rn+1 + (s1(n+1) - S1(n+1)) P-1 u
    • + *
    + * where the upper case Yn+1, S1(n+1) and Rn+1 represent the + * predicted states whereas the lower case yn+1, sn+1 and rn+1 + * represent the corrected states.

    + * + *

    The P-1u vector and the P-1 A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.

    + * + * @param the type of the field elements + * @since 3.6 + */ +public class AdamsMoultonFieldIntegrator> extends AdamsFieldIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Moulton"; + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsMoultonFieldIntegrator(final Field field, final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsMoultonFieldIntegrator(final Field field, final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public FieldODEStateAndDerivative integrate(final FieldExpandableODE equations, + final FieldODEState initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations, getStepStart(), finalTime); + + // reuse the step that was chosen by the starter integrator + FieldODEStateAndDerivative stepStart = getStepStart(); + FieldODEStateAndDerivative stepEnd = + AdamsFieldStepInterpolator.taylor(stepStart, + stepStart.getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + // main integration loop + setIsLastStep(false); + do { + + T[] predictedY = null; + final T[] predictedScaled = MathArrays.buildArray(getField(), y.length); + Array2DRowFieldMatrix predictedNordsieck = null; + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0.0) { + + // predict a first estimate of the state at step end (P in the PECE sequence) + predictedY = stepEnd.getState(); + + // evaluate a first estimate of the derivative (first E in the PECE sequence) + final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // update Nordsieck vector + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = getStepSize().multiply(yDot[j]); + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // apply correction (C in the PECE sequence) + error = predictedNordsieck.walkInOptimizedOrder(new Corrector(y, predictedScaled, predictedY)); + + if (error.subtract(1.0).getReal() >= 0.0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = computeStepGrowShrinkFactor(error); + rescale(filterStep(getStepSize().multiply(factor), forward, false)); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), + getStepStart().getTime().add(getStepSize()), + getStepSize(), + scaled, + nordsieck); + } + } + + // evaluate a final estimate of the derivative (second E in the PECE sequence) + final T[] correctedYDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // update Nordsieck vector + final T[] correctedScaled = MathArrays.buildArray(getField(), y.length); + for (int j = 0; j < correctedScaled.length; ++j) { + correctedScaled[j] = getStepSize().multiply(correctedYDot[j]); + } + updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, predictedNordsieck); + + // discrete events handling + stepEnd = new FieldODEStateAndDerivative(stepEnd.getTime(), predictedY, correctedYDot); + setStepStart(acceptStep(new AdamsFieldStepInterpolator(getStepSize(), stepEnd, + correctedScaled, predictedNordsieck, forward, + getStepStart(), stepEnd, + equations.getMapper()), + finalTime)); + scaled = correctedScaled; + nordsieck = predictedNordsieck; + + if (!isLastStep()) { + + System.arraycopy(predictedY, 0, y, 0, y.length); + + if (resetOccurred()) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(equations, getStepStart(), finalTime); + } + + // stepsize control for next step + final T factor = computeStepGrowShrinkFactor(error); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + T hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + rescale(hNew); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + + /** Corrector for current state in Adams-Moulton method. + *

    + * This visitor implements the Taylor series formula: + *

    +     * Yn+1 = yn + s1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    +     * 
    + *

    + */ + private class Corrector implements FieldMatrixPreservingVisitor { + + /** Previous state. */ + private final T[] previous; + + /** Current scaled first derivative. */ + private final T[] scaled; + + /** Current state before correction. */ + private final T[] before; + + /** Current state after correction. */ + private final T[] after; + + /** Simple constructor. + * @param previous previous state + * @param scaled current scaled first derivative + * @param state state to correct (will be overwritten after visit) + */ + Corrector(final T[] previous, final T[] scaled, final T[] state) { + this.previous = previous; + this.scaled = scaled; + this.after = state; + this.before = state.clone(); + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + Arrays.fill(after, getField().getZero()); + } + + /** {@inheritDoc} */ + public void visit(int row, int column, T value) { + if ((row & 0x1) == 0) { + after[column] = after[column].subtract(value); + } else { + after[column] = after[column].add(value); + } + } + + /** + * End visiting the Nordsieck vector. + *

    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.

    + * @return the normalized correction, if greater than 1, the step + * must be rejected + */ + public T end() { + + T error = getField().getZero(); + for (int i = 0; i < after.length; ++i) { + after[i] = after[i].add(previous[i].add(scaled[i])); + if (i < mainSetDimension) { + final T yScale = MathUtils.max(previous[i].abs(), after[i].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + final T ratio = after[i].subtract(before[i]).divide(tol); // (corrected-predicted)/tol + error = error.add(ratio.multiply(ratio)); + } + } + + return error.divide(mainSetDimension).sqrt(); + + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java new file mode 100644 index 000000000..e1a0bf81b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java @@ -0,0 +1,421 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrixPreservingVisitor; +import com.fr.third.org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + + +/** + * This class implements implicit Adams-Moulton integrators for Ordinary + * Differential Equations. + * + *

    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'n+1 is needed to + * compute yn+1, another method must be used to compute a first + * estimate of yn+1, then compute y'n+1, then compute + * a final estimate of yn+1 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:

    + *
      + *
    • k = 1: yn+1 = yn + h y'n+1
    • + *
    • k = 2: yn+1 = yn + h (y'n+1+y'n)/2
    • + *
    • k = 3: yn+1 = yn + h (5y'n+1+8y'n-y'n-1)/12
    • + *
    • k = 4: yn+1 = yn + h (9y'n+1+19y'n-5y'n-1+y'n-2)/24
    • + *
    • ...
    • + *
    + * + *

    A k-steps Adams-Moulton method is of order k+1.

    + * + *

    Implementation details

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    The definitions above use the classical representation with several previous first + * derivatives. Lets define + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Moulton methods can be written: + *
      + *
    • k = 1: yn+1 = yn + s1(n+1)
    • + *
    • k = 2: yn+1 = yn + 1/2 s1(n+1) + [ 1/2 ] qn+1
    • + *
    • k = 3: yn+1 = yn + 5/12 s1(n+1) + [ 8/12 -1/12 ] qn+1
    • + *
    • k = 4: yn+1 = yn + 9/24 s1(n+1) + [ 19/24 -5/24 1/24 ] qn+1
    • + *
    • ...
    • + *

    + * + *

    Instead of using the classical representation with first derivatives only (yn, + * s1(n+1) and qn+1), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (yn, s1(n) + * and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    Using the Nordsieck vector has several advantages: + *

      + *
    • it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,
    • + *
    • it simplifies step changes that occur when discrete events that truncate + * the step are triggered,
    • + *
    • it allows to extend the methods in order to support adaptive stepsize.
    • + *

    + * + *

    The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step + * n as follows: + *

      + *
    • Yn+1 = yn + s1(n) + uT rn
    • + *
    • S1(n+1) = h f(tn+1, Yn+1)
    • + *
    • Rn+1 = (s1(n) - S1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 
    + * From this predicted vector, the corrected vector is computed as follows: + *
      + *
    • yn+1 = yn + S1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = Rn+1 + (s1(n+1) - S1(n+1)) P-1 u
    • + *
    + * where the upper case Yn+1, S1(n+1) and Rn+1 represent the + * predicted states whereas the lower case yn+1, sn+1 and rn+1 + * represent the corrected states.

    + * + *

    The P-1u vector and the P-1 A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.

    + * + * @since 2.0 + */ +public class AdamsMoultonIntegrator extends AdamsIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Moulton"; + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsMoultonIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsMoultonIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations,final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // initialize working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final double[] yDot = new double[y.length]; + final double[] yTmp = new double[y.length]; + final double[] predictedScaled = new double[y.length]; + Array2DRowRealMatrix nordsieckTmp = null; + + // set up two interpolators sharing the integrator arrays + final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator(); + interpolator.reinitialize(y, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + + // set up integration control objects + initIntegration(equations.getTime(), y0, t); + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations.getTime(), y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + interpolator.storeTime(stepStart); + + double hNew = stepSize; + interpolator.rescale(hNew); + + isLastStep = false; + do { + + double error = 10; + while (error >= 1.0) { + + stepSize = hNew; + + // predict a first estimate of the state at step end (P in the PECE sequence) + final double stepEnd = stepStart + stepSize; + interpolator.setInterpolatedTime(stepEnd); + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), yTmp); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), yTmp); + ++index; + } + + // evaluate a first estimate of the derivative (first E in the PECE sequence) + computeDerivatives(stepEnd, yTmp, yDot); + + // update Nordsieck vector + for (int j = 0; j < y0.length; ++j) { + predictedScaled[j] = stepSize * yDot[j]; + } + nordsieckTmp = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, nordsieckTmp); + + // apply correction (C in the PECE sequence) + error = nordsieckTmp.walkInOptimizedOrder(new Corrector(y, predictedScaled, yTmp)); + + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = computeStepGrowShrinkFactor(error); + hNew = filterStep(stepSize * factor, forward, false); + interpolator.rescale(hNew); + } + } + + // evaluate a final estimate of the derivative (second E in the PECE sequence) + final double stepEnd = stepStart + stepSize; + computeDerivatives(stepEnd, yTmp, yDot); + + // update Nordsieck vector + final double[] correctedScaled = new double[y0.length]; + for (int j = 0; j < y0.length; ++j) { + correctedScaled[j] = stepSize * yDot[j]; + } + updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, nordsieckTmp); + + // discrete events handling + System.arraycopy(yTmp, 0, y, 0, y.length); + interpolator.reinitialize(stepEnd, stepSize, correctedScaled, nordsieckTmp); + interpolator.storeTime(stepStart); + interpolator.shift(); + interpolator.storeTime(stepEnd); + stepStart = acceptStep(interpolator, y, yDot, t); + scaled = correctedScaled; + nordsieck = nordsieckTmp; + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (resetOccurred) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(stepStart, y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + + } + + // stepsize control for next step + final double factor = computeStepGrowShrinkFactor(error); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + interpolator.rescale(hNew); + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + + /** Corrector for current state in Adams-Moulton method. + *

    + * This visitor implements the Taylor series formula: + *

    +     * Yn+1 = yn + s1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    +     * 
    + *

    + */ + private class Corrector implements RealMatrixPreservingVisitor { + + /** Previous state. */ + private final double[] previous; + + /** Current scaled first derivative. */ + private final double[] scaled; + + /** Current state before correction. */ + private final double[] before; + + /** Current state after correction. */ + private final double[] after; + + /** Simple constructor. + * @param previous previous state + * @param scaled current scaled first derivative + * @param state state to correct (will be overwritten after visit) + */ + Corrector(final double[] previous, final double[] scaled, final double[] state) { + this.previous = previous; + this.scaled = scaled; + this.after = state; + this.before = state.clone(); + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + Arrays.fill(after, 0.0); + } + + /** {@inheritDoc} */ + public void visit(int row, int column, double value) { + if ((row & 0x1) == 0) { + after[column] -= value; + } else { + after[column] += value; + } + } + + /** + * End visiting the Nordsieck vector. + *

    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.

    + * @return the normalized correction, if greater than 1, the step + * must be rejected + */ + public double end() { + + double error = 0; + for (int i = 0; i < after.length; ++i) { + after[i] += previous[i] + scaled[i]; + if (i < mainSetDimension) { + final double yScale = FastMath.max(FastMath.abs(previous[i]), FastMath.abs(after[i])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale); + final double ratio = (after[i] - before[i]) / tol; // (corrected-predicted)/tol + error += ratio * ratio; + } + } + + return FastMath.sqrt(error / mainSetDimension); + + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java new file mode 100644 index 000000000..fc5bfc2d4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java @@ -0,0 +1,363 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayFieldVector; +import com.fr.third.org.apache.commons.math3.linear.FieldDecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.FieldLUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrix; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** Transformer to Nordsieck vectors for Adams integrators. + *

    This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between + * classical representation with several previous first derivatives and Nordsieck + * representation with higher order scaled derivatives.

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    With the previous definition, the classical representation of multistep methods + * uses first derivatives only, i.e. it handles yn, s1(n) and + * qn where qn is defined as: + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity).

    + * + *

    Another possible representation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step, i.e it handles yn, + * s1(n) and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * 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 rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    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.

    + * + *

    For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + *

      + *
    • yn+1 = yn + s1(n) + uT rn
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 

    + * + *

    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: + *

      + *
    • Yn+1 = yn + s1(n) + uT rn
    • + *
    • S1(n+1) = h f(tn+1, Yn+1)
    • + *
    • Rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * From this predicted vector, the corrected vector is computed as follows: + *
      + *
    • yn+1 = yn + S1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = Rn+1 + (s1(n+1) - S1(n+1)) P-1 u
    • + *
    + * where the upper case Yn+1, S1(n+1) and Rn+1 represent the + * predicted states whereas the lower case yn+1, sn+1 and rn+1 + * represent the corrected states.

    + * + *

    We observe that both methods use similar update formulas. In both cases a P-1u + * vector and a P-1 A P matrix are used that do not depend on the state, + * they only depend on k. This class handles these transformations.

    + * + * @param the type of the field elements + * @since 3.6 + */ +public class AdamsNordsieckFieldTransformer> { + + /** Cache for already computed coefficients. */ + private static final Map>, + AdamsNordsieckFieldTransformer>>> CACHE = + new HashMap>, + AdamsNordsieckFieldTransformer>>>(); + + /** Field to which the time and state vector elements belong. */ + private final Field field; + + /** Update matrix for the higher order derivatives h2/2 y'', h3/6 y''' ... */ + private final Array2DRowFieldMatrix update; + + /** Update coefficients of the higher order derivatives wrt y'. */ + private final T[] c1; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param n number of steps of the multistep method + * (excluding the one being computed) + */ + private AdamsNordsieckFieldTransformer(final Field field, final int n) { + + this.field = field; + final int rows = n - 1; + + // compute coefficients + FieldMatrix bigP = buildP(rows); + FieldDecompositionSolver pSolver = + new FieldLUDecomposition(bigP).getSolver(); + + T[] u = MathArrays.buildArray(field, rows); + Arrays.fill(u, field.getOne()); + c1 = pSolver.solve(new ArrayFieldVector(u, false)).toArray(); + + // update coefficients are computed by combining transform from + // Nordsieck to multistep, then shifting rows to represent step advance + // then applying inverse transform + T[][] shiftedP = bigP.getData(); + for (int i = shiftedP.length - 1; i > 0; --i) { + // shift rows + shiftedP[i] = shiftedP[i - 1]; + } + shiftedP[0] = MathArrays.buildArray(field, rows); + Arrays.fill(shiftedP[0], field.getZero()); + update = new Array2DRowFieldMatrix(pSolver.solve(new Array2DRowFieldMatrix(shiftedP, false)).getData()); + + } + + /** Get the Nordsieck transformer for a given field and number of steps. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @return Nordsieck transformer for the specified field and number of steps + * @param the type of the field elements + */ + @SuppressWarnings("unchecked") + public static > AdamsNordsieckFieldTransformer + getInstance(final Field field, final int nSteps) { + synchronized(CACHE) { + Map>, + AdamsNordsieckFieldTransformer>> map = CACHE.get(nSteps); + if (map == null) { + map = new HashMap>, + AdamsNordsieckFieldTransformer>>(); + CACHE.put(nSteps, map); + } + @SuppressWarnings("rawtypes") // use rawtype to avoid compilation problems with java 1.5 + AdamsNordsieckFieldTransformer t = map.get(field); + if (t == null) { + t = new AdamsNordsieckFieldTransformer(field, nSteps); + map.put(field, (AdamsNordsieckFieldTransformer) t); + } + return (AdamsNordsieckFieldTransformer) t; + + } + } + + /** Build the P matrix. + *

    The P matrix general terms are shifted (j+1) (-i)j terms + * with i being the row number starting from 1 and j being the column + * number starting from 1: + *

    +     *        [  -2   3   -4    5  ... ]
    +     *        [  -4  12  -32   80  ... ]
    +     *   P =  [  -6  27 -108  405  ... ]
    +     *        [  -8  48 -256 1280  ... ]
    +     *        [          ...           ]
    +     * 

    + * @param rows number of rows of the matrix + * @return P matrix + */ + private FieldMatrix buildP(final int rows) { + + final T[][] pData = MathArrays.buildArray(field, rows, rows); + + for (int i = 1; i <= pData.length; ++i) { + // build the P matrix elements from Taylor series formulas + final T[] pI = pData[i - 1]; + final int factor = -i; + T aj = field.getZero().add(factor); + for (int j = 1; j <= pI.length; ++j) { + pI[j - 1] = aj.multiply(j + 1); + aj = aj.multiply(factor); + } + } + + return new Array2DRowFieldMatrix(pData, false); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at start of first step (h2/2 y''n, + * h3/6 y'''n ... hk/k! y(k)n) + */ + + public Array2DRowFieldMatrix initializeHighOrderDerivatives(final T h, final T[] t, + final T[][] y, + final T[][] yDot) { + + // using Taylor series with di = ti - t0, we get: + // y(ti) - y(t0) - di y'(t0) = di^2 / h^2 s2 + ... + di^k / h^k sk + O(h^k) + // y'(ti) - y'(t0) = 2 di / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1)) + // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear + // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond + // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder. + // The goal is to have s2 to sk as accurate as possible considering the fact the sum is + // truncated and we don't want the error terms to be included in s2 ... sk, so we need + // to solve also for the remainder + final T[][] a = MathArrays.buildArray(field, c1.length + 1, c1.length + 1); + final T[][] b = MathArrays.buildArray(field, c1.length + 1, y[0].length); + final T[] y0 = y[0]; + final T[] yDot0 = yDot[0]; + for (int i = 1; i < y.length; ++i) { + + final T di = t[i].subtract(t[0]); + final T ratio = di.divide(h); + T dikM1Ohk = h.reciprocal(); + + // linear coefficients of equations + // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0) + final T[] aI = a[2 * i - 2]; + final T[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null; + for (int j = 0; j < aI.length; ++j) { + dikM1Ohk = dikM1Ohk.multiply(ratio); + aI[j] = di.multiply(dikM1Ohk); + if (aDotI != null) { + aDotI[j] = dikM1Ohk.multiply(j + 2); + } + } + + // expected value of the previous equations + final T[] yI = y[i]; + final T[] yDotI = yDot[i]; + final T[] bI = b[2 * i - 2]; + final T[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null; + for (int j = 0; j < yI.length; ++j) { + bI[j] = yI[j].subtract(y0[j]).subtract(di.multiply(yDot0[j])); + if (bDotI != null) { + bDotI[j] = yDotI[j].subtract(yDot0[j]); + } + } + + } + + // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk], + // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion + final FieldLUDecomposition decomposition = new FieldLUDecomposition(new Array2DRowFieldMatrix(a, false)); + final FieldMatrix x = decomposition.getSolver().solve(new Array2DRowFieldMatrix(b, false)); + + // extract just the Nordsieck vector [s2 ... sk] + final Array2DRowFieldMatrix truncatedX = + new Array2DRowFieldMatrix(field, x.getRowDimension() - 1, x.getColumnDimension()); + for (int i = 0; i < truncatedX.getRowDimension(); ++i) { + for (int j = 0; j < truncatedX.getColumnDimension(); ++j) { + truncatedX.setEntry(i, j, x.getEntry(i, j)); + } + } + return truncatedX; + + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the P-1 A P rn part.

    + * @param highOrder high order scaled derivatives + * (h2/2 y'', ... hk/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix) + */ + public Array2DRowFieldMatrix updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix highOrder) { + return update.multiply(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the (s1(n) - s1(n+1)) P-1 u part.

    + *

    Phase 1 of the update must already have been performed.

    + * @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 + * (h2/2 y'', ... hk/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix) + */ + public void updateHighOrderDerivativesPhase2(final T[] start, + final T[] end, + final Array2DRowFieldMatrix highOrder) { + final T[][] data = highOrder.getDataRef(); + for (int i = 0; i < data.length; ++i) { + final T[] dataI = data[i]; + final T c1I = c1[i]; + for (int j = 0; j < dataI.length; ++j) { + dataI[j] = dataI[j].add(c1I.multiply(start[j].subtract(end[j]))); + } + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java new file mode 100644 index 000000000..ca12e0350 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayFieldVector; +import com.fr.third.org.apache.commons.math3.linear.FieldDecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.FieldLUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** Transformer to Nordsieck vectors for Adams integrators. + *

    This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between + * classical representation with several previous first derivatives and Nordsieck + * representation with higher order scaled derivatives.

    + * + *

    We define scaled derivatives si(n) at step n as: + *

    + * s1(n) = h y'n for first derivative
    + * s2(n) = h2/2 y''n for second derivative
    + * s3(n) = h3/6 y'''n for third derivative
    + * ...
    + * sk(n) = hk/k! y(k)n for kth derivative
    + * 

    + * + *

    With the previous definition, the classical representation of multistep methods + * uses first derivatives only, i.e. it handles yn, s1(n) and + * qn where qn is defined as: + *

    + *   qn = [ s1(n-1) s1(n-2) ... s1(n-(k-1)) ]T
    + * 
    + * (we omit the k index in the notation for clarity).

    + * + *

    Another possible representation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step, i.e it handles yn, + * s1(n) and rn) where rn is defined as: + *

    + * rn = [ s2(n), s3(n) ... sk(n) ]T
    + * 
    + * (here again we omit the k index in the notation for clarity) + *

    + * + *

    Taylor series formulas show that for any index offset i, s1(n-i) can be + * computed from s1(n), s2(n) ... sk(n), the formula being exact + * for degree k polynomials. + *

    + * s1(n-i) = s1(n) + ∑j>0 (j+1) (-i)j sj+1(n)
    + * 
    + * 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 rn + * and qn resulting from the Taylor series formulas above is: + *
    + * qn = s1(n) u + P rn
    + * 
    + * where u is the [ 1 1 ... 1 ]T vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)j terms with i being the row number starting from 1 and j being + * the column number starting from 1: + *
    + *        [  -2   3   -4    5  ... ]
    + *        [  -4  12  -32   80  ... ]
    + *   P =  [  -6  27 -108  405  ... ]
    + *        [  -8  48 -256 1280  ... ]
    + *        [          ...           ]
    + * 

    + * + *

    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.

    + * + *

    For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + *

      + *
    • yn+1 = yn + s1(n) + uT rn
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * where A is a rows shifting matrix (the lower left part is an identity matrix): + *
    + *        [ 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 ]
    + * 

    + * + *

    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: + *

      + *
    • Yn+1 = yn + s1(n) + uT rn
    • + *
    • S1(n+1) = h f(tn+1, Yn+1)
    • + *
    • Rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    • + *
    + * From this predicted vector, the corrected vector is computed as follows: + *
      + *
    • yn+1 = yn + S1(n+1) + [ -1 +1 -1 +1 ... ±1 ] rn+1
    • + *
    • s1(n+1) = h f(tn+1, yn+1)
    • + *
    • rn+1 = Rn+1 + (s1(n+1) - S1(n+1)) P-1 u
    • + *
    + * where the upper case Yn+1, S1(n+1) and Rn+1 represent the + * predicted states whereas the lower case yn+1, sn+1 and rn+1 + * represent the corrected states.

    + * + *

    We observe that both methods use similar update formulas. In both cases a P-1u + * vector and a P-1 A P matrix are used that do not depend on the state, + * they only depend on k. This class handles these transformations.

    + * + * @since 2.0 + */ +public class AdamsNordsieckTransformer { + + /** Cache for already computed coefficients. */ + private static final Map CACHE = + new HashMap(); + + /** Update matrix for the higher order derivatives h2/2 y'', h3/6 y''' ... */ + private final Array2DRowRealMatrix update; + + /** Update coefficients of the higher order derivatives wrt y'. */ + private final double[] c1; + + /** Simple constructor. + * @param n number of steps of the multistep method + * (excluding the one being computed) + */ + private AdamsNordsieckTransformer(final int n) { + + final int rows = n - 1; + + // compute exact coefficients + FieldMatrix bigP = buildP(rows); + FieldDecompositionSolver pSolver = + new FieldLUDecomposition(bigP).getSolver(); + + BigFraction[] u = new BigFraction[rows]; + Arrays.fill(u, BigFraction.ONE); + BigFraction[] bigC1 = pSolver.solve(new ArrayFieldVector(u, false)).toArray(); + + // update coefficients are computed by combining transform from + // Nordsieck to multistep, then shifting rows to represent step advance + // then applying inverse transform + BigFraction[][] shiftedP = bigP.getData(); + for (int i = shiftedP.length - 1; i > 0; --i) { + // shift rows + shiftedP[i] = shiftedP[i - 1]; + } + shiftedP[0] = new BigFraction[rows]; + Arrays.fill(shiftedP[0], BigFraction.ZERO); + FieldMatrix bigMSupdate = + pSolver.solve(new Array2DRowFieldMatrix(shiftedP, false)); + + // convert coefficients to double + update = MatrixUtils.bigFractionMatrixToRealMatrix(bigMSupdate); + c1 = new double[rows]; + for (int i = 0; i < rows; ++i) { + c1[i] = bigC1[i].doubleValue(); + } + + } + + /** Get the Nordsieck transformer for a given number of steps. + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @return Nordsieck transformer for the specified number of steps + */ + public static AdamsNordsieckTransformer getInstance(final int nSteps) { + synchronized(CACHE) { + AdamsNordsieckTransformer t = CACHE.get(nSteps); + if (t == null) { + t = new AdamsNordsieckTransformer(nSteps); + CACHE.put(nSteps, t); + } + return t; + } + } + + /** Get the number of steps of the method + * (excluding the one being computed). + * @return number of steps of the method + * (excluding the one being computed) + * @deprecated as of 3.6, this method is not used anymore + */ + @Deprecated + public int getNSteps() { + return c1.length; + } + + /** Build the P matrix. + *

    The P matrix general terms are shifted (j+1) (-i)j terms + * with i being the row number starting from 1 and j being the column + * number starting from 1: + *

    +     *        [  -2   3   -4    5  ... ]
    +     *        [  -4  12  -32   80  ... ]
    +     *   P =  [  -6  27 -108  405  ... ]
    +     *        [  -8  48 -256 1280  ... ]
    +     *        [          ...           ]
    +     * 

    + * @param rows number of rows of the matrix + * @return P matrix + */ + private FieldMatrix buildP(final int rows) { + + final BigFraction[][] pData = new BigFraction[rows][rows]; + + for (int i = 1; i <= pData.length; ++i) { + // build the P matrix elements from Taylor series formulas + final BigFraction[] pI = pData[i - 1]; + final int factor = -i; + int aj = factor; + for (int j = 1; j <= pI.length; ++j) { + pI[j - 1] = new BigFraction(aj * (j + 1)); + aj *= factor; + } + } + + return new Array2DRowFieldMatrix(pData, false); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at start of first step (h2/2 y''n, + * h3/6 y'''n ... hk/k! y(k)n) + */ + + public Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot) { + + // using Taylor series with di = ti - t0, we get: + // y(ti) - y(t0) - di y'(t0) = di^2 / h^2 s2 + ... + di^k / h^k sk + O(h^k) + // y'(ti) - y'(t0) = 2 di / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1)) + // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear + // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond + // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder. + // The goal is to have s2 to sk as accurate as possible considering the fact the sum is + // truncated and we don't want the error terms to be included in s2 ... sk, so we need + // to solve also for the remainder + final double[][] a = new double[c1.length + 1][c1.length + 1]; + final double[][] b = new double[c1.length + 1][y[0].length]; + final double[] y0 = y[0]; + final double[] yDot0 = yDot[0]; + for (int i = 1; i < y.length; ++i) { + + final double di = t[i] - t[0]; + final double ratio = di / h; + double dikM1Ohk = 1 / h; + + // linear coefficients of equations + // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0) + final double[] aI = a[2 * i - 2]; + final double[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null; + for (int j = 0; j < aI.length; ++j) { + dikM1Ohk *= ratio; + aI[j] = di * dikM1Ohk; + if (aDotI != null) { + aDotI[j] = (j + 2) * dikM1Ohk; + } + } + + // expected value of the previous equations + final double[] yI = y[i]; + final double[] yDotI = yDot[i]; + final double[] bI = b[2 * i - 2]; + final double[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null; + for (int j = 0; j < yI.length; ++j) { + bI[j] = yI[j] - y0[j] - di * yDot0[j]; + if (bDotI != null) { + bDotI[j] = yDotI[j] - yDot0[j]; + } + } + + } + + // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk], + // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion + final QRDecomposition decomposition = new QRDecomposition(new Array2DRowRealMatrix(a, false)); + final RealMatrix x = decomposition.getSolver().solve(new Array2DRowRealMatrix(b, false)); + + // extract just the Nordsieck vector [s2 ... sk] + final Array2DRowRealMatrix truncatedX = new Array2DRowRealMatrix(x.getRowDimension() - 1, x.getColumnDimension()); + for (int i = 0; i < truncatedX.getRowDimension(); ++i) { + for (int j = 0; j < truncatedX.getColumnDimension(); ++j) { + truncatedX.setEntry(i, j, x.getEntry(i, j)); + } + } + return truncatedX; + + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the P-1 A P rn part.

    + * @param highOrder high order scaled derivatives + * (h2/2 y'', ... hk/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). + *

    The complete update of high order derivatives has a form similar to: + *

    +     * rn+1 = (s1(n) - s1(n+1)) P-1 u + P-1 A P rn
    +     * 
    + * this method computes the (s1(n) - s1(n+1)) P-1 u part.

    + *

    Phase 1 of the update must already have been performed.

    + * @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 + * (h2/2 y'', ... hk/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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java new file mode 100644 index 000000000..564e5bf00 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.AbstractFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This abstract class holds the common part of all adaptive + * stepsize integrators for Ordinary Differential Equations. + * + *

    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 + *

    + * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
    + * 
    + * 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. + *

    + *

    + * Note that only the {@link FieldODEState#getState() main part} + * of the state vector is used for stepsize control. The {@link + * FieldODEState#getSecondaryState(int) secondary parts} of the state + * vector are explicitly ignored for stepsize control. + *

    + * + *

    If the estimated error for ym+1 is such that + *

    + * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
    + * 
    + * + * (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.

    + * + * @param the type of the field elements + * @since 3.6 + * + */ + +public abstract class AdaptiveStepsizeFieldIntegrator> + extends AbstractFieldIntegrator { + + /** Allowed absolute scalar error. */ + protected double scalAbsoluteTolerance; + + /** Allowed relative scalar error. */ + protected double scalRelativeTolerance; + + /** Allowed absolute vectorial error. */ + protected double[] vecAbsoluteTolerance; + + /** Allowed relative vectorial error. */ + protected double[] vecRelativeTolerance; + + /** Main set dimension. */ + protected int mainSetDimension; + + /** User supplied initial step. */ + private T initialStep; + + /** Minimal step. */ + private T minStep; + + /** Maximal step. */ + private T maxStep; + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeFieldIntegrator(final Field field, final String name, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(field, name); + setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + resetInternalState(); + + } + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeFieldIntegrator(final Field field, final String name, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(field, name); + setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + resetInternalState(); + + } + + /** Set the adaptive step size control parameters. + *

    + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize} + * is not called by the user. + *

    + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double absoluteTolerance, + final double relativeTolerance) { + + minStep = getField().getZero().add(FastMath.abs(minimalStep)); + maxStep = getField().getZero().add(FastMath.abs(maximalStep)); + initialStep = getField().getOne().negate(); + + scalAbsoluteTolerance = absoluteTolerance; + scalRelativeTolerance = relativeTolerance; + vecAbsoluteTolerance = null; + vecRelativeTolerance = null; + + } + + /** Set the adaptive step size control parameters. + *

    + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize} + * is not called by the user. + *

    + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double[] absoluteTolerance, + final double[] relativeTolerance) { + + minStep = getField().getZero().add(FastMath.abs(minimalStep)); + maxStep = getField().getZero().add(FastMath.abs(maximalStep)); + initialStep = getField().getOne().negate(); + + scalAbsoluteTolerance = 0; + scalRelativeTolerance = 0; + vecAbsoluteTolerance = absoluteTolerance.clone(); + vecRelativeTolerance = relativeTolerance.clone(); + + } + + /** Set the initial step size. + *

    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.

    + * @param initialStepSize initial step size to use (must be positive even + * for backward integration ; providing a negative value or a value + * outside of the min/max step interval will lead the integrator to + * ignore the value and compute the initial step size by itself) + */ + public void setInitialStepSize(final T initialStepSize) { + if (initialStepSize.subtract(minStep).getReal() < 0 || + initialStepSize.subtract(maxStep).getReal() > 0) { + initialStep = getField().getOne().negate(); + } else { + initialStep = initialStepSize; + } + } + + /** {@inheritDoc} */ + @Override + protected void sanityChecks(final FieldODEState eqn, final T t) + throws DimensionMismatchException, NumberIsTooSmallException { + + super.sanityChecks(eqn, t); + + mainSetDimension = eqn.getStateDimension(); + + if (vecAbsoluteTolerance != null && vecAbsoluteTolerance.length != mainSetDimension) { + throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length); + } + + if (vecRelativeTolerance != null && vecRelativeTolerance.length != mainSetDimension) { + throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length); + } + + } + + /** Initialize the integration step. + * @param forward forward integration indicator + * @param order order of the method + * @param scale scaling vector for the state vector (can be shorter than state vector) + * @param state0 state at integration start time + * @param mapper mapper for all the equations + * @return first integration step + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public T initializeStep(final boolean forward, final int order, final T[] scale, + final FieldODEStateAndDerivative state0, + final FieldEquationsMapper mapper) + throws MaxCountExceededException, DimensionMismatchException { + + if (initialStep.getReal() > 0) { + // use the user provided value + return forward ? initialStep : initialStep.negate(); + } + + // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale|| + // this guess will be used to perform an Euler step + final T[] y0 = mapper.mapState(state0); + final T[] yDot0 = mapper.mapDerivative(state0); + T yOnScale2 = getField().getZero(); + T yDotOnScale2 = getField().getZero(); + for (int j = 0; j < scale.length; ++j) { + final T ratio = y0[j].divide(scale[j]); + yOnScale2 = yOnScale2.add(ratio.multiply(ratio)); + final T ratioDot = yDot0[j].divide(scale[j]); + yDotOnScale2 = yDotOnScale2.add(ratioDot.multiply(ratioDot)); + } + + T h = (yOnScale2.getReal() < 1.0e-10 || yDotOnScale2.getReal() < 1.0e-10) ? + getField().getZero().add(1.0e-6) : + yOnScale2.divide(yDotOnScale2).sqrt().multiply(0.01); + if (! forward) { + h = h.negate(); + } + + // perform an Euler step using the preceding rough guess + final T[] y1 = MathArrays.buildArray(getField(), y0.length); + for (int j = 0; j < y0.length; ++j) { + y1[j] = y0[j].add(yDot0[j].multiply(h)); + } + final T[] yDot1 = computeDerivatives(state0.getTime().add(h), y1); + + // estimate the second derivative of the solution + T yDDotOnScale = getField().getZero(); + for (int j = 0; j < scale.length; ++j) { + final T ratioDotDot = yDot1[j].subtract(yDot0[j]).divide(scale[j]); + yDDotOnScale = yDDotOnScale.add(ratioDotDot.multiply(ratioDotDot)); + } + yDDotOnScale = yDDotOnScale.sqrt().divide(h); + + // step size is computed such that + // h^order * max (||y'/tol||, ||y''/tol||) = 0.01 + final T maxInv2 = MathUtils.max(yDotOnScale2.sqrt(), yDDotOnScale); + final T h1 = maxInv2.getReal() < 1.0e-15 ? + MathUtils.max(getField().getZero().add(1.0e-6), h.abs().multiply(0.001)) : + maxInv2.multiply(100).reciprocal().pow(1.0 / order); + h = MathUtils.min(h.abs().multiply(100), h1); + h = MathUtils.max(h, state0.getTime().abs().multiply(1.0e-12)); // avoids cancellation when computing t1 - t0 + h = MathUtils.max(minStep, MathUtils.min(maxStep, h)); + if (! forward) { + h = h.negate(); + } + + return h; + + } + + /** Filter the integration step. + * @param h signed step + * @param forward forward integration indicator + * @param acceptSmall if true, steps smaller than the minimal value + * are silently increased up to this value, if false such small + * steps generate an exception + * @return a bounded integration step (h if no bound is reach, or a bounded value) + * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false + */ + protected T filterStep(final T h, final boolean forward, final boolean acceptSmall) + throws NumberIsTooSmallException { + + T filteredH = h; + if (h.abs().subtract(minStep).getReal() < 0) { + if (acceptSmall) { + filteredH = forward ? minStep : minStep.negate(); + } else { + throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION, + h.abs().getReal(), minStep.getReal(), true); + } + } + + if (filteredH.subtract(maxStep).getReal() > 0) { + filteredH = maxStep; + } else if (filteredH.add(maxStep).getReal() < 0) { + filteredH = maxStep.negate(); + } + + return filteredH; + + } + + /** Reset internal state to dummy values. */ + protected void resetInternalState() { + setStepStart(null); + setStepSize(minStep.multiply(maxStep).sqrt()); + } + + /** Get the minimal step. + * @return minimal step + */ + public T getMinStep() { + return minStep; + } + + /** Get the maximal step. + * @return maximal step + */ + public T getMaxStep() { + return maxStep; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java new file mode 100644 index 000000000..6d763b70a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java @@ -0,0 +1,376 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderDifferentialEquations; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.AbstractIntegrator; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + +/** + * This abstract class holds the common part of all adaptive + * stepsize integrators for Ordinary Differential Equations. + * + *

    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 + *

    + * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
    + * 
    + * 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. + *

    + *

    + * If the Ordinary Differential Equations is an {@link ExpandableStatefulODE + * extended ODE} rather than a {@link + * FirstOrderDifferentialEquations basic ODE}, then + * only the {@link ExpandableStatefulODE#getPrimaryState() primary part} + * of the state vector is used for stepsize control, not the complete state vector. + *

    + * + *

    If the estimated error for ym+1 is such that + *

    + * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
    + * 
    + * + * (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.

    + * + * @since 1.2 + * + */ + +public abstract class AdaptiveStepsizeIntegrator + extends AbstractIntegrator { + + /** Allowed absolute scalar error. */ + protected double scalAbsoluteTolerance; + + /** Allowed relative scalar error. */ + protected double scalRelativeTolerance; + + /** Allowed absolute vectorial error. */ + protected double[] vecAbsoluteTolerance; + + /** Allowed relative vectorial error. */ + protected double[] vecRelativeTolerance; + + /** Main set dimension. */ + protected int mainSetDimension; + + /** User supplied initial step. */ + private double initialStep; + + /** Minimal step. */ + private double minStep; + + /** Maximal step. */ + private double maxStep; + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(final String name, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(name); + setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + resetInternalState(); + + } + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(final String name, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(name); + setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + resetInternalState(); + + } + + /** Set the adaptive step size control parameters. + *

    + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(double) setInitialStepSize} + * is not called by the user. + *

    + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double absoluteTolerance, + final double relativeTolerance) { + + minStep = FastMath.abs(minimalStep); + maxStep = FastMath.abs(maximalStep); + initialStep = -1; + + scalAbsoluteTolerance = absoluteTolerance; + scalRelativeTolerance = relativeTolerance; + vecAbsoluteTolerance = null; + vecRelativeTolerance = null; + + } + + /** Set the adaptive step size control parameters. + *

    + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(double) setInitialStepSize} + * is not called by the user. + *

    + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double[] absoluteTolerance, + final double[] relativeTolerance) { + + minStep = FastMath.abs(minimalStep); + maxStep = FastMath.abs(maximalStep); + initialStep = -1; + + scalAbsoluteTolerance = 0; + scalRelativeTolerance = 0; + vecAbsoluteTolerance = absoluteTolerance.clone(); + vecRelativeTolerance = relativeTolerance.clone(); + + } + + /** Set the initial step size. + *

    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.

    + * @param initialStepSize initial step size to use (must be positive even + * for backward integration ; providing a negative value or a value + * outside of the min/max step interval will lead the integrator to + * ignore the value and compute the initial step size by itself) + */ + public void setInitialStepSize(final double initialStepSize) { + if ((initialStepSize < minStep) || (initialStepSize > maxStep)) { + initialStep = -1.0; + } else { + initialStep = initialStepSize; + } + } + + /** {@inheritDoc} */ + @Override + protected void sanityChecks(final ExpandableStatefulODE equations, final double t) + throws DimensionMismatchException, NumberIsTooSmallException { + + super.sanityChecks(equations, t); + + mainSetDimension = equations.getPrimaryMapper().getDimension(); + + if ((vecAbsoluteTolerance != null) && (vecAbsoluteTolerance.length != mainSetDimension)) { + throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length); + } + + if ((vecRelativeTolerance != null) && (vecRelativeTolerance.length != mainSetDimension)) { + throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length); + } + + } + + /** Initialize the integration step. + * @param forward forward integration indicator + * @param order order of the method + * @param scale scaling vector for the state vector (can be shorter than state vector) + * @param t0 start time + * @param y0 state vector at t0 + * @param yDot0 first time derivative of y0 + * @param y1 work array for a state vector + * @param yDot1 work array for the first time derivative of y1 + * @return first integration step + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public double initializeStep(final boolean forward, final int order, final double[] scale, + final double t0, final double[] y0, final double[] yDot0, + final double[] y1, final double[] yDot1) + throws MaxCountExceededException, DimensionMismatchException { + + if (initialStep > 0) { + // use the user provided value + return forward ? initialStep : -initialStep; + } + + // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale|| + // this guess will be used to perform an Euler step + double ratio; + double yOnScale2 = 0; + double yDotOnScale2 = 0; + for (int j = 0; j < scale.length; ++j) { + ratio = y0[j] / scale[j]; + yOnScale2 += ratio * ratio; + ratio = yDot0[j] / scale[j]; + yDotOnScale2 += ratio * ratio; + } + + double h = ((yOnScale2 < 1.0e-10) || (yDotOnScale2 < 1.0e-10)) ? + 1.0e-6 : (0.01 * FastMath.sqrt(yOnScale2 / yDotOnScale2)); + if (! forward) { + h = -h; + } + + // perform an Euler step using the preceding rough guess + for (int j = 0; j < y0.length; ++j) { + y1[j] = y0[j] + h * yDot0[j]; + } + computeDerivatives(t0 + h, y1, yDot1); + + // estimate the second derivative of the solution + double yDDotOnScale = 0; + for (int j = 0; j < scale.length; ++j) { + ratio = (yDot1[j] - yDot0[j]) / scale[j]; + yDDotOnScale += ratio * ratio; + } + yDDotOnScale = FastMath.sqrt(yDDotOnScale) / h; + + // step size is computed such that + // h^order * max (||y'/tol||, ||y''/tol||) = 0.01 + final double maxInv2 = FastMath.max(FastMath.sqrt(yDotOnScale2), yDDotOnScale); + final double h1 = (maxInv2 < 1.0e-15) ? + FastMath.max(1.0e-6, 0.001 * FastMath.abs(h)) : + FastMath.pow(0.01 / maxInv2, 1.0 / order); + h = FastMath.min(100.0 * FastMath.abs(h), h1); + h = FastMath.max(h, 1.0e-12 * FastMath.abs(t0)); // avoids cancellation when computing t1 - t0 + if (h < getMinStep()) { + h = getMinStep(); + } + if (h > getMaxStep()) { + h = getMaxStep(); + } + if (! forward) { + h = -h; + } + + return h; + + } + + /** Filter the integration step. + * @param h signed step + * @param forward forward integration indicator + * @param acceptSmall if true, steps smaller than the minimal value + * are silently increased up to this value, if false such small + * steps generate an exception + * @return a bounded integration step (h if no bound is reach, or a bounded value) + * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false + */ + protected double filterStep(final double h, final boolean forward, final boolean acceptSmall) + throws NumberIsTooSmallException { + + double filteredH = h; + if (FastMath.abs(h) < minStep) { + if (acceptSmall) { + filteredH = forward ? minStep : -minStep; + } else { + throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION, + FastMath.abs(h), minStep, true); + } + } + + if (filteredH > maxStep) { + filteredH = maxStep; + } else if (filteredH < -maxStep) { + filteredH = -maxStep; + } + + return filteredH; + + } + + /** {@inheritDoc} */ + @Override + public abstract void integrate (ExpandableStatefulODE equations, double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + public double getCurrentStepStart() { + return stepStart; + } + + /** Reset internal state to dummy values. */ + protected void resetInternalState() { + stepStart = Double.NaN; + stepSize = FastMath.sqrt(minStep * maxStep); + } + + /** Get the minimal step. + * @return minimal step + */ + public double getMinStep() { + return minStep; + } + + /** Get the maximal step. + * @return maximal step + */ + public double getMaxStep() { + return maxStep; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java new file mode 100644 index 000000000..90b30931e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements the classical fourth order Runge-Kutta + * integrator for Ordinary Differential Equations (it is the most + * often used Runge-Kutta method). + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + *

    + * + * @see EulerFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class ClassicalRungeKuttaFieldIntegrator> + extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build a fourth-order Runge-Kutta integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public ClassicalRungeKuttaFieldIntegrator(final Field field, final T step) { + super(field, "classical Runge-Kutta", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = getField().getOne().multiply(0.5); + c[1] = c[0]; + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 2); + a[1][0] = getField().getZero(); + a[1][1] = a[0][0]; + a[2][0] = getField().getZero(); + a[2][1] = getField().getZero(); + a[2][2] = getField().getOne(); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 6); + b[1] = fraction(1, 3); + b[2] = b[1]; + b[3] = b[0]; + return b; + } + + /** {@inheritDoc} */ + @Override + protected ClassicalRungeKuttaFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new ClassicalRungeKuttaFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java new file mode 100644 index 000000000..71a3b8b78 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the classical fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/6) [ (6 - 9 θ + 4 θ2) y'1 + * + ( 6 θ - 4 θ2) (y'2 + y'3) + * + ( -3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + * + (1 - θ) (h/6) [ (-4 θ^2 + 5 θ - 1) y'1 + * +(4 θ^2 - 2 θ - 2) (y'2 + y'3) + * -(4 θ^2 + θ + 1) y'4 + * ] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 to y'4 are the four + * evaluations of the derivatives already computed during the + * step.

    + * + * @see ClassicalRungeKuttaFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class ClassicalRungeKuttaFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + ClassicalRungeKuttaFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected ClassicalRungeKuttaFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new ClassicalRungeKuttaFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T one = time.getField().getOne(); + final T oneMinusTheta = one.subtract(theta); + final T oneMinus2Theta = one.subtract(theta.multiply(2)); + final T coeffDot1 = oneMinusTheta.multiply(oneMinus2Theta); + final T coeffDot23 = theta.multiply(oneMinusTheta).multiply(2); + final T coeffDot4 = theta.multiply(oneMinus2Theta).negate(); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T s = thetaH.divide(6.0); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6)); + final T coeff23 = s.multiply(theta.multiply(6).subtract(fourTheta2)); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff23, coeff23, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4); + } else { + final T fourTheta = theta.multiply(4); + final T s = oneMinusThetaH.divide(6); + final T coeff1 = s.multiply(theta.multiply(fourTheta.negate().add(5)).subtract(1)); + final T coeff23 = s.multiply(theta.multiply(fourTheta.subtract(2)).subtract(2)); + final T coeff4 = s.multiply(theta.multiply(fourTheta.negate().subtract(1)).subtract(1)); + interpolatedState = currentStateLinearCombination(coeff1, coeff23, coeff23, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java new file mode 100644 index 000000000..4509cba1e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements the classical fourth order Runge-Kutta + * integrator for Ordinary Differential Equations (it is the most + * often used Runge-Kutta method). + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + *

    + * + * @see EulerIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class ClassicalRungeKuttaIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 }, + { 0.0, 1.0 / 2.0 }, + { 0.0, 0.0, 1.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Runge-Kutta integrator with the given + * step. + * @param step integration step + */ + public ClassicalRungeKuttaIntegrator(final double step) { + super("classical Runge-Kutta", STATIC_C, STATIC_A, STATIC_B, + new ClassicalRungeKuttaStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java new file mode 100644 index 000000000..89aa17656 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for the classical fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/6) [ (6 - 9 θ + 4 θ2) y'1 + * + ( 6 θ - 4 θ2) (y'2 + y'3) + * + ( -3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + * + (1 - θ) (h/6) [ (-4 θ^2 + 5 θ - 1) y'1 + * +(4 θ^2 - 2 θ - 2) (y'2 + y'3) + * -(4 θ^2 + θ + 1) y'4 + * ] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 to y'4 are the four + * evaluations of the derivatives already computed during the + * step.

    + * + * @see ClassicalRungeKuttaIntegrator + * @since 1.2 + */ + +class ClassicalRungeKuttaStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link RungeKuttaStepInterpolator#reinitialize} method should be + * called before using the instance in order to initialize the + * internal arrays. This constructor is used only in order to delay + * the initialization in some cases. The {@link RungeKuttaIntegrator} + * class uses the prototyping design pattern to create the step + * interpolators by cloning an uninitialized model and latter initializing + * the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public ClassicalRungeKuttaStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + ClassicalRungeKuttaStepInterpolator(final ClassicalRungeKuttaStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new ClassicalRungeKuttaStepInterpolator(this); + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double oneMinusTheta = 1 - theta; + final double oneMinus2Theta = 1 - 2 * theta; + final double coeffDot1 = oneMinusTheta * oneMinus2Theta; + final double coeffDot23 = 2 * theta * oneMinusTheta; + final double coeffDot4 = -theta * oneMinus2Theta; + if ((previousState != null) && (theta <= 0.5)) { + final double fourTheta2 = 4 * theta * theta; + final double s = theta * h / 6.0; + final double coeff1 = s * ( 6 - 9 * theta + fourTheta2); + final double coeff23 = s * ( 6 * theta - fourTheta2); + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot23 = yDotK[1][i] + yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff23 * yDot23 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4; + } + } else { + final double fourTheta = 4 * theta; + final double s = oneMinusThetaH / 6.0; + final double coeff1 = s * ((-fourTheta + 5) * theta - 1); + final double coeff23 = s * (( fourTheta - 2) * theta - 2); + final double coeff4 = s * ((-fourTheta - 1) * theta - 1); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot23 = yDotK[1][i] + yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] + coeff1 * yDot1 + coeff23 * yDot23 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java new file mode 100644 index 000000000..5ae92d30e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements the 5(4) Dormand-Prince integrator for Ordinary + * Differential Equations. + + *

    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 fsal, 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.

    + * + *

    This method has been published (whithout the continuous output + * that was added by Shampine in 1986) in the following article : + *

    + *  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
    + * 

    + * + * @param the type of the field elements + * @since 3.6 + */ + +public class DormandPrince54FieldIntegrator> + extends EmbeddedRungeKuttaFieldIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 5(4)"; + + /** Error array, element 1. */ + private final T e1; + + // element 2 is zero, so it is neither stored nor used + + /** Error array, element 3. */ + private final T e3; + + /** Error array, element 4. */ + private final T e4; + + /** Error array, element 5. */ + private final T e5; + + /** Error array, element 6. */ + private final T e6; + + /** Error array, element 7. */ + private final T e7; + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince54FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, 6, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e1 = fraction( 71, 57600); + e3 = fraction( -71, 16695); + e4 = fraction( 71, 1920); + e5 = fraction(-17253, 339200); + e6 = fraction( 22, 525); + e7 = fraction( -1, 40); + } + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince54FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, 6, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e1 = fraction( 71, 57600); + e3 = fraction( -71, 16695); + e4 = fraction( 71, 1920); + e5 = fraction(-17253, 339200); + e6 = fraction( 22, 525); + e7 = fraction( -1, 40); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = fraction(1, 5); + c[1] = fraction(3, 10); + c[2] = fraction(4, 5); + c[3] = fraction(8, 9); + c[4] = getField().getOne(); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction( 1, 5); + a[1][0] = fraction( 3, 40); + a[1][1] = fraction( 9, 40); + a[2][0] = fraction( 44, 45); + a[2][1] = fraction( -56, 15); + a[2][2] = fraction( 32, 9); + a[3][0] = fraction( 19372, 6561); + a[3][1] = fraction(-25360, 2187); + a[3][2] = fraction( 64448, 6561); + a[3][3] = fraction( -212, 729); + a[4][0] = fraction( 9017, 3168); + a[4][1] = fraction( -355, 33); + a[4][2] = fraction( 46732, 5247); + a[4][3] = fraction( 49, 176); + a[4][4] = fraction( -5103, 18656); + a[5][0] = fraction( 35, 384); + a[5][1] = getField().getZero(); + a[5][2] = fraction( 500, 1113); + a[5][3] = fraction( 125, 192); + a[5][4] = fraction( -2187, 6784); + a[5][5] = fraction( 11, 84); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 35, 384); + b[1] = getField().getZero(); + b[2] = fraction( 500, 1113); + b[3] = fraction( 125, 192); + b[4] = fraction(-2187, 6784); + b[5] = fraction( 11, 84); + b[6] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince54FieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, final FieldEquationsMapper mapper) { + return new DormandPrince54FieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + + T error = getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + final T errSum = yDotK[0][j].multiply(e1). + add(yDotK[2][j].multiply(e3)). + add(yDotK[3][j].multiply(e4)). + add(yDotK[4][j].multiply(e5)). + add(yDotK[5][j].multiply(e6)). + add(yDotK[6][j].multiply(e7)); + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio = h.multiply(errSum).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java new file mode 100644 index 000000000..d4693caa1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Dormand-Prince integrator. + * + * @see DormandPrince54Integrator + * + * @param the type of the field elements + * @since 3.6 + */ + +class DormandPrince54FieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Last row of the Butcher-array internal weights, element 0. */ + private final T a70; + + // element 1 is zero, so it is neither stored nor used + + /** Last row of the Butcher-array internal weights, element 2. */ + private final T a72; + + /** Last row of the Butcher-array internal weights, element 3. */ + private final T a73; + + /** Last row of the Butcher-array internal weights, element 4. */ + private final T a74; + + /** Last row of the Butcher-array internal weights, element 5. */ + private final T a75; + + /** Shampine (1986) Dense output, element 0. */ + private final T d0; + + // element 1 is zero, so it is neither stored nor used + + /** Shampine (1986) Dense output, element 2. */ + private final T d2; + + /** Shampine (1986) Dense output, element 3. */ + private final T d3; + + /** Shampine (1986) Dense output, element 4. */ + private final T d4; + + /** Shampine (1986) Dense output, element 5. */ + private final T d5; + + /** Shampine (1986) Dense output, element 6. */ + private final T d6; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + DormandPrince54FieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T one = field.getOne(); + a70 = one.multiply( 35.0).divide( 384.0); + a72 = one.multiply( 500.0).divide(1113.0); + a73 = one.multiply( 125.0).divide( 192.0); + a74 = one.multiply(-2187.0).divide(6784.0); + a75 = one.multiply( 11.0).divide( 84.0); + d0 = one.multiply(-12715105075.0).divide( 11282082432.0); + d2 = one.multiply( 87487479700.0).divide( 32700410799.0); + d3 = one.multiply(-10690763975.0).divide( 1880347072.0); + d4 = one.multiply(701980252875.0).divide(199316789632.0); + d5 = one.multiply( -1453857185.0).divide( 822651844.0); + d6 = one.multiply( 69997945.0).divide( 29380423.0); + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince54FieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new DormandPrince54FieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + // interpolate + final T one = time.getField().getOne(); + final T eta = one.subtract(theta); + final T twoTheta = theta.multiply(2); + final T dot2 = one.subtract(twoTheta); + final T dot3 = theta.multiply(theta.multiply(-3).add(2)); + final T dot4 = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T f1 = thetaH; + final T f2 = f1.multiply(eta); + final T f3 = f2.multiply(theta); + final T f4 = f3.multiply(eta); + final T coeff0 = f1.multiply(a70). + subtract(f2.multiply(a70.subtract(1))). + add(f3.multiply(a70.multiply(2).subtract(1))). + add(f4.multiply(d0)); + final T coeff1 = time.getField().getZero(); + final T coeff2 = f1.multiply(a72). + subtract(f2.multiply(a72)). + add(f3.multiply(a72.multiply(2))). + add(f4.multiply(d2)); + final T coeff3 = f1.multiply(a73). + subtract(f2.multiply(a73)). + add(f3.multiply(a73.multiply(2))). + add(f4.multiply(d3)); + final T coeff4 = f1.multiply(a74). + subtract(f2.multiply(a74)). + add(f3.multiply(a74.multiply(2))). + add(f4.multiply(d4)); + final T coeff5 = f1.multiply(a75). + subtract(f2.multiply(a75)). + add(f3.multiply(a75.multiply(2))). + add(f4.multiply(d5)); + final T coeff6 = f4.multiply(d6).subtract(f3); + final T coeffDot0 = a70. + subtract(dot2.multiply(a70.subtract(1))). + add(dot3.multiply(a70.multiply(2).subtract(1))). + add(dot4.multiply(d0)); + final T coeffDot1 = time.getField().getZero(); + final T coeffDot2 = a72. + subtract(dot2.multiply(a72)). + add(dot3.multiply(a72.multiply(2))). + add(dot4.multiply(d2)); + final T coeffDot3 = a73. + subtract(dot2.multiply(a73)). + add(dot3.multiply(a73.multiply(2))). + add(dot4.multiply(d3)); + final T coeffDot4 = a74. + subtract(dot2.multiply(a74)). + add(dot3.multiply(a74.multiply(2))). + add(dot4.multiply(d4)); + final T coeffDot5 = a75. + subtract(dot2.multiply(a75)). + add(dot3.multiply(a75.multiply(2))). + add(dot4.multiply(d5)); + final T coeffDot6 = dot4.multiply(d6).subtract(dot3); + interpolatedState = previousStateLinearCombination(coeff0, coeff1, coeff2, coeff3, + coeff4, coeff5, coeff6); + interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3, + coeffDot4, coeffDot5, coeffDot6); + } else { + final T f1 = oneMinusThetaH.negate(); + final T f2 = oneMinusThetaH.multiply(theta); + final T f3 = f2.multiply(theta); + final T f4 = f3.multiply(eta); + final T coeff0 = f1.multiply(a70). + subtract(f2.multiply(a70.subtract(1))). + add(f3.multiply(a70.multiply(2).subtract(1))). + add(f4.multiply(d0)); + final T coeff1 = time.getField().getZero(); + final T coeff2 = f1.multiply(a72). + subtract(f2.multiply(a72)). + add(f3.multiply(a72.multiply(2))). + add(f4.multiply(d2)); + final T coeff3 = f1.multiply(a73). + subtract(f2.multiply(a73)). + add(f3.multiply(a73.multiply(2))). + add(f4.multiply(d3)); + final T coeff4 = f1.multiply(a74). + subtract(f2.multiply(a74)). + add(f3.multiply(a74.multiply(2))). + add(f4.multiply(d4)); + final T coeff5 = f1.multiply(a75). + subtract(f2.multiply(a75)). + add(f3.multiply(a75.multiply(2))). + add(f4.multiply(d5)); + final T coeff6 = f4.multiply(d6).subtract(f3); + final T coeffDot0 = a70. + subtract(dot2.multiply(a70.subtract(1))). + add(dot3.multiply(a70.multiply(2).subtract(1))). + add(dot4.multiply(d0)); + final T coeffDot1 = time.getField().getZero(); + final T coeffDot2 = a72. + subtract(dot2.multiply(a72)). + add(dot3.multiply(a72.multiply(2))). + add(dot4.multiply(d2)); + final T coeffDot3 = a73. + subtract(dot2.multiply(a73)). + add(dot3.multiply(a73.multiply(2))). + add(dot4.multiply(d3)); + final T coeffDot4 = a74. + subtract(dot2.multiply(a74)). + add(dot3.multiply(a74.multiply(2))). + add(dot4.multiply(d4)); + final T coeffDot5 = a75. + subtract(dot2.multiply(a75)). + add(dot3.multiply(a75.multiply(2))). + add(dot4.multiply(d5)); + final T coeffDot6 = dot4.multiply(d6).subtract(dot3); + interpolatedState = currentStateLinearCombination(coeff0, coeff1, coeff2, coeff3, + coeff4, coeff5, coeff6); + interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3, + coeffDot4, coeffDot5, coeffDot6); + } + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java new file mode 100644 index 000000000..257bcadd2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 5(4) Dormand-Prince integrator for Ordinary + * Differential Equations. + + *

    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 fsal, 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.

    + * + *

    This method has been published (whithout the continuous output + * that was added by Shampine in 1986) in the following article : + *

    + *  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
    + * 

    + * + * @since 1.2 + */ + +public class DormandPrince54Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 5(4)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0/5.0, 3.0/10.0, 4.0/5.0, 8.0/9.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + {1.0/5.0}, + {3.0/40.0, 9.0/40.0}, + {44.0/45.0, -56.0/15.0, 32.0/9.0}, + {19372.0/6561.0, -25360.0/2187.0, 64448.0/6561.0, -212.0/729.0}, + {9017.0/3168.0, -355.0/33.0, 46732.0/5247.0, 49.0/176.0, -5103.0/18656.0}, + {35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0} + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0, 0.0 + }; + + /** Error array, element 1. */ + private static final double E1 = 71.0 / 57600.0; + + // element 2 is zero, so it is neither stored nor used + + /** Error array, element 3. */ + private static final double E3 = -71.0 / 16695.0; + + /** Error array, element 4. */ + private static final double E4 = 71.0 / 1920.0; + + /** Error array, element 5. */ + private static final double E5 = -17253.0 / 339200.0; + + /** Error array, element 6. */ + private static final double E6 = 22.0 / 525.0; + + /** Error array, element 7. */ + private static final double E7 = -1.0 / 40.0; + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + + double error = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + final double errSum = E1 * yDotK[0][j] + E3 * yDotK[2][j] + + E4 * yDotK[3][j] + E5 * yDotK[4][j] + + E6 * yDotK[5][j] + E7 * yDotK[6][j]; + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java new file mode 100644 index 000000000..059dd3c36 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.AbstractIntegrator; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Dormand-Prince integrator. + * + * @see DormandPrince54Integrator + * + * @since 1.2 + */ + +class DormandPrince54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Last row of the Butcher-array internal weights, element 0. */ + private static final double A70 = 35.0 / 384.0; + + // element 1 is zero, so it is neither stored nor used + + /** Last row of the Butcher-array internal weights, element 2. */ + private static final double A72 = 500.0 / 1113.0; + + /** Last row of the Butcher-array internal weights, element 3. */ + private static final double A73 = 125.0 / 192.0; + + /** Last row of the Butcher-array internal weights, element 4. */ + private static final double A74 = -2187.0 / 6784.0; + + /** Last row of the Butcher-array internal weights, element 5. */ + private static final double A75 = 11.0 / 84.0; + + /** Shampine (1986) Dense output, element 0. */ + private static final double D0 = -12715105075.0 / 11282082432.0; + + // element 1 is zero, so it is neither stored nor used + + /** Shampine (1986) Dense output, element 2. */ + private static final double D2 = 87487479700.0 / 32700410799.0; + + /** Shampine (1986) Dense output, element 3. */ + private static final double D3 = -10690763975.0 / 1880347072.0; + + /** Shampine (1986) Dense output, element 4. */ + private static final double D4 = 701980252875.0 / 199316789632.0; + + /** Shampine (1986) Dense output, element 5. */ + private static final double D5 = -1453857185.0 / 822651844.0; + + /** Shampine (1986) Dense output, element 6. */ + private static final double D6 = 69997945.0 / 29380423.0; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** First vector for interpolation. */ + private double[] v1; + + /** Second vector for interpolation. */ + private double[] v2; + + /** Third vector for interpolation. */ + private double[] v3; + + /** Fourth vector for interpolation. */ + private double[] v4; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public DormandPrince54StepInterpolator() { + super(); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + DormandPrince54StepInterpolator(final DormandPrince54StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.v1 == null) { + + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + + } else { + + v1 = interpolator.v1.clone(); + v2 = interpolator.v2.clone(); + v3 = interpolator.v3.clone(); + v4 = interpolator.v4.clone(); + vectorsInitialized = interpolator.vectorsInitialized; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new DormandPrince54StepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + public void reinitialize(final AbstractIntegrator integrator, + final double[] y, final double[][] yDotK, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + public void storeTime(final double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + if (! vectorsInitialized) { + + if (v1 == null) { + v1 = new double[interpolatedState.length]; + v2 = new double[interpolatedState.length]; + v3 = new double[interpolatedState.length]; + v4 = new double[interpolatedState.length]; + } + + // no step finalization is needed for this interpolator + + // we need to compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + final double yDot6 = yDotK[6][i]; + v1[i] = A70 * yDot0 + A72 * yDot2 + A73 * yDot3 + A74 * yDot4 + A75 * yDot5; + v2[i] = yDot0 - v1[i]; + v3[i] = v1[i] - v2[i] - yDot6; + v4[i] = D0 * yDot0 + D2 * yDot2 + D3 * yDot3 + D4 * yDot4 + D5 * yDot5 + D6 * yDot6; + } + + vectorsInitialized = true; + + } + + // interpolate + final double eta = 1 - theta; + final double twoTheta = 2 * theta; + final double dot2 = 1 - twoTheta; + final double dot3 = theta * (2 - 3 * theta); + final double dot4 = twoTheta * (1 + theta * (twoTheta - 3)); + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = + previousState[i] + theta * h * (v1[i] + eta * (v2[i] + theta * (v3[i] + eta * v4[i]))); + interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i]; + } + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = + currentState[i] - oneMinusThetaH * (v1[i] - theta * (v2[i] + theta * (v3[i] + eta * v4[i]))); + interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i]; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java new file mode 100644 index 000000000..22e156484 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary + * Differential Equations. + * + *

    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 + * fsal. 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.

    + * + *

    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 severe difficulties when + * applied to problems with discontinuities. This modification is + * explained in the second edition of the first volume (Nonstiff + * Problems) of the reference book by Hairer, Norsett and Wanner: + * Solving Ordinary Differential Equations (Springer-Verlag, + * ISBN 3-540-56670-8).

    + * + * @param the type of the field elements + * @since 3.6 + */ + +public class DormandPrince853FieldIntegrator> + extends EmbeddedRungeKuttaFieldIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)"; + + /** First error weights array, element 1. */ + private final T e1_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** First error weights array, element 6. */ + private final T e1_06; + + /** First error weights array, element 7. */ + private final T e1_07; + + /** First error weights array, element 8. */ + private final T e1_08; + + /** First error weights array, element 9. */ + private final T e1_09; + + /** First error weights array, element 10. */ + private final T e1_10; + + /** First error weights array, element 11. */ + private final T e1_11; + + /** First error weights array, element 12. */ + private final T e1_12; + + + /** Second error weights array, element 1. */ + private final T e2_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Second error weights array, element 6. */ + private final T e2_06; + + /** Second error weights array, element 7. */ + private final T e2_07; + + /** Second error weights array, element 8. */ + private final T e2_08; + + /** Second error weights array, element 9. */ + private final T e2_09; + + /** Second error weights array, element 10. */ + private final T e2_10; + + /** Second error weights array, element 11. */ + private final T e2_11; + + /** Second error weights array, element 12. */ + private final T e2_12; + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince853FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, 12, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e1_01 = fraction( 116092271.0, 8848465920.0); + e1_06 = fraction( -1871647.0, 1527680.0); + e1_07 = fraction( -69799717.0, 140793660.0); + e1_08 = fraction( 1230164450203.0, 739113984000.0); + e1_09 = fraction(-1980813971228885.0, 5654156025964544.0); + e1_10 = fraction( 464500805.0, 1389975552.0); + e1_11 = fraction( 1606764981773.0, 19613062656000.0); + e1_12 = fraction( -137909.0, 6168960.0); + e2_01 = fraction( -364463.0, 1920240.0); + e2_06 = fraction( 3399327.0, 763840.0); + e2_07 = fraction( 66578432.0, 35198415.0); + e2_08 = fraction( -1674902723.0, 288716400.0); + e2_09 = fraction( -74684743568175.0, 176692375811392.0); + e2_10 = fraction( -734375.0, 4826304.0); + e2_11 = fraction( 171414593.0, 851261400.0); + e2_12 = fraction( 69869.0, 3084480.0); + } + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince853FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, 12, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e1_01 = fraction( 116092271.0, 8848465920.0); + e1_06 = fraction( -1871647.0, 1527680.0); + e1_07 = fraction( -69799717.0, 140793660.0); + e1_08 = fraction( 1230164450203.0, 739113984000.0); + e1_09 = fraction(-1980813971228885.0, 5654156025964544.0); + e1_10 = fraction( 464500805.0, 1389975552.0); + e1_11 = fraction( 1606764981773.0, 19613062656000.0); + e1_12 = fraction( -137909.0, 6168960.0); + e2_01 = fraction( -364463.0, 1920240.0); + e2_06 = fraction( 3399327.0, 763840.0); + e2_07 = fraction( 66578432.0, 35198415.0); + e2_08 = fraction( -1674902723.0, 288716400.0); + e2_09 = fraction( -74684743568175.0, 176692375811392.0); + e2_10 = fraction( -734375.0, 4826304.0); + e2_11 = fraction( 171414593.0, 851261400.0); + e2_12 = fraction( 69869.0, 3084480.0); + } + + /** {@inheritDoc} */ + public T[] getC() { + + final T sqrt6 = getField().getOne().multiply(6).sqrt(); + + final T[] c = MathArrays.buildArray(getField(), 15); + c[ 0] = sqrt6.add(-6).divide(-67.5); + c[ 1] = sqrt6.add(-6).divide(-45.0); + c[ 2] = sqrt6.add(-6).divide(-30.0); + c[ 3] = sqrt6.add( 6).divide( 30.0); + c[ 4] = fraction(1, 3); + c[ 5] = fraction(1, 4); + c[ 6] = fraction(4, 13); + c[ 7] = fraction(127, 195); + c[ 8] = fraction(3, 5); + c[ 9] = fraction(6, 7); + c[10] = getField().getOne(); + c[11] = getField().getOne(); + c[12] = fraction(1.0, 10.0); + c[13] = fraction(1.0, 5.0); + c[14] = fraction(7.0, 9.0); + + return c; + + } + + /** {@inheritDoc} */ + public T[][] getA() { + + final T sqrt6 = getField().getOne().multiply(6).sqrt(); + + final T[][] a = MathArrays.buildArray(getField(), 15, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + + a[ 0][ 0] = sqrt6.add(-6).divide(-67.5); + + a[ 1][ 0] = sqrt6.add(-6).divide(-180); + a[ 1][ 1] = sqrt6.add(-6).divide( -60); + + a[ 2][ 0] = sqrt6.add(-6).divide(-120); + a[ 2][ 1] = getField().getZero(); + a[ 2][ 2] = sqrt6.add(-6).divide( -40); + + a[ 3][ 0] = sqrt6.multiply(107).add(462).divide( 3000); + a[ 3][ 1] = getField().getZero(); + a[ 3][ 2] = sqrt6.multiply(197).add(402).divide(-1000); + a[ 3][ 3] = sqrt6.multiply( 73).add(168).divide( 375); + + a[ 4][ 0] = fraction(1, 27); + a[ 4][ 1] = getField().getZero(); + a[ 4][ 2] = getField().getZero(); + a[ 4][ 3] = sqrt6.add( 16).divide( 108); + a[ 4][ 4] = sqrt6.add(-16).divide(-108); + + a[ 5][ 0] = fraction(19, 512); + a[ 5][ 1] = getField().getZero(); + a[ 5][ 2] = getField().getZero(); + a[ 5][ 3] = sqrt6.multiply( 23).add(118).divide(1024); + a[ 5][ 4] = sqrt6.multiply(-23).add(118).divide(1024); + a[ 5][ 5] = fraction(-9, 512); + + a[ 6][ 0] = fraction(13772, 371293); + a[ 6][ 1] = getField().getZero(); + a[ 6][ 2] = getField().getZero(); + a[ 6][ 3] = sqrt6.multiply( 4784).add(51544).divide(371293); + a[ 6][ 4] = sqrt6.multiply(-4784).add(51544).divide(371293); + a[ 6][ 5] = fraction(-5688, 371293); + a[ 6][ 6] = fraction( 3072, 371293); + + a[ 7][ 0] = fraction(58656157643.0, 93983540625.0); + a[ 7][ 1] = getField().getZero(); + a[ 7][ 2] = getField().getZero(); + a[ 7][ 3] = sqrt6.multiply(-318801444819.0).add(-1324889724104.0).divide(626556937500.0); + a[ 7][ 4] = sqrt6.multiply( 318801444819.0).add(-1324889724104.0).divide(626556937500.0); + a[ 7][ 5] = fraction(96044563816.0, 3480871875.0); + a[ 7][ 6] = fraction(5682451879168.0, 281950621875.0); + a[ 7][ 7] = fraction(-165125654.0, 3796875.0); + + a[ 8][ 0] = fraction(8909899.0, 18653125.0); + a[ 8][ 1] = getField().getZero(); + a[ 8][ 2] = getField().getZero(); + a[ 8][ 3] = sqrt6.multiply(-1137963.0).add(-4521408.0).divide(2937500.0); + a[ 8][ 4] = sqrt6.multiply( 1137963.0).add(-4521408.0).divide(2937500.0); + a[ 8][ 5] = fraction(96663078.0, 4553125.0); + a[ 8][ 6] = fraction(2107245056.0, 137915625.0); + a[ 8][ 7] = fraction(-4913652016.0, 147609375.0); + a[ 8][ 8] = fraction(-78894270.0, 3880452869.0); + + a[ 9][ 0] = fraction(-20401265806.0, 21769653311.0); + a[ 9][ 1] = getField().getZero(); + a[ 9][ 2] = getField().getZero(); + a[ 9][ 3] = sqrt6.multiply( 94326.0).add(354216.0).divide(112847.0); + a[ 9][ 4] = sqrt6.multiply(-94326.0).add(354216.0).divide(112847.0); + a[ 9][ 5] = fraction(-43306765128.0, 5313852383.0); + a[ 9][ 6] = fraction(-20866708358144.0, 1126708119789.0); + a[ 9][ 7] = fraction(14886003438020.0, 654632330667.0); + a[ 9][ 8] = fraction(35290686222309375.0, 14152473387134411.0); + a[ 9][ 9] = fraction(-1477884375.0, 485066827.0); + + a[10][ 0] = fraction(39815761.0, 17514443.0); + a[10][ 1] = getField().getZero(); + a[10][ 2] = getField().getZero(); + a[10][ 3] = sqrt6.multiply(-960905.0).add(-3457480.0).divide(551636.0); + a[10][ 4] = sqrt6.multiply( 960905.0).add(-3457480.0).divide(551636.0); + a[10][ 5] = fraction(-844554132.0, 47026969.0); + a[10][ 6] = fraction(8444996352.0, 302158619.0); + a[10][ 7] = fraction(-2509602342.0, 877790785.0); + a[10][ 8] = fraction(-28388795297996250.0, 3199510091356783.0); + a[10][ 9] = fraction(226716250.0, 18341897.0); + a[10][10] = fraction(1371316744.0, 2131383595.0); + + // the following stage is both for interpolation and the first stage in next step + // (the coefficients are identical to the B array) + a[11][ 0] = fraction(104257.0, 1920240.0); + a[11][ 1] = getField().getZero(); + a[11][ 2] = getField().getZero(); + a[11][ 3] = getField().getZero(); + a[11][ 4] = getField().getZero(); + a[11][ 5] = fraction(3399327.0, 763840.0); + a[11][ 6] = fraction(66578432.0, 35198415.0); + a[11][ 7] = fraction(-1674902723.0, 288716400.0); + a[11][ 8] = fraction(54980371265625.0, 176692375811392.0); + a[11][ 9] = fraction(-734375.0, 4826304.0); + a[11][10] = fraction(171414593.0, 851261400.0); + a[11][11] = fraction(137909.0, 3084480.0); + + // the following stages are for interpolation only + a[12][ 0] = fraction( 13481885573.0, 240030000000.0); + a[12][ 1] = getField().getZero(); + a[12][ 2] = getField().getZero(); + a[12][ 3] = getField().getZero(); + a[12][ 4] = getField().getZero(); + a[12][ 5] = getField().getZero(); + a[12][ 6] = fraction( 139418837528.0, 549975234375.0); + a[12][ 7] = fraction( -11108320068443.0, 45111937500000.0); + a[12][ 8] = fraction(-1769651421925959.0, 14249385146080000.0); + a[12][ 9] = fraction( 57799439.0, 377055000.0); + a[12][10] = fraction( 793322643029.0, 96734250000000.0); + a[12][11] = fraction( 1458939311.0, 192780000000.0); + a[12][12] = fraction( -4149.0, 500000.0); + + a[13][ 0] = fraction( 1595561272731.0, 50120273500000.0); + a[13][ 1] = getField().getZero(); + a[13][ 2] = getField().getZero(); + a[13][ 3] = getField().getZero(); + a[13][ 4] = getField().getZero(); + a[13][ 5] = fraction( 975183916491.0, 34457688031250.0); + a[13][ 6] = fraction( 38492013932672.0, 718912673015625.0); + a[13][ 7] = fraction(-1114881286517557.0, 20298710767500000.0); + a[13][ 8] = getField().getZero(); + a[13][ 9] = getField().getZero(); + a[13][10] = fraction( -2538710946863.0, 23431227861250000.0); + a[13][11] = fraction( 8824659001.0, 23066716781250.0); + a[13][12] = fraction( -11518334563.0, 33831184612500.0); + a[13][13] = fraction( 1912306948.0, 13532473845.0); + + a[14][ 0] = fraction( -13613986967.0, 31741908048.0); + a[14][ 1] = getField().getZero(); + a[14][ 2] = getField().getZero(); + a[14][ 3] = getField().getZero(); + a[14][ 4] = getField().getZero(); + a[14][ 5] = fraction( -4755612631.0, 1012344804.0); + a[14][ 6] = fraction( 42939257944576.0, 5588559685701.0); + a[14][ 7] = fraction( 77881972900277.0, 19140370552944.0); + a[14][ 8] = fraction( 22719829234375.0, 63689648654052.0); + a[14][ 9] = getField().getZero(); + a[14][10] = getField().getZero(); + a[14][11] = getField().getZero(); + a[14][12] = fraction( -1199007803.0, 857031517296.0); + a[14][13] = fraction( 157882067000.0, 53564469831.0); + a[14][14] = fraction( -290468882375.0, 31741908048.0); + + return a; + + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 16); + b[ 0] = fraction(104257, 1920240); + b[ 1] = getField().getZero(); + b[ 2] = getField().getZero(); + b[ 3] = getField().getZero(); + b[ 4] = getField().getZero(); + b[ 5] = fraction( 3399327.0, 763840.0); + b[ 6] = fraction( 66578432.0, 35198415.0); + b[ 7] = fraction( -1674902723.0, 288716400.0); + b[ 8] = fraction( 54980371265625.0, 176692375811392.0); + b[ 9] = fraction( -734375.0, 4826304.0); + b[10] = fraction( 171414593.0, 851261400.0); + b[11] = fraction( 137909.0, 3084480.0); + b[12] = getField().getZero(); + b[13] = getField().getZero(); + b[14] = getField().getZero(); + b[15] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince853FieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, final FieldEquationsMapper mapper) { + return new DormandPrince853FieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 8; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + T error1 = h.getField().getZero(); + T error2 = h.getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + final T errSum1 = yDotK[ 0][j].multiply(e1_01). + add(yDotK[ 5][j].multiply(e1_06)). + add(yDotK[ 6][j].multiply(e1_07)). + add(yDotK[ 7][j].multiply(e1_08)). + add(yDotK[ 8][j].multiply(e1_09)). + add(yDotK[ 9][j].multiply(e1_10)). + add(yDotK[10][j].multiply(e1_11)). + add(yDotK[11][j].multiply(e1_12)); + final T errSum2 = yDotK[ 0][j].multiply(e2_01). + add(yDotK[ 5][j].multiply(e2_06)). + add(yDotK[ 6][j].multiply(e2_07)). + add(yDotK[ 7][j].multiply(e2_08)). + add(yDotK[ 8][j].multiply(e2_09)). + add(yDotK[ 9][j].multiply(e2_10)). + add(yDotK[10][j].multiply(e2_11)). + add(yDotK[11][j].multiply(e2_12)); + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = vecAbsoluteTolerance == null ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio1 = errSum1.divide(tol); + error1 = error1.add(ratio1.multiply(ratio1)); + final T ratio2 = errSum2.divide(tol); + error2 = error2.add(ratio2.multiply(ratio2)); + } + + T den = error1.add(error2.multiply(0.01)); + if (den.getReal() <= 0.0) { + den = h.getField().getOne(); + } + + return h.abs().multiply(error1).divide(den.multiply(mainSetDimension).sqrt()); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java new file mode 100644 index 000000000..c30d00811 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 8(5,3) Dormand-Prince integrator. + * + * @see DormandPrince853FieldIntegrator + * + * @param the type of the field elements + * @since 3.6 + */ + +class DormandPrince853FieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Interpolation weights. + * (beware that only the non-null values are in the table) + */ + private final T[][] d; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + DormandPrince853FieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + // interpolation weights + d = MathArrays.buildArray(field, 7, 16); + + // this row is the same as the b array + d[0][ 0] = fraction(field, 104257, 1920240); + d[0][ 1] = field.getZero(); + d[0][ 2] = field.getZero(); + d[0][ 3] = field.getZero(); + d[0][ 4] = field.getZero(); + d[0][ 5] = fraction(field, 3399327.0, 763840.0); + d[0][ 6] = fraction(field, 66578432.0, 35198415.0); + d[0][ 7] = fraction(field, -1674902723.0, 288716400.0); + d[0][ 8] = fraction(field, 54980371265625.0, 176692375811392.0); + d[0][ 9] = fraction(field, -734375.0, 4826304.0); + d[0][10] = fraction(field, 171414593.0, 851261400.0); + d[0][11] = fraction(field, 137909.0, 3084480.0); + d[0][12] = field.getZero(); + d[0][13] = field.getZero(); + d[0][14] = field.getZero(); + d[0][15] = field.getZero(); + + d[1][ 0] = d[0][ 0].negate().add(1); + d[1][ 1] = d[0][ 1].negate(); + d[1][ 2] = d[0][ 2].negate(); + d[1][ 3] = d[0][ 3].negate(); + d[1][ 4] = d[0][ 4].negate(); + d[1][ 5] = d[0][ 5].negate(); + d[1][ 6] = d[0][ 6].negate(); + d[1][ 7] = d[0][ 7].negate(); + d[1][ 8] = d[0][ 8].negate(); + d[1][ 9] = d[0][ 9].negate(); + d[1][10] = d[0][10].negate(); + d[1][11] = d[0][11].negate(); + d[1][12] = d[0][12].negate(); // really 0 + d[1][13] = d[0][13].negate(); // really 0 + d[1][14] = d[0][14].negate(); // really 0 + d[1][15] = d[0][15].negate(); // really 0 + + d[2][ 0] = d[0][ 0].multiply(2).subtract(1); + d[2][ 1] = d[0][ 1].multiply(2); + d[2][ 2] = d[0][ 2].multiply(2); + d[2][ 3] = d[0][ 3].multiply(2); + d[2][ 4] = d[0][ 4].multiply(2); + d[2][ 5] = d[0][ 5].multiply(2); + d[2][ 6] = d[0][ 6].multiply(2); + d[2][ 7] = d[0][ 7].multiply(2); + d[2][ 8] = d[0][ 8].multiply(2); + d[2][ 9] = d[0][ 9].multiply(2); + d[2][10] = d[0][10].multiply(2); + d[2][11] = d[0][11].multiply(2); + d[2][12] = d[0][12].multiply(2).subtract(1); // really -1 + d[2][13] = d[0][13].multiply(2); // really 0 + d[2][14] = d[0][14].multiply(2); // really 0 + d[2][15] = d[0][15].multiply(2); // really 0 + + d[3][ 0] = fraction(field, -17751989329.0, 2106076560.0); + d[3][ 1] = field.getZero(); + d[3][ 2] = field.getZero(); + d[3][ 3] = field.getZero(); + d[3][ 4] = field.getZero(); + d[3][ 5] = fraction(field, 4272954039.0, 7539864640.0); + d[3][ 6] = fraction(field, -118476319744.0, 38604839385.0); + d[3][ 7] = fraction(field, 755123450731.0, 316657731600.0); + d[3][ 8] = fraction(field, 3692384461234828125.0, 1744130441634250432.0); + d[3][ 9] = fraction(field, -4612609375.0, 5293382976.0); + d[3][10] = fraction(field, 2091772278379.0, 933644586600.0); + d[3][11] = fraction(field, 2136624137.0, 3382989120.0); + d[3][12] = fraction(field, -126493.0, 1421424.0); + d[3][13] = fraction(field, 98350000.0, 5419179.0); + d[3][14] = fraction(field, -18878125.0, 2053168.0); + d[3][15] = fraction(field, -1944542619.0, 438351368.0); + + d[4][ 0] = fraction(field, 32941697297.0, 3159114840.0); + d[4][ 1] = field.getZero(); + d[4][ 2] = field.getZero(); + d[4][ 3] = field.getZero(); + d[4][ 4] = field.getZero(); + d[4][ 5] = fraction(field, 456696183123.0, 1884966160.0); + d[4][ 6] = fraction(field, 19132610714624.0, 115814518155.0); + d[4][ 7] = fraction(field, -177904688592943.0, 474986597400.0); + d[4][ 8] = fraction(field, -4821139941836765625.0, 218016305204281304.0); + d[4][ 9] = fraction(field, 30702015625.0, 3970037232.0); + d[4][10] = fraction(field, -85916079474274.0, 2800933759800.0); + d[4][11] = fraction(field, -5919468007.0, 634310460.0); + d[4][12] = fraction(field, 2479159.0, 157936.0); + d[4][13] = fraction(field, -18750000.0, 602131.0); + d[4][14] = fraction(field, -19203125.0, 2053168.0); + d[4][15] = fraction(field, 15700361463.0, 438351368.0); + + d[5][ 0] = fraction(field, 12627015655.0, 631822968.0); + d[5][ 1] = field.getZero(); + d[5][ 2] = field.getZero(); + d[5][ 3] = field.getZero(); + d[5][ 4] = field.getZero(); + d[5][ 5] = fraction(field, -72955222965.0, 188496616.0); + d[5][ 6] = fraction(field, -13145744952320.0, 69488710893.0); + d[5][ 7] = fraction(field, 30084216194513.0, 56998391688.0); + d[5][ 8] = fraction(field, -296858761006640625.0, 25648977082856624.0); + d[5][ 9] = fraction(field, 569140625.0, 82709109.0); + d[5][10] = fraction(field, -18684190637.0, 18672891732.0); + d[5][11] = fraction(field, 69644045.0, 89549712.0); + d[5][12] = fraction(field, -11847025.0, 4264272.0); + d[5][13] = fraction(field, -978650000.0, 16257537.0); + d[5][14] = fraction(field, 519371875.0, 6159504.0); + d[5][15] = fraction(field, 5256837225.0, 438351368.0); + + d[6][ 0] = fraction(field, -450944925.0, 17550638.0); + d[6][ 1] = field.getZero(); + d[6][ 2] = field.getZero(); + d[6][ 3] = field.getZero(); + d[6][ 4] = field.getZero(); + d[6][ 5] = fraction(field, -14532122925.0, 94248308.0); + d[6][ 6] = fraction(field, -595876966400.0, 2573655959.0); + d[6][ 7] = fraction(field, 188748653015.0, 527762886.0); + d[6][ 8] = fraction(field, 2545485458115234375.0, 27252038150535163.0); + d[6][ 9] = fraction(field, -1376953125.0, 36759604.0); + d[6][10] = fraction(field, 53995596795.0, 518691437.0); + d[6][11] = fraction(field, 210311225.0, 7047894.0); + d[6][12] = fraction(field, -1718875.0, 39484.0); + d[6][13] = fraction(field, 58000000.0, 602131.0); + d[6][14] = fraction(field, -1546875.0, 39484.0); + d[6][15] = fraction(field, -1262172375.0, 8429834.0); + + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince853FieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new DormandPrince853FieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** Create a fraction. + * @param field field to which the elements belong + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + private T fraction(final Field field, final double p, final double q) { + return field.getZero().add(p).divide(q); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) + throws MaxCountExceededException { + + final T one = time.getField().getOne(); + final T eta = one.subtract(theta); + final T twoTheta = theta.multiply(2); + final T theta2 = theta.multiply(theta); + final T dot1 = one.subtract(twoTheta); + final T dot2 = theta.multiply(theta.multiply(-3).add(2)); + final T dot3 = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1)); + final T dot4 = theta2.multiply(theta.multiply(theta.multiply(5).subtract(8)).add(3)); + final T dot5 = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(-6).add(15)).subtract(12)).add(3)); + final T dot6 = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-7).add(18)).subtract(15)).add(4))); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T f0 = thetaH; + final T f1 = f0.multiply(eta); + final T f2 = f1.multiply(theta); + final T f3 = f2.multiply(eta); + final T f4 = f3.multiply(theta); + final T f5 = f4.multiply(eta); + final T f6 = f5.multiply(theta); + final T[] p = MathArrays.buildArray(time.getField(), 16); + final T[] q = MathArrays.buildArray(time.getField(), 16); + for (int i = 0; i < p.length; ++i) { + p[i] = f0.multiply(d[0][i]). + add(f1.multiply(d[1][i])). + add(f2.multiply(d[2][i])). + add(f3.multiply(d[3][i])). + add(f4.multiply(d[4][i])). + add(f5.multiply(d[5][i])). + add(f6.multiply(d[6][i])); + q[i] = d[0][i]. + add(dot1.multiply(d[1][i])). + add(dot2.multiply(d[2][i])). + add(dot3.multiply(d[3][i])). + add(dot4.multiply(d[4][i])). + add(dot5.multiply(d[5][i])). + add(dot6.multiply(d[6][i])); + } + interpolatedState = previousStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7], + q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]); + } else { + final T f0 = oneMinusThetaH.negate(); + final T f1 = f0.multiply(theta).negate(); + final T f2 = f1.multiply(theta); + final T f3 = f2.multiply(eta); + final T f4 = f3.multiply(theta); + final T f5 = f4.multiply(eta); + final T f6 = f5.multiply(theta); + final T[] p = MathArrays.buildArray(time.getField(), 16); + final T[] q = MathArrays.buildArray(time.getField(), 16); + for (int i = 0; i < p.length; ++i) { + p[i] = f0.multiply(d[0][i]). + add(f1.multiply(d[1][i])). + add(f2.multiply(d[2][i])). + add(f3.multiply(d[3][i])). + add(f4.multiply(d[4][i])). + add(f5.multiply(d[5][i])). + add(f6.multiply(d[6][i])); + q[i] = d[0][i]. + add(dot1.multiply(d[1][i])). + add(dot2.multiply(d[2][i])). + add(dot3.multiply(d[3][i])). + add(dot4.multiply(d[4][i])). + add(dot5.multiply(d[5][i])). + add(dot6.multiply(d[6][i])); + } + interpolatedState = currentStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7], + q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java new file mode 100644 index 000000000..c547c55b5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary + * Differential Equations. + * + *

    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 + * fsal. 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.

    + * + *

    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 severe difficulties when + * applied to problems with discontinuities. This modification is + * explained in the second edition of the first volume (Nonstiff + * Problems) of the reference book by Hairer, Norsett and Wanner: + * Solving Ordinary Differential Equations (Springer-Verlag, + * ISBN 3-540-56670-8).

    + * + * @since 1.2 + */ + +public class DormandPrince853Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + (12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0, (6.0 - FastMath.sqrt(6.0)) / 45.0, (6.0 - FastMath.sqrt(6.0)) / 30.0, + (6.0 + FastMath.sqrt(6.0)) / 30.0, 1.0/3.0, 1.0/4.0, 4.0/13.0, 127.0/195.0, 3.0/5.0, + 6.0/7.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + + // k2 + {(12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0}, + + // k3 + {(6.0 - FastMath.sqrt(6.0)) / 180.0, (6.0 - FastMath.sqrt(6.0)) / 60.0}, + + // k4 + {(6.0 - FastMath.sqrt(6.0)) / 120.0, 0.0, (6.0 - FastMath.sqrt(6.0)) / 40.0}, + + // k5 + {(462.0 + 107.0 * FastMath.sqrt(6.0)) / 3000.0, 0.0, + (-402.0 - 197.0 * FastMath.sqrt(6.0)) / 1000.0, (168.0 + 73.0 * FastMath.sqrt(6.0)) / 375.0}, + + // k6 + {1.0 / 27.0, 0.0, 0.0, (16.0 + FastMath.sqrt(6.0)) / 108.0, (16.0 - FastMath.sqrt(6.0)) / 108.0}, + + // k7 + {19.0 / 512.0, 0.0, 0.0, (118.0 + 23.0 * FastMath.sqrt(6.0)) / 1024.0, + (118.0 - 23.0 * FastMath.sqrt(6.0)) / 1024.0, -9.0 / 512.0}, + + // k8 + {13772.0 / 371293.0, 0.0, 0.0, (51544.0 + 4784.0 * FastMath.sqrt(6.0)) / 371293.0, + (51544.0 - 4784.0 * FastMath.sqrt(6.0)) / 371293.0, -5688.0 / 371293.0, 3072.0 / 371293.0}, + + // k9 + {58656157643.0 / 93983540625.0, 0.0, 0.0, + (-1324889724104.0 - 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0, + (-1324889724104.0 + 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0, + 96044563816.0 / 3480871875.0, 5682451879168.0 / 281950621875.0, + -165125654.0 / 3796875.0}, + + // k10 + {8909899.0 / 18653125.0, 0.0, 0.0, + (-4521408.0 - 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0, + (-4521408.0 + 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0, + 96663078.0 / 4553125.0, 2107245056.0 / 137915625.0, + -4913652016.0 / 147609375.0, -78894270.0 / 3880452869.0}, + + // k11 + {-20401265806.0 / 21769653311.0, 0.0, 0.0, + (354216.0 + 94326.0 * FastMath.sqrt(6.0)) / 112847.0, + (354216.0 - 94326.0 * FastMath.sqrt(6.0)) / 112847.0, + -43306765128.0 / 5313852383.0, -20866708358144.0 / 1126708119789.0, + 14886003438020.0 / 654632330667.0, 35290686222309375.0 / 14152473387134411.0, + -1477884375.0 / 485066827.0}, + + // k12 + {39815761.0 / 17514443.0, 0.0, 0.0, + (-3457480.0 - 960905.0 * FastMath.sqrt(6.0)) / 551636.0, + (-3457480.0 + 960905.0 * FastMath.sqrt(6.0)) / 551636.0, + -844554132.0 / 47026969.0, 8444996352.0 / 302158619.0, + -2509602342.0 / 877790785.0, -28388795297996250.0 / 3199510091356783.0, + 226716250.0 / 18341897.0, 1371316744.0 / 2131383595.0}, + + // k13 should be for interpolation only, but since it is the same + // stage as the first evaluation of the next step, we perform it + // here at no cost by specifying this is an fsal method + {104257.0/1920240.0, 0.0, 0.0, 0.0, 0.0, 3399327.0/763840.0, + 66578432.0/35198415.0, -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, -734375.0/4826304.0, + 171414593.0/851261400.0, 137909.0/3084480.0} + + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 104257.0/1920240.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3399327.0/763840.0, + 66578432.0/35198415.0, + -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, + -734375.0/4826304.0, + 171414593.0/851261400.0, + 137909.0/3084480.0, + 0.0 + }; + + /** First error weights array, element 1. */ + private static final double E1_01 = 116092271.0 / 8848465920.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** First error weights array, element 6. */ + private static final double E1_06 = -1871647.0 / 1527680.0; + + /** First error weights array, element 7. */ + private static final double E1_07 = -69799717.0 / 140793660.0; + + /** First error weights array, element 8. */ + private static final double E1_08 = 1230164450203.0 / 739113984000.0; + + /** First error weights array, element 9. */ + private static final double E1_09 = -1980813971228885.0 / 5654156025964544.0; + + /** First error weights array, element 10. */ + private static final double E1_10 = 464500805.0 / 1389975552.0; + + /** First error weights array, element 11. */ + private static final double E1_11 = 1606764981773.0 / 19613062656000.0; + + /** First error weights array, element 12. */ + private static final double E1_12 = -137909.0 / 6168960.0; + + + /** Second error weights array, element 1. */ + private static final double E2_01 = -364463.0 / 1920240.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Second error weights array, element 6. */ + private static final double E2_06 = 3399327.0 / 763840.0; + + /** Second error weights array, element 7. */ + private static final double E2_07 = 66578432.0 / 35198415.0; + + /** Second error weights array, element 8. */ + private static final double E2_08 = -1674902723.0 / 288716400.0; + + /** Second error weights array, element 9. */ + private static final double E2_09 = -74684743568175.0 / 176692375811392.0; + + /** Second error weights array, element 10. */ + private static final double E2_10 = -734375.0 / 4826304.0; + + /** Second error weights array, element 11. */ + private static final double E2_11 = 171414593.0 / 851261400.0; + + /** Second error weights array, element 12. */ + private static final double E2_12 = 69869.0 / 3084480.0; + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, + new DormandPrince853StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, + new DormandPrince853StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 8; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + double error1 = 0; + double error2 = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + final double errSum1 = E1_01 * yDotK[0][j] + E1_06 * yDotK[5][j] + + E1_07 * yDotK[6][j] + E1_08 * yDotK[7][j] + + E1_09 * yDotK[8][j] + E1_10 * yDotK[9][j] + + E1_11 * yDotK[10][j] + E1_12 * yDotK[11][j]; + final double errSum2 = E2_01 * yDotK[0][j] + E2_06 * yDotK[5][j] + + E2_07 * yDotK[6][j] + E2_08 * yDotK[7][j] + + E2_09 * yDotK[8][j] + E2_10 * yDotK[9][j] + + E2_11 * yDotK[10][j] + E2_12 * yDotK[11][j]; + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio1 = errSum1 / tol; + error1 += ratio1 * ratio1; + final double ratio2 = errSum2 / tol; + error2 += ratio2 * ratio2; + } + + double den = error1 + 0.01 * error2; + if (den <= 0.0) { + den = 1.0; + } + + return FastMath.abs(h) * error1 / FastMath.sqrt(mainSetDimension * den); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java new file mode 100644 index 000000000..b039132f4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java @@ -0,0 +1,501 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.AbstractIntegrator; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 8(5,3) Dormand-Prince integrator. + * + * @see DormandPrince853Integrator + * + * @since 1.2 + */ + +class DormandPrince853StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Propagation weights, element 1. */ + private static final double B_01 = 104257.0 / 1920240.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Propagation weights, element 6. */ + private static final double B_06 = 3399327.0 / 763840.0; + + /** Propagation weights, element 7. */ + private static final double B_07 = 66578432.0 / 35198415.0; + + /** Propagation weights, element 8. */ + private static final double B_08 = -1674902723.0 / 288716400.0; + + /** Propagation weights, element 9. */ + private static final double B_09 = 54980371265625.0 / 176692375811392.0; + + /** Propagation weights, element 10. */ + private static final double B_10 = -734375.0 / 4826304.0; + + /** Propagation weights, element 11. */ + private static final double B_11 = 171414593.0 / 851261400.0; + + /** Propagation weights, element 12. */ + private static final double B_12 = 137909.0 / 3084480.0; + + /** Time step for stage 14 (interpolation only). */ + private static final double C14 = 1.0 / 10.0; + + /** Internal weights for stage 14, element 1. */ + private static final double K14_01 = 13481885573.0 / 240030000000.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 14, element 6. */ + private static final double K14_06 = 0.0 - B_06; + + /** Internal weights for stage 14, element 7. */ + private static final double K14_07 = 139418837528.0 / 549975234375.0 - B_07; + + /** Internal weights for stage 14, element 8. */ + private static final double K14_08 = -11108320068443.0 / 45111937500000.0 - B_08; + + /** Internal weights for stage 14, element 9. */ + private static final double K14_09 = -1769651421925959.0 / 14249385146080000.0 - B_09; + + /** Internal weights for stage 14, element 10. */ + private static final double K14_10 = 57799439.0 / 377055000.0 - B_10; + + /** Internal weights for stage 14, element 11. */ + private static final double K14_11 = 793322643029.0 / 96734250000000.0 - B_11; + + /** Internal weights for stage 14, element 12. */ + private static final double K14_12 = 1458939311.0 / 192780000000.0 - B_12; + + /** Internal weights for stage 14, element 13. */ + private static final double K14_13 = -4149.0 / 500000.0; + + /** Time step for stage 15 (interpolation only). */ + private static final double C15 = 1.0 / 5.0; + + + /** Internal weights for stage 15, element 1. */ + private static final double K15_01 = 1595561272731.0 / 50120273500000.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 15, element 6. */ + private static final double K15_06 = 975183916491.0 / 34457688031250.0 - B_06; + + /** Internal weights for stage 15, element 7. */ + private static final double K15_07 = 38492013932672.0 / 718912673015625.0 - B_07; + + /** Internal weights for stage 15, element 8. */ + private static final double K15_08 = -1114881286517557.0 / 20298710767500000.0 - B_08; + + /** Internal weights for stage 15, element 9. */ + private static final double K15_09 = 0.0 - B_09; + + /** Internal weights for stage 15, element 10. */ + private static final double K15_10 = 0.0 - B_10; + + /** Internal weights for stage 15, element 11. */ + private static final double K15_11 = -2538710946863.0 / 23431227861250000.0 - B_11; + + /** Internal weights for stage 15, element 12. */ + private static final double K15_12 = 8824659001.0 / 23066716781250.0 - B_12; + + /** Internal weights for stage 15, element 13. */ + private static final double K15_13 = -11518334563.0 / 33831184612500.0; + + /** Internal weights for stage 15, element 14. */ + private static final double K15_14 = 1912306948.0 / 13532473845.0; + + /** Time step for stage 16 (interpolation only). */ + private static final double C16 = 7.0 / 9.0; + + + /** Internal weights for stage 16, element 1. */ + private static final double K16_01 = -13613986967.0 / 31741908048.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 16, element 6. */ + private static final double K16_06 = -4755612631.0 / 1012344804.0 - B_06; + + /** Internal weights for stage 16, element 7. */ + private static final double K16_07 = 42939257944576.0 / 5588559685701.0 - B_07; + + /** Internal weights for stage 16, element 8. */ + private static final double K16_08 = 77881972900277.0 / 19140370552944.0 - B_08; + + /** Internal weights for stage 16, element 9. */ + private static final double K16_09 = 22719829234375.0 / 63689648654052.0 - B_09; + + /** Internal weights for stage 16, element 10. */ + private static final double K16_10 = 0.0 - B_10; + + /** Internal weights for stage 16, element 11. */ + private static final double K16_11 = 0.0 - B_11; + + /** Internal weights for stage 16, element 12. */ + private static final double K16_12 = 0.0 - B_12; + + /** Internal weights for stage 16, element 13. */ + private static final double K16_13 = -1199007803.0 / 857031517296.0; + + /** Internal weights for stage 16, element 14. */ + private static final double K16_14 = 157882067000.0 / 53564469831.0; + + /** Internal weights for stage 16, element 15. */ + private static final double K16_15 = -290468882375.0 / 31741908048.0; + + /** Interpolation weights. + * (beware that only the non-null values are in the table) + */ + private static final double[][] D = { + + { -17751989329.0 / 2106076560.0, 4272954039.0 / 7539864640.0, + -118476319744.0 / 38604839385.0, 755123450731.0 / 316657731600.0, + 3692384461234828125.0 / 1744130441634250432.0, -4612609375.0 / 5293382976.0, + 2091772278379.0 / 933644586600.0, 2136624137.0 / 3382989120.0, + -126493.0 / 1421424.0, 98350000.0 / 5419179.0, + -18878125.0 / 2053168.0, -1944542619.0 / 438351368.0}, + + { 32941697297.0 / 3159114840.0, 456696183123.0 / 1884966160.0, + 19132610714624.0 / 115814518155.0, -177904688592943.0 / 474986597400.0, + -4821139941836765625.0 / 218016305204281304.0, 30702015625.0 / 3970037232.0, + -85916079474274.0 / 2800933759800.0, -5919468007.0 / 634310460.0, + 2479159.0 / 157936.0, -18750000.0 / 602131.0, + -19203125.0 / 2053168.0, 15700361463.0 / 438351368.0}, + + { 12627015655.0 / 631822968.0, -72955222965.0 / 188496616.0, + -13145744952320.0 / 69488710893.0, 30084216194513.0 / 56998391688.0, + -296858761006640625.0 / 25648977082856624.0, 569140625.0 / 82709109.0, + -18684190637.0 / 18672891732.0, 69644045.0 / 89549712.0, + -11847025.0 / 4264272.0, -978650000.0 / 16257537.0, + 519371875.0 / 6159504.0, 5256837225.0 / 438351368.0}, + + { -450944925.0 / 17550638.0, -14532122925.0 / 94248308.0, + -595876966400.0 / 2573655959.0, 188748653015.0 / 527762886.0, + 2545485458115234375.0 / 27252038150535163.0, -1376953125.0 / 36759604.0, + 53995596795.0 / 518691437.0, 210311225.0 / 7047894.0, + -1718875.0 / 39484.0, 58000000.0 / 602131.0, + -1546875.0 / 39484.0, -1262172375.0 / 8429834.0} + + }; + + /** Last evaluations. */ + private double[][] yDotKLast; + + /** Vectors for interpolation. */ + private double[][] v; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public DormandPrince853StepInterpolator() { + super(); + yDotKLast = null; + v = null; + vectorsInitialized = false; + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + DormandPrince853StepInterpolator(final DormandPrince853StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState == null) { + + yDotKLast = null; + v = null; + vectorsInitialized = false; + + } else { + + final int dimension = interpolator.currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + System.arraycopy(interpolator.yDotKLast[k], 0, yDotKLast[k], 0, + dimension); + } + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + System.arraycopy(interpolator.v[k], 0, v[k], 0, dimension); + } + + vectorsInitialized = interpolator.vectorsInitialized; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new DormandPrince853StepInterpolator(this); + } + + /** {@inheritDoc} */ + @Override + public void reinitialize(final AbstractIntegrator integrator, + final double[] y, final double[][] yDotK, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + + super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers); + + final int dimension = currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + } + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + } + + vectorsInitialized = false; + + } + + /** {@inheritDoc} */ + @Override + public void storeTime(final double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) + throws MaxCountExceededException { + + if (! vectorsInitialized) { + + if (v == null) { + v = new double[7][]; + for (int k = 0; k < 7; ++k) { + v[k] = new double[interpolatedState.length]; + } + } + + // perform the last evaluations if they have not been done yet + finalizeStep(); + + // compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + final double yDot8 = yDotK[7][i]; + final double yDot9 = yDotK[8][i]; + final double yDot10 = yDotK[9][i]; + final double yDot11 = yDotK[10][i]; + final double yDot12 = yDotK[11][i]; + final double yDot13 = yDotK[12][i]; + final double yDot14 = yDotKLast[0][i]; + final double yDot15 = yDotKLast[1][i]; + final double yDot16 = yDotKLast[2][i]; + v[0][i] = B_01 * yDot1 + B_06 * yDot6 + B_07 * yDot7 + + B_08 * yDot8 + B_09 * yDot9 + B_10 * yDot10 + + B_11 * yDot11 + B_12 * yDot12; + v[1][i] = yDot1 - v[0][i]; + v[2][i] = v[0][i] - v[1][i] - yDotK[12][i]; + for (int k = 0; k < D.length; ++k) { + v[k+3][i] = D[k][0] * yDot1 + D[k][1] * yDot6 + D[k][2] * yDot7 + + D[k][3] * yDot8 + D[k][4] * yDot9 + D[k][5] * yDot10 + + D[k][6] * yDot11 + D[k][7] * yDot12 + D[k][8] * yDot13 + + D[k][9] * yDot14 + D[k][10] * yDot15 + D[k][11] * yDot16; + } + } + + vectorsInitialized = true; + + } + + final double eta = 1 - theta; + final double twoTheta = 2 * theta; + final double theta2 = theta * theta; + final double dot1 = 1 - twoTheta; + final double dot2 = theta * (2 - 3 * theta); + final double dot3 = twoTheta * (1 + theta * (twoTheta -3)); + final double dot4 = theta2 * (3 + theta * (5 * theta - 8)); + final double dot5 = theta2 * (3 + theta * (-12 + theta * (15 - 6 * theta))); + final double dot6 = theta2 * theta * (4 + theta * (-15 + theta * (18 - 7 * theta))); + + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = previousState[i] + + theta * h * (v[0][i] + + eta * (v[1][i] + + theta * (v[2][i] + + eta * (v[3][i] + + theta * (v[4][i] + + eta * (v[5][i] + + theta * (v[6][i]))))))); + interpolatedDerivatives[i] = v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] + + dot3 * v[3][i] + dot4 * v[4][i] + + dot5 * v[5][i] + dot6 * v[6][i]; + } + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] - + oneMinusThetaH * (v[0][i] - + theta * (v[1][i] + + theta * (v[2][i] + + eta * (v[3][i] + + theta * (v[4][i] + + eta * (v[5][i] + + theta * (v[6][i]))))))); + interpolatedDerivatives[i] = v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] + + dot3 * v[3][i] + dot4 * v[4][i] + + dot5 * v[5][i] + dot6 * v[6][i]; + } + } + + } + + /** {@inheritDoc} */ + @Override + protected void doFinalize() throws MaxCountExceededException { + + if (currentState == null) { + // we are finalizing an uninitialized instance + return; + } + + double s; + final double[] yTmp = new double[currentState.length]; + final double pT = getGlobalPreviousTime(); + + // k14 + for (int j = 0; j < currentState.length; ++j) { + s = K14_01 * yDotK[0][j] + K14_06 * yDotK[5][j] + K14_07 * yDotK[6][j] + + K14_08 * yDotK[7][j] + K14_09 * yDotK[8][j] + K14_10 * yDotK[9][j] + + K14_11 * yDotK[10][j] + K14_12 * yDotK[11][j] + K14_13 * yDotK[12][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C14 * h, yTmp, yDotKLast[0]); + + // k15 + for (int j = 0; j < currentState.length; ++j) { + s = K15_01 * yDotK[0][j] + K15_06 * yDotK[5][j] + K15_07 * yDotK[6][j] + + K15_08 * yDotK[7][j] + K15_09 * yDotK[8][j] + K15_10 * yDotK[9][j] + + K15_11 * yDotK[10][j] + K15_12 * yDotK[11][j] + K15_13 * yDotK[12][j] + + K15_14 * yDotKLast[0][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C15 * h, yTmp, yDotKLast[1]); + + // k16 + for (int j = 0; j < currentState.length; ++j) { + s = K16_01 * yDotK[0][j] + K16_06 * yDotK[5][j] + K16_07 * yDotK[6][j] + + K16_08 * yDotK[7][j] + K16_09 * yDotK[8][j] + K16_10 * yDotK[9][j] + + K16_11 * yDotK[10][j] + K16_12 * yDotK[11][j] + K16_13 * yDotK[12][j] + + K16_14 * yDotKLast[0][j] + K16_15 * yDotKLast[1][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C16 * h, yTmp, yDotKLast[2]); + + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + try { + // save the local attributes + finalizeStep(); + } catch (MaxCountExceededException mcee) { + final IOException ioe = new IOException(mcee.getLocalizedMessage()); + ioe.initCause(mcee); + throw ioe; + } + + final int dimension = (currentState == null) ? -1 : currentState.length; + out.writeInt(dimension); + for (int i = 0; i < dimension; ++i) { + out.writeDouble(yDotKLast[0][i]); + out.writeDouble(yDotKLast[1][i]); + out.writeDouble(yDotKLast[2][i]); + } + + // save the state of the base class + super.writeExternal(out); + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the local attributes + yDotKLast = new double[3][]; + final int dimension = in.readInt(); + yDotKLast[0] = (dimension < 0) ? null : new double[dimension]; + yDotKLast[1] = (dimension < 0) ? null : new double[dimension]; + yDotKLast[2] = (dimension < 0) ? null : new double[dimension]; + + for (int i = 0; i < dimension; ++i) { + yDotKLast[0][i] = in.readDouble(); + yDotKLast[1][i] = in.readDouble(); + yDotKLast[2][i] = in.readDouble(); + } + + // read the base state + super.readExternal(in); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java new file mode 100644 index 000000000..52004ca5b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java @@ -0,0 +1,385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldExpandableODE; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements the common part of all embedded Runge-Kutta + * integrators for Ordinary Differential Equations. + * + *

    These methods are embedded explicit Runge-Kutta methods with two + * sets of coefficients allowing to estimate the error, their Butcher + * arrays are as follows : + *

    + *    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
    + * 
    + *

    + * + *

    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.

    + * + *

    Some methods are qualified as fsal (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 fsal method, we have cs = 1 and + * asi = bi for all i.

    + * + * @param the type of the field elements + * @since 3.6 + */ + +public abstract class EmbeddedRungeKuttaFieldIntegrator> + extends AdaptiveStepsizeFieldIntegrator + implements FieldButcherArrayProvider { + + /** Index of the pre-computed derivative for fsal methods. */ + private final int fsal; + + /** Time steps from Butcher array (without the first zero). */ + private final T[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final T[][] a; + + /** External weights for the high order method from Butcher array. */ + private final T[] b; + + /** Stepsize control exponent. */ + private final T exp; + + /** Safety factor for stepsize control. */ + private T safety; + + /** Minimal reduction factor for stepsize control. */ + private T minReduction; + + /** Maximal growth factor for stepsize control. */ + private T maxGrowth; + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param fsal index of the pre-computed derivative for fsal methods + * or -1 if method is not fsal + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaFieldIntegrator(final Field field, final String name, final int fsal, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(field, name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + this.fsal = fsal; + this.c = getC(); + this.a = getA(); + this.b = getB(); + + exp = field.getOne().divide(-getOrder()); + + // set the default values of the algorithm control parameters + setSafety(field.getZero().add(0.9)); + setMinReduction(field.getZero().add(0.2)); + setMaxGrowth(field.getZero().add(10.0)); + + } + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param fsal index of the pre-computed derivative for fsal methods + * or -1 if method is not fsal + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaFieldIntegrator(final Field field, final String name, final int fsal, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(field, name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + + this.fsal = fsal; + this.c = getC(); + this.a = getA(); + this.b = getB(); + + exp = field.getOne().divide(-getOrder()); + + // set the default values of the algorithm control parameters + setSafety(field.getZero().add(0.9)); + setMinReduction(field.getZero().add(0.2)); + setMaxGrowth(field.getZero().add(10.0)); + + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final int p, final int q) { + return getField().getOne().multiply(p).divide(q); + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final double p, final double q) { + return getField().getOne().multiply(p).divide(q); + } + + /** Create an interpolator. + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param mapper equations mapper for the all equations + * @return external weights for the high order method from Butcher array + */ + protected abstract RungeKuttaFieldStepInterpolator createInterpolator(boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + FieldEquationsMapper mapper); + /** Get the order of the method. + * @return order of the method + */ + public abstract int getOrder(); + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public T getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final T safety) { + this.safety = safety; + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative integrate(final FieldExpandableODE equations, + final FieldODEState initialState, final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y0 = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y0, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // create some internal working arrays + final int stages = c.length + 1; + T[] y = y0; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = MathArrays.buildArray(getField(), y0.length); + + // set up integration control objects + T hNew = getField().getZero(); + boolean firstTime = true; + + // main integration loop + setIsLastStep(false); + do { + + // iterate over step size, ensuring local normalized error is smaller than 1 + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0) { + + // first stage + y = equations.getMapper().mapState(getStepStart()); + yDotK[0] = equations.getMapper().mapDerivative(getStepStart()); + + if (firstTime) { + final T[] scale = MathArrays.buildArray(getField(), mainSetDimension); + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + scale[i] = y[i].abs().multiply(scalRelativeTolerance).add(scalAbsoluteTolerance); + } + } else { + for (int i = 0; i < scale.length; ++i) { + scale[i] = y[i].abs().multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + } + } + hNew = initializeStep(forward, getOrder(), scale, getStepStart(), equations.getMapper()); + firstTime = false; + } + + setStepSize(hNew); + if (forward) { + if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() >= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } else { + if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() <= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + // estimate the error at the end of the step + error = estimateError(yDotK, y, yTmp, getStepSize()); + if (error.subtract(1.0).getReal() >= 0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = MathUtils.min(maxGrowth, + MathUtils.max(minReduction, safety.multiply(error.pow(exp)))); + hNew = filterStep(getStepSize().multiply(factor), forward, false); + } + + } + final T stepEnd = getStepStart().getTime().add(getStepSize()); + final T[] yDotTmp = (fsal >= 0) ? yDotK[fsal] : computeDerivatives(stepEnd, yTmp); + final FieldODEStateAndDerivative stateTmp = new FieldODEStateAndDerivative(stepEnd, yTmp, yDotTmp); + + // local error is small enough: accept the step, trigger events and step handlers + System.arraycopy(yTmp, 0, y, 0, y0.length); + setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()), + finalTime)); + + if (!isLastStep()) { + + // stepsize control for next step + final T factor = MathUtils.min(maxGrowth, + MathUtils.max(minReduction, safety.multiply(error.pow(exp)))); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative finalState = getStepStart(); + resetInternalState(); + return finalState; + + } + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public T getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final T minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public T getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final T maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected abstract T estimateError(T[][] yDotK, T[] y0, T[] y1, T h); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java new file mode 100644 index 000000000..abb9e3946 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; + +/** + * This class implements the common part of all embedded Runge-Kutta + * integrators for Ordinary Differential Equations. + * + *

    These methods are embedded explicit Runge-Kutta methods with two + * sets of coefficients allowing to estimate the error, their Butcher + * arrays are as follows : + *

    + *    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
    + * 
    + *

    + * + *

    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.

    + * + *

    Some methods are qualified as fsal (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 fsal method, we have cs = 1 and + * asi = bi for all i.

    + * + * @since 1.2 + */ + +public abstract class EmbeddedRungeKuttaIntegrator + extends AdaptiveStepsizeIntegrator { + + /** Indicator for fsal 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 fsal + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param name name of the method + * @param fsal indicate that the method is an fsal + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Get the order of the method. + * @return order of the method + */ + public abstract int getOrder(); + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public double getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final double safety) { + this.safety = safety; + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][y.length]; + final double[] yTmp = y0.clone(); + final double[] yDotTmp = new double[y.length]; + + // set up an interpolator sharing the integrator arrays + final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy(); + interpolator.reinitialize(this, yTmp, yDotK, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + // set up integration control objects + stepStart = equations.getTime(); + double hNew = 0; + boolean firstTime = true; + initIntegration(equations.getTime(), y0, t); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + + // iterate over step size, ensuring local normalized error is smaller than 1 + double error = 10; + while (error >= 1.0) { + + if (firstTime || !fsal) { + // first stage + computeDerivatives(stepStart, y, yDotK[0]); + } + + if (firstTime) { + final double[] scale = new double[mainSetDimension]; + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * FastMath.abs(y[i]); + } + } else { + for (int i = 0; i < scale.length; ++i) { + scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * FastMath.abs(y[i]); + } + } + hNew = initializeStep(forward, getOrder(), scale, + stepStart, y, yDotK[0], yTmp, yDotK[1]); + firstTime = false; + } + + stepSize = hNew; + if (forward) { + if (stepStart + stepSize >= t) { + stepSize = t - stepStart; + } + } else { + if (stepStart + stepSize <= t) { + stepSize = t - stepStart; + } + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + // estimate the error at the end of the step + error = estimateError(yDotK, y, yTmp, stepSize); + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = + FastMath.min(maxGrowth, + FastMath.max(minReduction, safety * FastMath.pow(error, exp))); + hNew = filterStep(stepSize * factor, forward, false); + } + + } + + // local error is small enough: accept the step, trigger events and step handlers + interpolator.storeTime(stepStart + stepSize); + System.arraycopy(yTmp, 0, y, 0, y0.length); + System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length); + stepStart = acceptStep(interpolator, y, yDotTmp, t); + System.arraycopy(y, 0, yTmp, 0, y.length); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (fsal) { + // save the last evaluation for the next step + System.arraycopy(yDotTmp, 0, yDotK[0], 0, y0.length); + } + + // stepsize control for next step + final double factor = + FastMath.min(maxGrowth, FastMath.max(minReduction, safety * FastMath.pow(error, exp))); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public double getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final double minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public double getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final double maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected abstract double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java new file mode 100644 index 000000000..45f8eaa43 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a simple Euler integrator for Ordinary + * Differential Equations. + * + *

    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 : + * f'=(f(t+h)-f(t))/h which leads to + * f(t+h)=f(t)+hf'. The interpolation scheme used for + * dense output is the linear scheme already used for integration.

    + * + *

    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.

    + * + *

    This algorithm is almost never used and has been included in + * this package only as a comparison reference for more useful + * integrators.

    + * + * @see MidpointFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class EulerFieldIntegrator> extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build an Euler integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public EulerFieldIntegrator(final Field field, final T step) { + super(field, "Euler", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + return MathArrays.buildArray(getField(), 0); + } + + /** {@inheritDoc} */ + public T[][] getA() { + return MathArrays.buildArray(getField(), 0, 0); + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 1); + b[0] = getField().getOne(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected EulerFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new EulerFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java new file mode 100644 index 000000000..4a544e3cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a linear interpolator for step. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + θ h y' + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) - (1-θ) h y' + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y' is the evaluation of + * the derivatives already computed during the step.

    + * + * @see EulerFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class EulerFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + EulerFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected EulerFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new EulerFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + final T[] interpolatedState; + final T[] interpolatedDerivatives; + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + interpolatedState = previousStateLinearCombination(thetaH); + interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne()); + } else { + interpolatedState = currentStateLinearCombination(oneMinusThetaH.negate()); + interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne()); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java new file mode 100644 index 000000000..4f3f0b1b4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements a simple Euler integrator for Ordinary + * Differential Equations. + * + *

    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 : + * f'=(f(t+h)-f(t))/h which leads to + * f(t+h)=f(t)+hf'. The interpolation scheme used for + * dense output is the linear scheme already used for integration.

    + * + *

    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.

    + * + *

    This algorithm is almost never used and has been included in + * this package only as a comparison reference for more useful + * integrators.

    + * + * @see MidpointIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class EulerIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 + }; + + /** Simple constructor. + * Build an Euler integrator with the given step. + * @param step integration step + */ + public EulerIntegrator(final double step) { + super("Euler", STATIC_C, STATIC_A, STATIC_B, new EulerStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java new file mode 100644 index 000000000..758fbfcd6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a linear interpolator for step. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + θ h y' + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) - (1-θ) h y' + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y' is the evaluation of + * the derivatives already computed during the step.

    + * + * @see EulerIntegrator + * @since 1.2 + */ + +class EulerStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public EulerStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + EulerStepInterpolator(final EulerStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new EulerStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = previousState[i] + theta * h * yDotK[0][i]; + } + System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length); + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] - oneMinusThetaH * yDotK[0][i]; + } + System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java new file mode 100644 index 000000000..a5ca3c456 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** This interface represents an integrator based on Butcher arrays. + * @see RungeKuttaFieldIntegrator + * @see EmbeddedRungeKuttaFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public interface FieldButcherArrayProvider> { + + /** Get the time steps from Butcher array (without the first zero). + * @return time steps from Butcher array (without the first zero + */ + T[] getC(); + + /** Get the internal weights from Butcher array (without the first empty row). + * @return internal weights from Butcher array (without the first empty row) + */ + T[][] getA(); + + /** Get the external weights for the high order method from Butcher array. + * @return external weights for the high order method from Butcher array + */ + T[] getB(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java new file mode 100644 index 000000000..c25ba829b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements the Gill fourth order Runge-Kutta + * integrator for Ordinary Differential Equations . + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + * where q = sqrt(2)

    + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class GillFieldIntegrator> + extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build a fourth-order Gill integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public GillFieldIntegrator(final Field field, final T step) { + super(field, "Gill", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = fraction(1, 2); + c[1] = c[0]; + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + + final T two = getField().getZero().add(2); + final T sqrtTwo = two.sqrt(); + + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 2); + a[1][0] = sqrtTwo.subtract(1).multiply(0.5); + a[1][1] = sqrtTwo.subtract(2).multiply(-0.5); + a[2][0] = getField().getZero(); + a[2][1] = sqrtTwo.multiply(-0.5); + a[2][2] = sqrtTwo.add(2).multiply(0.5); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + + final T two = getField().getZero().add(2); + final T sqrtTwo = two.sqrt(); + + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 6); + b[1] = sqrtTwo.subtract(2).divide(-6); + b[2] = sqrtTwo.add(2).divide(6); + b[3] = b[0]; + + return b; + + } + + /** {@inheritDoc} */ + @Override + protected GillFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new GillFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java new file mode 100644 index 000000000..1318d44e7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the Gill fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/6) [ (6 - 9 θ + 4 θ2) y'1 + * + ( 6 θ - 4 θ2) ((1-1/√2) y'2 + (1+1/√2)) y'3) + * + ( - 3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn + h) + * - (1 - θ) (h/6) [ (1 - 5 θ + 4 θ2) y'1 + * + (2 + 2 θ - 4 θ2) ((1-1/√2) y'2 + (1+1/√2)) y'3) + * + (1 + θ + 4 θ2) y'4 + * ] + *
    • + *
    + *

    + * where θ belongs to [0 ; 1] and where y'1 to y'4 + * are the four evaluations of the derivatives already computed during + * the step.

    + * + * @see GillFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class GillFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** First Gill coefficient. */ + private final T one_minus_inv_sqrt_2; + + /** Second Gill coefficient. */ + private final T one_plus_inv_sqrt_2; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + GillFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T sqrt = field.getZero().add(0.5).sqrt(); + one_minus_inv_sqrt_2 = field.getOne().subtract(sqrt); + one_plus_inv_sqrt_2 = field.getOne().add(sqrt); + } + + /** {@inheritDoc} */ + @Override + protected GillFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new GillFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T one = time.getField().getOne(); + final T twoTheta = theta.multiply(2); + final T fourTheta2 = twoTheta.multiply(twoTheta); + final T coeffDot1 = theta.multiply(twoTheta.subtract(3)).add(1); + final T cDot23 = twoTheta.multiply(one.subtract(theta)); + final T coeffDot2 = cDot23.multiply(one_minus_inv_sqrt_2); + final T coeffDot3 = cDot23.multiply(one_plus_inv_sqrt_2); + final T coeffDot4 = theta.multiply(twoTheta.subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T s = thetaH.divide(6.0); + final T c23 = s.multiply(theta.multiply(6).subtract(fourTheta2)); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6)); + final T coeff2 = c23.multiply(one_minus_inv_sqrt_2); + final T coeff3 = c23.multiply(one_plus_inv_sqrt_2); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } else { + final T s = oneMinusThetaH.divide(-6.0); + final T c23 = s.multiply(twoTheta.add(2).subtract(fourTheta2)); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(5)).add(1)); + final T coeff2 = c23.multiply(one_minus_inv_sqrt_2); + final T coeff3 = c23.multiply(one_plus_inv_sqrt_2); + final T coeff4 = s.multiply(fourTheta2.add(theta).add(1)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java new file mode 100644 index 000000000..ef2f1a5c0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the Gill fourth order Runge-Kutta + * integrator for Ordinary Differential Equations . + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + * where q = sqrt(2)

    + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class GillIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 }, + { (FastMath.sqrt(2.0) - 1.0) / 2.0, (2.0 - FastMath.sqrt(2.0)) / 2.0 }, + { 0.0, -FastMath.sqrt(2.0) / 2.0, (2.0 + FastMath.sqrt(2.0)) / 2.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 6.0, (2.0 - FastMath.sqrt(2.0)) / 6.0, (2.0 + FastMath.sqrt(2.0)) / 6.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Gill integrator with the given step. + * @param step integration step + */ + public GillIntegrator(final double step) { + super("Gill", STATIC_C, STATIC_A, STATIC_B, new GillStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java new file mode 100644 index 000000000..878770867 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements a step interpolator for the Gill fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/6) [ (6 - 9 θ + 4 θ2) y'1 + * + ( 6 θ - 4 θ2) ((1-1/√2) y'2 + (1+1/√2)) y'3) + * + ( - 3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn + h) + * - (1 - θ) (h/6) [ (1 - 5 θ + 4 θ2) y'1 + * + (2 + 2 θ - 4 θ2) ((1-1/√2) y'2 + (1+1/√2)) y'3) + * + (1 + θ + 4 θ2) y'4 + * ] + *
    • + *
    + *

    + * where θ belongs to [0 ; 1] and where y'1 to y'4 + * are the four evaluations of the derivatives already computed during + * the step.

    + * + * @see GillIntegrator + * @since 1.2 + */ + +class GillStepInterpolator + extends RungeKuttaStepInterpolator { + + /** First Gill coefficient. */ + private static final double ONE_MINUS_INV_SQRT_2 = 1 - FastMath.sqrt(0.5); + + /** Second Gill coefficient. */ + private static final double ONE_PLUS_INV_SQRT_2 = 1 + FastMath.sqrt(0.5); + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public GillStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + GillStepInterpolator(final GillStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new GillStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double twoTheta = 2 * theta; + final double fourTheta2 = twoTheta * twoTheta; + final double coeffDot1 = theta * (twoTheta - 3) + 1; + final double cDot23 = twoTheta * (1 - theta); + final double coeffDot2 = cDot23 * ONE_MINUS_INV_SQRT_2; + final double coeffDot3 = cDot23 * ONE_PLUS_INV_SQRT_2; + final double coeffDot4 = theta * (twoTheta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double s = theta * h / 6.0; + final double c23 = s * (6 * theta - fourTheta2); + final double coeff1 = s * (6 - 9 * theta + fourTheta2); + final double coeff2 = c23 * ONE_MINUS_INV_SQRT_2; + final double coeff3 = c23 * ONE_PLUS_INV_SQRT_2; + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + } + } else { + final double s = oneMinusThetaH / 6.0; + final double c23 = s * (2 + twoTheta - fourTheta2); + final double coeff1 = s * (1 - 5 * theta + fourTheta2); + final double coeff2 = c23 * ONE_MINUS_INV_SQRT_2; + final double coeff3 = c23 * ONE_PLUS_INV_SQRT_2; + final double coeff4 = s * (1 + theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java new file mode 100644 index 000000000..3c43088af --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java @@ -0,0 +1,949 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; +import com.fr.third.org.apache.commons.math3.ode.events.EventHandler; + +/** + * This class implements a Gragg-Bulirsch-Stoer integrator for + * Ordinary Differential Equations. + * + *

    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.

    + * + *

    + * 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 extremely sensitive + * to the errors in the first integration steps), and around 1e-11 + * for a two dimensional celestial mechanics problems with seven + * bodies (pleiades problem, involving quasi-collisions for which + * automatic step size control is essential). + *

    + * + *

    + * This implementation is basically a reimplementation in Java of the + * odex + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available here, for + * convenience, it is reproduced below.

    + *

    + * + * + * + * + * + * + * + *
    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.
    + * + * @since 1.2 + */ + +public class GraggBulirschStoerIntegrator extends AdaptiveStepsizeIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Gragg-Bulirsch-Stoer"; + + /** maximal order. */ + private int maxOrder; + + /** step size sequence. */ + private int[] sequence; + + /** overall cost of applying step reduction up to iteration k+1, in number of calls. */ + private int[] costPerStep; + + /** cost per unit step. */ + private double[] costPerTimeUnit; + + /** optimal steps for each order. */ + private double[] optimalStep; + + /** extrapolation coefficients. */ + private double[][] coeff; + + /** stability check enabling parameter. */ + private boolean performTest; + + /** maximal number of checks for each iteration. */ + private int maxChecks; + + /** maximal number of iterations for which checks are performed. */ + private int maxIter; + + /** stepsize reduction factor in case of stability check failure. */ + private double stabilityReduction; + + /** first stepsize control factor. */ + private double stepControl1; + + /** second stepsize control factor. */ + private double stepControl2; + + /** third stepsize control factor. */ + private double stepControl3; + + /** fourth stepsize control factor. */ + private double stepControl4; + + /** first order control factor. */ + private double orderControl1; + + /** second order control factor. */ + private double orderControl2; + + /** use interpolation error in stepsize control. */ + private boolean useInterpolationError; + + /** interpolation order control parameter. */ + private int mudif; + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + setStabilityCheck(true, -1, -1, -1); + setControlFactors(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + setStabilityCheck(true, -1, -1, -1); + setControlFactors(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Set the stability check controls. + *

    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.

    + *

    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.

    + * @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. + + *

    The new step size hNew is computed from the old one h by: + *

    +   * hNew = h * stepControl2 / (err/stepControl1)^(1/(2k+1))
    +   * 
    + * 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.

    + *

    The step size is subject to the restriction: + *

    +   * stepControl3^(1/(2k+1))/stepControl4 <= hNew/h <= 1/stepControl3^(1/(2k+1))
    +   * 
    + * The default values are 0.02 for stepControl3 and 4.0 for + * stepControl4.

    + * @param control1 first stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control2 second stepsize control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + * @param control3 third stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control4 fourth stepsize control factor (the factor + * is reset to default if lower than 1.0001 or greater than 999.9) + */ + public void setControlFactors(final double control1, final double control2, + final double control3, final double control4) { + + if ((control1 < 0.0001) || (control1 > 0.9999)) { + this.stepControl1 = 0.65; + } else { + this.stepControl1 = control1; + } + + if ((control2 < 0.0001) || (control2 > 0.9999)) { + this.stepControl2 = 0.94; + } else { + this.stepControl2 = control2; + } + + if ((control3 < 0.0001) || (control3 > 0.9999)) { + this.stepControl3 = 0.02; + } else { + this.stepControl3 = control3; + } + + if ((control4 < 1.0001) || (control4 > 999.9)) { + this.stepControl4 = 4.0; + } else { + this.stepControl4 = control4; + } + + } + + /** Set the order control parameters. + *

    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.

    + *
    +   * order is decreased if w(k-1) <= w(k)   * orderControl1
    +   * order is increased if w(k)   <= w(k-1) * orderControl2
    +   * 
    + *

    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.

    + *

    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.

    + * @param maximalOrder maximal order in the extrapolation table (the + * maximal order is reset to default if order <= 6 or odd) + * @param control1 first order control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control2 second order control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + */ + public void setOrderControl(final int maximalOrder, + final double control1, final double control2) { + + if ((maximalOrder <= 6) || (maximalOrder % 2 != 0)) { + this.maxOrder = 18; + } + + if ((control1 < 0.0001) || (control1 > 0.9999)) { + this.orderControl1 = 0.8; + } else { + this.orderControl1 = control1; + } + + if ((control2 < 0.0001) || (control2 > 0.9999)) { + this.orderControl2 = 0.9; + } else { + this.orderControl2 = control2; + } + + // reinitialize the arrays + initializeArrays(); + + } + + /** {@inheritDoc} */ + @Override + public void addStepHandler (final StepHandler handler) { + + super.addStepHandler(handler); + + // reinitialize the arrays + initializeArrays(); + + } + + /** {@inheritDoc} */ + @Override + public void addEventHandler(final EventHandler function, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount, + final UnivariateSolver solver) { + super.addEventHandler(function, maxCheckInterval, convergence, + maxIterationCount, solver); + + // reinitialize the arrays + initializeArrays(); + + } + + /** Initialize the integrator internal arrays. */ + private void initializeArrays() { + + final int size = maxOrder / 2; + + if ((sequence == null) || (sequence.length != size)) { + // all arrays should be reallocated with the right size + sequence = new int[size]; + costPerStep = new int[size]; + coeff = new double[size][]; + costPerTimeUnit = new double[size]; + optimalStep = new double[size]; + } + + // step size sequence: 2, 6, 10, 14, ... + for (int k = 0; k < size; ++k) { + sequence[k] = 4 * k + 2; + } + + // initialize the order selection cost array + // (number of function calls for each column of the extrapolation table) + costPerStep[0] = sequence[0] + 1; + for (int k = 1; k < size; ++k) { + costPerStep[k] = costPerStep[k-1] + sequence[k]; + } + + // initialize the extrapolation tables + for (int k = 0; k < size; ++k) { + coeff[k] = (k > 0) ? new double[k] : null; + for (int l = 0; l < k; ++l) { + final double ratio = ((double) sequence[k]) / sequence[k-l-1]; + coeff[k][l] = 1.0 / (ratio * ratio - 1.0); + } + } + + } + + /** Set the interpolation order control parameter. + * The interpolation order for dense output is 2k - mudif + 1. The + * default value for mudif is 4 and the interpolation error is used + * in stepsize control by default. + + * @param useInterpolationErrorForControl if true, interpolation error is used + * for stepsize control + * @param mudifControlParameter interpolation order control parameter (the parameter + * is reset to default if <= 0 or >= 7) + */ + public void setInterpolationControl(final boolean useInterpolationErrorForControl, + final int mudifControlParameter) { + + this.useInterpolationError = useInterpolationErrorForControl; + + if ((mudifControlParameter <= 0) || (mudifControlParameter >= 7)) { + this.mudif = 4; + } else { + this.mudif = mudifControlParameter; + } + + } + + /** Update scaling array. + * @param y1 first state vector to use for scaling + * @param y2 second state vector to use for scaling + * @param scale scaling array to update (can be shorter than state) + */ + private void rescale(final double[] y1, final double[] y2, final double[] scale) { + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i])); + scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * yi; + } + } else { + for (int i = 0; i < scale.length; ++i) { + final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i])); + scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yi; + } + } + } + + /** Perform integration over one step using substeps of a modified + * midpoint method. + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param step global step + * @param k iteration number (from 0 to sequence.length - 1) + * @param scale scaling array (can be shorter than state) + * @param f placeholder where to put the state vector derivatives at each substep + * (element 0 already contains initial derivative) + * @param yMiddle placeholder where to put the state vector at the middle of the step + * @param yEnd placeholder where to put the state vector at the end + * @param yTmp placeholder for one state vector + * @return true if computation was done properly, + * false if stability check failed before end of computation + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + private boolean tryStep(final double t0, final double[] y0, final double step, final int k, + final double[] scale, final double[][] f, + final double[] yMiddle, final double[] yEnd, + final double[] yTmp) + throws MaxCountExceededException, DimensionMismatchException { + + final int n = sequence[k]; + final double subStep = step / n; + final double subStep2 = 2 * subStep; + + // first substep + double t = t0 + subStep; + for (int i = 0; i < y0.length; ++i) { + yTmp[i] = y0[i]; + yEnd[i] = y0[i] + subStep * f[0][i]; + } + computeDerivatives(t, yEnd, f[1]); + + // other substeps + for (int j = 1; j < n; ++j) { + + if (2 * j == n) { + // save the point at the middle of the step + System.arraycopy(yEnd, 0, yMiddle, 0, y0.length); + } + + t += subStep; + for (int i = 0; i < y0.length; ++i) { + final double middle = yEnd[i]; + yEnd[i] = yTmp[i] + subStep2 * f[j][i]; + yTmp[i] = middle; + } + + computeDerivatives(t, yEnd, f[j+1]); + + // stability check + if (performTest && (j <= maxChecks) && (k < maxIter)) { + double initialNorm = 0.0; + for (int l = 0; l < scale.length; ++l) { + final double ratio = f[0][l] / scale[l]; + initialNorm += ratio * ratio; + } + double deltaNorm = 0.0; + for (int l = 0; l < scale.length; ++l) { + final double ratio = (f[j+1][l] - f[0][l]) / scale[l]; + deltaNorm += ratio * ratio; + } + if (deltaNorm > 4 * FastMath.max(1.0e-15, initialNorm)) { + return false; + } + } + + } + + // correction of the last substep (at t0 + step) + for (int i = 0; i < y0.length; ++i) { + yEnd[i] = 0.5 * (yTmp[i] + yEnd[i] + subStep * f[n][i]); + } + + return true; + + } + + /** Extrapolate a vector. + * @param offset offset to use in the coefficients table + * @param k index of the last updated point + * @param diag working diagonal of the Aitken-Neville's + * triangle, without the last element + * @param last last element + */ + private void extrapolate(final int offset, final int k, + final double[][] diag, final double[] last) { + + // update the diagonal + for (int j = 1; j < k; ++j) { + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + diag[k-j-1][i] = diag[k-j][i] + + coeff[k+offset][j-1] * (diag[k-j][i] - diag[k-j-1][i]); + } + } + + // update the last element + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + last[i] = diag[0][i] + coeff[k+offset][k-1] * (diag[0][i] - last[i]); + } + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final double[] yDot0 = new double[y.length]; + final double[] y1 = new double[y.length]; + final double[] yTmp = new double[y.length]; + final double[] yTmpDot = new double[y.length]; + + final double[][] diagonal = new double[sequence.length-1][]; + final double[][] y1Diag = new double[sequence.length-1][]; + for (int k = 0; k < sequence.length-1; ++k) { + diagonal[k] = new double[y.length]; + y1Diag[k] = new double[y.length]; + } + + final double[][][] fk = new double[sequence.length][][]; + for (int k = 0; k < sequence.length; ++k) { + + fk[k] = new double[sequence[k] + 1][]; + + // all substeps start at the same point, so share the first array + fk[k][0] = yDot0; + + for (int l = 0; l < sequence[k]; ++l) { + fk[k][l+1] = new double[y0.length]; + } + + } + + if (y != y0) { + System.arraycopy(y0, 0, y, 0, y0.length); + } + + final double[] yDot1 = new double[y0.length]; + final double[][] yMidDots = new double[1 + 2 * sequence.length][y0.length]; + + // initial scaling + final double[] scale = new double[mainSetDimension]; + rescale(y, y, scale); + + // initial order selection + final double tol = + (vecRelativeTolerance == null) ? scalRelativeTolerance : vecRelativeTolerance[0]; + final double log10R = FastMath.log10(FastMath.max(1.0e-10, tol)); + int targetIter = FastMath.max(1, + FastMath.min(sequence.length - 2, + (int) FastMath.floor(0.5 - 0.6 * log10R))); + + // set up an interpolator sharing the integrator arrays + final AbstractStepInterpolator interpolator = + new GraggBulirschStoerStepInterpolator(y, yDot0, + y1, yDot1, + yMidDots, forward, + equations.getPrimaryMapper(), + equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + stepStart = equations.getTime(); + double hNew = 0; + double maxError = Double.MAX_VALUE; + boolean previousRejected = false; + boolean firstTime = true; + boolean newStep = true; + boolean firstStepAlreadyComputed = false; + initIntegration(equations.getTime(), y0, t); + costPerTimeUnit[0] = 0; + isLastStep = false; + do { + + double error; + boolean reject = false; + + if (newStep) { + + interpolator.shift(); + + // first evaluation, at the beginning of the step + if (! firstStepAlreadyComputed) { + computeDerivatives(stepStart, y, yDot0); + } + + if (firstTime) { + hNew = initializeStep(forward, 2 * targetIter + 1, scale, + stepStart, y, yDot0, yTmp, yTmpDot); + } + + newStep = false; + + } + + stepSize = hNew; + + // step adjustment near bounds + if ((forward && (stepStart + stepSize > t)) || + ((! forward) && (stepStart + stepSize < t))) { + stepSize = t - stepStart; + } + final double nextT = stepStart + stepSize; + isLastStep = forward ? (nextT >= t) : (nextT <= t); + + // iterate over several substep sizes + int k = -1; + for (boolean loop = true; loop; ) { + + ++k; + + // modified midpoint integration with the current substep + if ( ! tryStep(stepStart, y, stepSize, k, scale, fk[k], + (k == 0) ? yMidDots[0] : diagonal[k-1], + (k == 0) ? y1 : y1Diag[k-1], + yTmp)) { + + // the stability check failed, we reduce the global step + hNew = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false)); + reject = true; + loop = false; + + } else { + + // the substep was computed successfully + if (k > 0) { + + // extrapolate the state at the end of the step + // using last iteration data + extrapolate(0, k, y1Diag, y1); + rescale(y, y1, scale); + + // estimate the error at the end of the step. + error = 0; + for (int j = 0; j < mainSetDimension; ++j) { + final double e = FastMath.abs(y1[j] - y1Diag[0][j]) / scale[j]; + error += e * e; + } + error = FastMath.sqrt(error / mainSetDimension); + + if ((error > 1.0e15) || ((k > 1) && (error > maxError))) { + // error is too big, we reduce the global step + hNew = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false)); + reject = true; + loop = false; + } else { + + maxError = FastMath.max(4 * error, 1.0); + + // compute optimal stepsize for this order + final double exp = 1.0 / (2 * k + 1); + double fac = stepControl2 / FastMath.pow(error / stepControl1, exp); + final double pow = FastMath.pow(stepControl3, exp); + fac = FastMath.max(pow / stepControl4, FastMath.min(1 / pow, fac)); + optimalStep[k] = FastMath.abs(filterStep(stepSize * fac, forward, true)); + costPerTimeUnit[k] = costPerStep[k] / optimalStep[k]; + + // check convergence + switch (k - targetIter) { + + case -1 : + if ((targetIter > 1) && ! previousRejected) { + + // check if we can stop iterations now + if (error <= 1.0) { + // convergence have been reached just before targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + final double ratio = ((double) sequence [targetIter] * sequence[targetIter + 1]) / + (sequence[0] * sequence[0]); + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately and reduce order + reject = true; + loop = false; + targetIter = k; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + } + break; + + case 0: + if (error <= 1.0) { + // convergence has been reached exactly at targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + final double ratio = ((double) sequence[k+1]) / sequence[0]; + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately + reject = true; + loop = false; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + break; + + case 1 : + if (error > 1.0) { + reject = true; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + loop = false; + break; + + default : + if ((firstTime || isLastStep) && (error <= 1.0)) { + loop = false; + } + break; + + } + + } + } + } + } + + if (! reject) { + // derivatives at end of step + computeDerivatives(stepStart + stepSize, y1, yDot1); + } + + // dense output handling + double hInt = getMaxStep(); + if (! reject) { + + // extrapolate state at middle point of the step + for (int j = 1; j <= k; ++j) { + extrapolate(0, j, diagonal, yMidDots[0]); + } + + final int mu = 2 * k - mudif + 3; + + for (int l = 0; l < mu; ++l) { + + // derivative at middle point of the step + final int l2 = l / 2; + double factor = FastMath.pow(0.5 * sequence[l2], l); + int middleIndex = fk[l2].length / 2; + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] = factor * fk[l2][middleIndex + l][i]; + } + for (int j = 1; j <= k - l2; ++j) { + factor = FastMath.pow(0.5 * sequence[j + l2], l); + middleIndex = fk[l2+j].length / 2; + for (int i = 0; i < y0.length; ++i) { + diagonal[j-1][i] = factor * fk[l2+j][middleIndex+l][i]; + } + extrapolate(l2, j, diagonal, yMidDots[l+1]); + } + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] *= stepSize; + } + + // compute centered differences to evaluate next derivatives + for (int j = (l + 1) / 2; j <= k; ++j) { + for (int m = fk[j].length - 1; m >= 2 * (l + 1); --m) { + for (int i = 0; i < y0.length; ++i) { + fk[j][m][i] -= fk[j][m-2][i]; + } + } + } + + } + + if (mu >= 0) { + + // estimate the dense output coefficients + final GraggBulirschStoerStepInterpolator gbsInterpolator + = (GraggBulirschStoerStepInterpolator) interpolator; + gbsInterpolator.computeCoefficients(mu, stepSize); + + if (useInterpolationError) { + // use the interpolation error to limit stepsize + final double interpError = gbsInterpolator.estimateError(scale); + hInt = FastMath.abs(stepSize / FastMath.max(FastMath.pow(interpError, 1.0 / (mu+4)), + 0.01)); + if (interpError > 10.0) { + hNew = hInt; + reject = true; + } + } + + } + + } + + if (! reject) { + + // Discrete events handling + interpolator.storeTime(stepStart + stepSize); + stepStart = acceptStep(interpolator, y1, yDot1, t); + + // prepare next step + interpolator.storeTime(stepStart); + System.arraycopy(y1, 0, y, 0, y0.length); + System.arraycopy(yDot1, 0, yDot0, 0, y0.length); + firstStepAlreadyComputed = true; + + int optimalIter; + if (k == 1) { + optimalIter = 2; + if (previousRejected) { + optimalIter = 1; + } + } else if (k <= targetIter) { + optimalIter = k; + if (costPerTimeUnit[k-1] < orderControl1 * costPerTimeUnit[k]) { + optimalIter = k-1; + } else if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1]) { + optimalIter = FastMath.min(k+1, sequence.length - 2); + } + } else { + optimalIter = k - 1; + if ((k > 2) && + (costPerTimeUnit[k-2] < orderControl1 * costPerTimeUnit[k-1])) { + optimalIter = k - 2; + } + if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[optimalIter]) { + optimalIter = FastMath.min(k, sequence.length - 2); + } + } + + if (previousRejected) { + // after a rejected step neither order nor stepsize + // should increase + targetIter = FastMath.min(optimalIter, k); + hNew = FastMath.min(FastMath.abs(stepSize), optimalStep[targetIter]); + } else { + // stepsize control + if (optimalIter <= k) { + hNew = optimalStep[optimalIter]; + } else { + if ((k < targetIter) && + (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1])) { + hNew = filterStep(optimalStep[k] * costPerStep[optimalIter+1] / costPerStep[k], + forward, false); + } else { + hNew = filterStep(optimalStep[k] * costPerStep[optimalIter] / costPerStep[k], + forward, false); + } + } + + targetIter = optimalIter; + + } + + newStep = true; + + } + + hNew = FastMath.min(hNew, hInt); + if (! forward) { + hNew = -hNew; + } + + firstTime = false; + + if (reject) { + isLastStep = false; + previousRejected = true; + } else { + previousRejected = false; + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java new file mode 100644 index 000000000..bcff38ee2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java @@ -0,0 +1,407 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements an interpolator for the Gragg-Bulirsch-Stoer + * integrator. + * + *

    This interpolator compute dense output inside the last step + * produced by a Gragg-Bulirsch-Stoer integrator.

    + * + *

    + * This implementation is basically a reimplementation in Java of the + * odex + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available here, for + * convenience, it is reproduced below.

    + *

    + * + * + * + * + * + * + * + *
    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.
    + * + * @see GraggBulirschStoerIntegrator + * @since 1.2 + */ + +class GraggBulirschStoerStepInterpolator + extends AbstractStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20110928L; + + /** Slope at the beginning of the step. */ + private double[] y0Dot; + + /** State at the end of the step. */ + private double[] y1; + + /** Slope at the end of the step. */ + private double[] y1Dot; + + /** Derivatives at the middle of the step. + * element 0 is state at midpoint, element 1 is first derivative ... + */ + private double[][] yMidDots; + + /** Interpolation polynomials. */ + private double[][] polynomials; + + /** Error coefficients for the interpolation. */ + private double[] errfac; + + /** Degree of the interpolation polynomials. */ + private int currentDegree; + + /** Simple constructor. + * This constructor should not be used directly, it is only intended + * for the serialization process. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public GraggBulirschStoerStepInterpolator() { + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + resetTables(-1); + } + // CHECKSTYLE: resume RedundantModifier + + /** Simple constructor. + * @param y reference to the integrator array holding the current state + * @param y0Dot reference to the integrator array holding the slope + * at the beginning of the step + * @param y1 reference to the integrator array holding the state at + * the end of the step + * @param y1Dot reference to the integrator array holding the slope + * at the end of the step + * @param yMidDots reference to the integrator array holding the + * derivatives at the middle point of the step + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + GraggBulirschStoerStepInterpolator(final double[] y, final double[] y0Dot, + final double[] y1, final double[] y1Dot, + final double[][] yMidDots, + final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + + super(y, forward, primaryMapper, secondaryMappers); + this.y0Dot = y0Dot; + this.y1 = y1; + this.y1Dot = y1Dot; + this.yMidDots = yMidDots; + + resetTables(yMidDots.length + 4); + + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + GraggBulirschStoerStepInterpolator(final GraggBulirschStoerStepInterpolator interpolator) { + + super(interpolator); + + final int dimension = currentState.length; + + // the interpolator has been finalized, + // the following arrays are not needed anymore + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + + // copy the interpolation polynomials (up to the current degree only) + if (interpolator.polynomials == null) { + polynomials = null; + currentDegree = -1; + } else { + resetTables(interpolator.currentDegree); + for (int i = 0; i < polynomials.length; ++i) { + polynomials[i] = new double[dimension]; + System.arraycopy(interpolator.polynomials[i], 0, + polynomials[i], 0, dimension); + } + currentDegree = interpolator.currentDegree; + } + + } + + /** Reallocate the internal tables. + * Reallocate the internal tables in order to be able to handle + * interpolation polynomials up to the given degree + * @param maxDegree maximal degree to handle + */ + private void resetTables(final int maxDegree) { + + if (maxDegree < 0) { + polynomials = null; + errfac = null; + currentDegree = -1; + } else { + + final double[][] newPols = new double[maxDegree + 1][]; + if (polynomials != null) { + System.arraycopy(polynomials, 0, newPols, 0, polynomials.length); + for (int i = polynomials.length; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } else { + for (int i = 0; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } + polynomials = newPols; + + // initialize the error factors array for interpolation + if (maxDegree <= 4) { + errfac = null; + } else { + errfac = new double[maxDegree - 4]; + for (int i = 0; i < errfac.length; ++i) { + final int ip5 = i + 5; + errfac[i] = 1.0 / (ip5 * ip5); + final double e = 0.5 * FastMath.sqrt (((double) (i + 1)) / ip5); + for (int j = 0; j <= i; ++j) { + errfac[i] *= e / (j + 1); + } + } + } + + currentDegree = 0; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new GraggBulirschStoerStepInterpolator(this); + } + + + /** Compute the interpolation coefficients for dense output. + * @param mu degree of the interpolation polynomial + * @param h current step + */ + public void computeCoefficients(final int mu, final double h) { + + if ((polynomials == null) || (polynomials.length <= (mu + 4))) { + resetTables(mu + 4); + } + + currentDegree = mu + 4; + + for (int i = 0; i < currentState.length; ++i) { + + final double yp0 = h * y0Dot[i]; + final double yp1 = h * y1Dot[i]; + final double ydiff = y1[i] - currentState[i]; + final double aspl = ydiff - yp1; + final double bspl = yp0 - ydiff; + + polynomials[0][i] = currentState[i]; + polynomials[1][i] = ydiff; + polynomials[2][i] = aspl; + polynomials[3][i] = bspl; + + if (mu < 0) { + return; + } + + // compute the remaining coefficients + final double ph0 = 0.5 * (currentState[i] + y1[i]) + 0.125 * (aspl + bspl); + polynomials[4][i] = 16 * (yMidDots[0][i] - ph0); + + if (mu > 0) { + final double ph1 = ydiff + 0.25 * (aspl - bspl); + polynomials[5][i] = 16 * (yMidDots[1][i] - ph1); + + if (mu > 1) { + final double ph2 = yp1 - yp0; + polynomials[6][i] = 16 * (yMidDots[2][i] - ph2 + polynomials[4][i]); + + if (mu > 2) { + final double ph3 = 6 * (bspl - aspl); + polynomials[7][i] = 16 * (yMidDots[3][i] - ph3 + 3 * polynomials[5][i]); + + for (int j = 4; j <= mu; ++j) { + final double fac1 = 0.5 * j * (j - 1); + final double fac2 = 2 * fac1 * (j - 2) * (j - 3); + polynomials[j+4][i] = + 16 * (yMidDots[j][i] + fac1 * polynomials[j+2][i] - fac2 * polynomials[j][i]); + } + + } + } + } + } + + } + + /** Estimate interpolation error. + * @param scale scaling array + * @return estimate of the interpolation error + */ + public double estimateError(final double[] scale) { + double error = 0; + if (currentDegree >= 5) { + for (int i = 0; i < scale.length; ++i) { + final double e = polynomials[currentDegree][i] / scale[i]; + error += e * e; + } + error = FastMath.sqrt(error / scale.length) * errfac[currentDegree - 5]; + } + return error; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final int dimension = currentState.length; + + final double oneMinusTheta = 1.0 - theta; + final double theta05 = theta - 0.5; + final double tOmT = theta * oneMinusTheta; + final double t4 = tOmT * tOmT; + final double t4Dot = 2 * tOmT * (1 - 2 * theta); + final double dot1 = 1.0 / h; + final double dot2 = theta * (2 - 3 * theta) / h; + final double dot3 = ((3 * theta - 4) * theta + 1) / h; + + for (int i = 0; i < dimension; ++i) { + + final double p0 = polynomials[0][i]; + final double p1 = polynomials[1][i]; + final double p2 = polynomials[2][i]; + final double p3 = polynomials[3][i]; + interpolatedState[i] = p0 + theta * (p1 + oneMinusTheta * (p2 * theta + p3 * oneMinusTheta)); + interpolatedDerivatives[i] = dot1 * p1 + dot2 * p2 + dot3 * p3; + + if (currentDegree > 3) { + double cDot = 0; + double c = polynomials[currentDegree][i]; + for (int j = currentDegree - 1; j > 3; --j) { + final double d = 1.0 / (j - 3); + cDot = d * (theta05 * cDot + c); + c = polynomials[j][i] + c * d * theta05; + } + interpolatedState[i] += t4 * c; + interpolatedDerivatives[i] += (t4 * cDot + t4Dot * c) / h; + } + + } + + if (h == 0) { + // in this degenerated case, the previous computation leads to NaN for derivatives + // we fix this by using the derivatives at midpoint + System.arraycopy(yMidDots[1], 0, interpolatedDerivatives, 0, dimension); + } + + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + final int dimension = (currentState == null) ? -1 : currentState.length; + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes (but not the temporary vectors) + out.writeInt(currentDegree); + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + out.writeDouble(polynomials[k][l]); + } + } + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the base class + final double t = readBaseExternal(in); + final int dimension = (currentState == null) ? -1 : currentState.length; + + // read the local attributes + final int degree = in.readInt(); + resetTables(degree); + currentDegree = degree; + + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + polynomials[k][l] = in.readDouble(); + } + } + + // we can now set the interpolated time and state + setInterpolatedTime(t); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java new file mode 100644 index 000000000..dabc80385 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements the 5(4) Higham and Hall integrator for + * Ordinary Differential Equations. + * + *

    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.

    + * + * @param the type of the field elements + * @since 3.6 + */ + +public class HighamHall54FieldIntegrator> + extends EmbeddedRungeKuttaFieldIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Higham-Hall 5(4)"; + + /** Error weights Butcher array. */ + private final T[] e ; + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public HighamHall54FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, -1, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e = MathArrays.buildArray(field, 7); + e[0] = fraction(-1, 20); + e[1] = field.getZero(); + e[2] = fraction(81, 160); + e[3] = fraction(-6, 5); + e[4] = fraction(25, 32); + e[5] = fraction( 1, 16); + e[6] = fraction(-1, 10); + } + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public HighamHall54FieldIntegrator(final Field field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, -1, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e = MathArrays.buildArray(field, 7); + e[0] = fraction(-1, 20); + e[1] = field.getZero(); + e[2] = fraction(81, 160); + e[3] = fraction(-6, 5); + e[4] = fraction(25, 32); + e[5] = fraction( 1, 16); + e[6] = fraction(-1, 10); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = fraction(2, 9); + c[1] = fraction(1, 3); + c[2] = fraction(1, 2); + c[3] = fraction(3, 5); + c[4] = getField().getOne(); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction( 2, 9); + a[1][0] = fraction( 1, 12); + a[1][1] = fraction( 1, 4); + a[2][0] = fraction( 1, 8); + a[2][1] = getField().getZero(); + a[2][2] = fraction( 3, 8); + a[3][0] = fraction( 91, 500); + a[3][1] = fraction( -27, 100); + a[3][2] = fraction( 78, 125); + a[3][3] = fraction( 8, 125); + a[4][0] = fraction( -11, 20); + a[4][1] = fraction( 27, 20); + a[4][2] = fraction( 12, 5); + a[4][3] = fraction( -36, 5); + a[4][4] = fraction( 5, 1); + a[5][0] = fraction( 1, 12); + a[5][1] = getField().getZero(); + a[5][2] = fraction( 27, 32); + a[5][3] = fraction( -4, 3); + a[5][4] = fraction( 125, 96); + a[5][5] = fraction( 5, 48); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 1, 12); + b[1] = getField().getZero(); + b[2] = fraction( 27, 32); + b[3] = fraction( -4, 3); + b[4] = fraction(125, 96); + b[5] = fraction( 5, 48); + b[6] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected HighamHall54FieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, final FieldEquationsMapper mapper) { + return new HighamHall54FieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + + T error = getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + T errSum = yDotK[0][j].multiply(e[0]); + for (int l = 1; l < e.length; ++l) { + errSum = errSum.add(yDotK[l][j].multiply(e[l])); + } + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio = h.multiply(errSum).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java new file mode 100644 index 000000000..82ab310d0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Higham and Hall integrator. + * + * @see HighamHall54FieldIntegrator + * + * @param the type of the field elements + * @since 3.6 + */ + +class HighamHall54FieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + HighamHall54FieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected HighamHall54FieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new HighamHall54FieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T bDot0 = theta.multiply(theta.multiply(theta.multiply( -10.0 ).add( 16.0 )).add(-15.0 / 2.0)).add(1); + final T bDot1 = time.getField().getZero(); + final T bDot2 = theta.multiply(theta.multiply(theta.multiply( 135.0 / 2.0).add(-729.0 / 8.0)).add(459.0 / 16.0)); + final T bDot3 = theta.multiply(theta.multiply(theta.multiply(-120.0 ).add( 152.0 )).add(-44.0 )); + final T bDot4 = theta.multiply(theta.multiply(theta.multiply( 125.0 / 2.0).add(-625.0 / 8.0)).add(375.0 / 16.0)); + final T bDot5 = theta.multiply( 5.0 / 8.0).multiply(theta.multiply(2).subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T b0 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply( -5.0 / 2.0).add( 16.0 / 3.0)).add(-15.0 / 4.0)).add(1)); + final T b1 = time.getField().getZero(); + final T b2 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(135.0 / 8.0).add(-243.0 / 8.0)).add(459.0 / 32.0))); + final T b3 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(-30.0 ).add( 152.0 / 3.0)).add(-22.0 ))); + final T b4 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(125.0 / 8.0).add(-625.0 / 24.0)).add(375.0 / 32.0))); + final T b5 = thetaH.multiply(theta.multiply(theta.multiply( 5.0 / 12.0 ).add( -5.0 / 16.0))); + interpolatedState = previousStateLinearCombination(b0, b1, b2, b3, b4, b5); + interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5); + } else { + final T theta2 = theta.multiply(theta); + final T h = thetaH.divide(theta); + final T b0 = h.multiply( theta.multiply(theta.multiply(theta.multiply(theta.multiply(-5.0 / 2.0).add( 16.0 / 3.0)).add( -15.0 / 4.0)).add( 1.0 )).add( -1.0 / 12.0)); + final T b1 = time.getField().getZero(); + final T b2 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( 135.0 / 8.0 ).add(-243.0 / 8.0)).add(459.0 / 32.0)).add( -27.0 / 32.0)); + final T b3 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( -30.0 ).add( 152.0 / 3.0)).add(-22.0 )).add( 4.0 / 3.0)); + final T b4 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( 125.0 / 8.0 ).add(-625.0 / 24.0)).add(375.0 / 32.0)).add(-125.0 / 96.0)); + final T b5 = h.multiply(theta2.multiply(theta.multiply( 5.0 / 12.0 ).add(-5.0 / 16.0)).add( -5.0 / 48.0)); + interpolatedState = currentStateLinearCombination(b0, b1, b2, b3, b4, b5); + interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java new file mode 100644 index 000000000..1c5407931 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 5(4) Higham and Hall integrator for + * Ordinary Differential Equations. + * + *

    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.

    + * + * @since 1.2 + */ + +public class HighamHall54Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Higham-Hall 5(4)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 2.0/9.0, 1.0/3.0, 1.0/2.0, 3.0/5.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + {2.0/9.0}, + {1.0/12.0, 1.0/4.0}, + {1.0/8.0, 0.0, 3.0/8.0}, + {91.0/500.0, -27.0/100.0, 78.0/125.0, 8.0/125.0}, + {-11.0/20.0, 27.0/20.0, 12.0/5.0, -36.0/5.0, 5.0}, + {1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0} + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0, 0.0 + }; + + /** Error weights Butcher array. */ + private static final double[] STATIC_E = { + -1.0/20.0, 0.0, 81.0/160.0, -6.0/5.0, 25.0/32.0, 1.0/16.0, -1.0/10.0 + }; + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + + double error = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + double errSum = STATIC_E[0] * yDotK[0][j]; + for (int l = 1; l < STATIC_E.length; ++l) { + errSum += STATIC_E[l] * yDotK[l][j]; + } + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java new file mode 100644 index 000000000..6e7abce69 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Higham and Hall integrator. + * + * @see HighamHall54Integrator + * + * @since 1.2 + */ + +class HighamHall54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * EmbeddedRungeKuttaIntegrator} uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public HighamHall54StepInterpolator() { + super(); + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + HighamHall54StepInterpolator(final HighamHall54StepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new HighamHall54StepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double bDot0 = 1 + theta * (-15.0/2.0 + theta * (16.0 - 10.0 * theta)); + final double bDot2 = theta * (459.0/16.0 + theta * (-729.0/8.0 + 135.0/2.0 * theta)); + final double bDot3 = theta * (-44.0 + theta * (152.0 - 120.0 * theta)); + final double bDot4 = theta * (375.0/16.0 + theta * (-625.0/8.0 + 125.0/2.0 * theta)); + final double bDot5 = theta * 5.0/8.0 * (2 * theta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double hTheta = h * theta; + final double b0 = hTheta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 - 5.0/2.0 * theta))); + final double b2 = hTheta * ( theta * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0))); + final double b3 = hTheta * ( theta * (-22.0 + theta * (152.0/3.0 + theta * -30.0))); + final double b4 = hTheta * ( theta * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0))); + final double b5 = hTheta * ( theta * (-5.0/16.0 + theta * 5.0/12.0)); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + interpolatedState[i] = + previousState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5; + interpolatedDerivatives[i] = + bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5; + } + } else { + final double theta2 = theta * theta; + final double b0 = h * (-1.0/12.0 + theta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 + theta * -5.0/2.0)))); + final double b2 = h * (-27.0/32.0 + theta2 * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0))); + final double b3 = h * (4.0/3.0 + theta2 * (-22.0 + theta * (152.0/3.0 + theta * -30.0))); + final double b4 = h * (-125.0/96.0 + theta2 * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0))); + final double b5 = h * (-5.0/48.0 + theta2 * (-5.0/16.0 + theta * 5.0/12.0)); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + interpolatedState[i] = + currentState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5; + interpolatedDerivatives[i] = + bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java new file mode 100644 index 000000000..2d1341acb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + + +/** + * This class implements the Luther sixth order Runge-Kutta + * integrator for Ordinary Differential Equations. + + *

    + * This method is described in H. A. Luther 1968 paper + * An explicit Sixth-Order Runge-Kutta Formula. + *

    + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *        0   |               0                     0                     0                     0                     0                     0
    + *        1   |               1                     0                     0                     0                     0                     0
    + *       1/2  |              3/8                   1/8                    0                     0                     0                     0
    + *       2/3  |              8/27                  2/27                  8/27                   0                     0                     0
    + *   (7-q)/14 | (  -21 +   9q)/392    (  -56 +   8q)/392    (  336 -  48q)/392    (  -63 +   3q)/392                  0                     0
    + *   (7+q)/14 | (-1155 - 255q)/1960   ( -280 -  40q)/1960   (    0 - 320q)/1960   (   63 + 363q)/1960   ( 2352 + 392q)/1960                 0
    + *        1   | (  330 + 105q)/180    (  120 +   0q)/180    ( -200 + 280q)/180    (  126 - 189q)/180    ( -686 - 126q)/180     ( 490 -  70q)/180
    + *            |--------------------------------------------------------------------------------------------------------------------------------------------------
    + *            |              1/20                   0                   16/45                  0                   49/180                 49/180         1/20
    + * 
    + * where q = √21

    + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class LutherFieldIntegrator> + extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build a fourth-order Luther integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public LutherFieldIntegrator(final Field field, final T step) { + super(field, "Luther", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T q = getField().getZero().add(21).sqrt(); + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = getField().getOne(); + c[1] = fraction(1, 2); + c[2] = fraction(2, 3); + c[3] = q.subtract(7).divide(-14); + c[4] = q.add(7).divide(14); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T q = getField().getZero().add(21).sqrt(); + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = getField().getOne(); + a[1][0] = fraction(3, 8); + a[1][1] = fraction(1, 8); + a[2][0] = fraction(8, 27); + a[2][1] = fraction(2, 27); + a[2][2] = a[2][0]; + a[3][0] = q.multiply( 9).add( -21).divide( 392); + a[3][1] = q.multiply( 8).add( -56).divide( 392); + a[3][2] = q.multiply( -48).add( 336).divide( 392); + a[3][3] = q.multiply( 3).add( -63).divide( 392); + a[4][0] = q.multiply(-255).add(-1155).divide(1960); + a[4][1] = q.multiply( -40).add( -280).divide(1960); + a[4][2] = q.multiply(-320) .divide(1960); + a[4][3] = q.multiply( 363).add( 63).divide(1960); + a[4][4] = q.multiply( 392).add( 2352).divide(1960); + a[5][0] = q.multiply( 105).add( 330).divide( 180); + a[5][1] = fraction(2, 3); + a[5][2] = q.multiply( 280).add( -200).divide( 180); + a[5][3] = q.multiply(-189).add( 126).divide( 180); + a[5][4] = q.multiply(-126).add( -686).divide( 180); + a[5][5] = q.multiply( -70).add( 490).divide( 180); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 1, 20); + b[1] = getField().getZero(); + b[2] = fraction(16, 45); + b[3] = getField().getZero(); + b[4] = fraction(49, 180); + b[5] = b[4]; + b[6] = b[0]; + + return b; + + } + + /** {@inheritDoc} */ + @Override + protected LutherFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new LutherFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java new file mode 100644 index 000000000..c4d5137dd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 6th order Luther integrator. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme.

    + * + * @see LutherFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class LutherFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** -49 - 49 q. */ + private final T c5a; + + /** 392 + 287 q. */ + private final T c5b; + + /** -637 - 357 q. */ + private final T c5c; + + /** 833 + 343 q. */ + private final T c5d; + + /** -49 + 49 q. */ + private final T c6a; + + /** -392 - 287 q. */ + private final T c6b; + + /** -637 + 357 q. */ + private final T c6c; + + /** 833 - 343 q. */ + private final T c6d; + + /** 49 + 49 q. */ + private final T d5a; + + /** -1372 - 847 q. */ + private final T d5b; + + /** 2254 + 1029 q */ + private final T d5c; + + /** 49 - 49 q. */ + private final T d6a; + + /** -1372 + 847 q. */ + private final T d6b; + + /** 2254 - 1029 q */ + private final T d6c; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + LutherFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T q = field.getZero().add(21).sqrt(); + c5a = q.multiply( -49).add( -49); + c5b = q.multiply( 287).add( 392); + c5c = q.multiply( -357).add( -637); + c5d = q.multiply( 343).add( 833); + c6a = q.multiply( 49).add( -49); + c6b = q.multiply( -287).add( 392); + c6c = q.multiply( 357).add( -637); + c6d = q.multiply( -343).add( 833); + d5a = q.multiply( 49).add( 49); + d5b = q.multiply( -847).add(-1372); + d5c = q.multiply( 1029).add( 2254); + d6a = q.multiply( -49).add( 49); + d6b = q.multiply( 847).add(-1372); + d6c = q.multiply(-1029).add( 2254); + } + + /** {@inheritDoc} */ + @Override + protected LutherFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new LutherFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + // the coefficients below have been computed by solving the + // order conditions from a theorem from Butcher (1963), using + // the method explained in Folkmar Bornemann paper "Runge-Kutta + // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich + // University of Technology, February 9, 2001 + // + + // the method is implemented in the rkcheck tool + // . + // Running it for order 5 gives the following order conditions + // for an interpolator: + // order 1 conditions + // \sum_{i=1}^{i=s}\left(b_{i} \right) =1 + // order 2 conditions + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2} + // order 3 conditions + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3} + // order 4 conditions + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4} + // order 5 conditions + // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20} + // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15} + // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5} + + // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve + // are the b_i for the interpolator. They are found by solving the above equations. + // For a given interpolator, some equations are redundant, so in our case when we select + // all equations from order 1 to 4, we still don't have enough independent equations + // to solve from b_1 to b_7. We need to also select one equation from order 5. Here, + // we selected the last equation. It appears this choice implied at least the last 3 equations + // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5. + // At the end, we get the b_i as polynomials in theta. + + final T coeffDot1 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( 21 ).add( -47 )).add( 36 )).add( -54 / 5.0)).add(1); + final T coeffDot2 = time.getField().getZero(); + final T coeffDot3 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( 112 ).add(-608 / 3.0)).add( 320 / 3.0 )).add(-208 / 15.0)); + final T coeffDot4 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( -567 / 5.0).add( 972 / 5.0)).add( -486 / 5.0 )).add( 324 / 25.0)); + final T coeffDot5 = theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(5)).add(c5b.divide(15))).add(c5c.divide(30))).add(c5d.divide(150))); + final T coeffDot6 = theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(5)).add(c6b.divide(15))).add(c6c.divide(30))).add(c6d.divide(150))); + final T coeffDot7 = theta.multiply(theta.multiply(theta.multiply( 3.0 ).add( -3 )).add( 3 / 5.0)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + + final T s = thetaH; + final T coeff1 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 21 / 5.0).add( -47 / 4.0)).add( 12 )).add( -27 / 5.0)).add(1)); + final T coeff2 = time.getField().getZero(); + final T coeff3 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 112 / 5.0).add(-152 / 3.0)).add( 320 / 9.0 )).add(-104 / 15.0))); + final T coeff4 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-567 / 25.0).add( 243 / 5.0)).add( -162 / 5.0 )).add( 162 / 25.0))); + final T coeff5 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(25)).add(c5b.divide(60))).add(c5c.divide(90))).add(c5d.divide(300)))); + final T coeff6 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(25)).add(c6b.divide(60))).add(c6c.divide(90))).add(c6d.divide(300)))); + final T coeff7 = s.multiply(theta.multiply(theta.multiply(theta.multiply( 3 / 4.0 ).add( -1 )).add( 3 / 10.0))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7); + } else { + + final T s = oneMinusThetaH; + final T coeff1 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( -21 / 5.0).add( 151 / 20.0)).add( -89 / 20.0)).add( 19 / 20.0)).add(- 1 / 20.0)); + final T coeff2 = time.getField().getZero(); + final T coeff3 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-112 / 5.0).add( 424 / 15.0)).add( -328 / 45.0)).add( -16 / 45.0)).add(-16 / 45.0)); + final T coeff4 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 567 / 25.0).add( -648 / 25.0)).add( 162 / 25.0)))); + final T coeff5 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d5a.divide(25)).add(d5b.divide(300))).add(d5c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0)); + final T coeff6 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d6a.divide(25)).add(d6b.divide(300))).add(d6c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0)); + final T coeff7 = s.multiply( theta.multiply(theta.multiply(theta.multiply( -3 / 4.0 ).add( 1 / 4.0)).add( -1 / 20.0)).add( -1 / 20.0)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java new file mode 100644 index 000000000..19f867356 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the Luther sixth order Runge-Kutta + * integrator for Ordinary Differential Equations. + + *

    + * This method is described in H. A. Luther 1968 paper + * An explicit Sixth-Order Runge-Kutta Formula. + *

    + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *        0   |               0                     0                     0                     0                     0                     0
    + *        1   |               1                     0                     0                     0                     0                     0
    + *       1/2  |              3/8                   1/8                    0                     0                     0                     0
    + *       2/3  |              8/27                  2/27                  8/27                   0                     0                     0
    + *   (7-q)/14 | (  -21 +   9q)/392    (  -56 +   8q)/392    (  336 -  48q)/392    (  -63 +   3q)/392                  0                     0
    + *   (7+q)/14 | (-1155 - 255q)/1960   ( -280 -  40q)/1960   (    0 - 320q)/1960   (   63 + 363q)/1960   ( 2352 + 392q)/1960                 0
    + *        1   | (  330 + 105q)/180    (  120 +   0q)/180    ( -200 + 280q)/180    (  126 - 189q)/180    ( -686 - 126q)/180     ( 490 -  70q)/180
    + *            |--------------------------------------------------------------------------------------------------------------------------------------------------
    + *            |              1/20                   0                   16/45                  0                   49/180                 49/180         1/20
    + * 
    + * where q = √21

    + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @since 3.3 + */ + +public class LutherIntegrator extends RungeKuttaIntegrator { + + /** Square root. */ + private static final double Q = FastMath.sqrt(21); + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0, 1.0 / 2.0, 2.0 / 3.0, (7.0 - Q) / 14.0, (7.0 + Q) / 14.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 }, + { 3.0 / 8.0, 1.0 / 8.0 }, + { 8.0 / 27.0, 2.0 / 27.0, 8.0 / 27.0 }, + { ( -21.0 + 9.0 * Q) / 392.0, ( -56.0 + 8.0 * Q) / 392.0, ( 336.0 - 48.0 * Q) / 392.0, (-63.0 + 3.0 * Q) / 392.0 }, + { (-1155.0 - 255.0 * Q) / 1960.0, (-280.0 - 40.0 * Q) / 1960.0, ( 0.0 - 320.0 * Q) / 1960.0, ( 63.0 + 363.0 * Q) / 1960.0, (2352.0 + 392.0 * Q) / 1960.0 }, + { ( 330.0 + 105.0 * Q) / 180.0, ( 120.0 + 0.0 * Q) / 180.0, (-200.0 + 280.0 * Q) / 180.0, (126.0 - 189.0 * Q) / 180.0, (-686.0 - 126.0 * Q) / 180.0, (490.0 - 70.0 * Q) / 180.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 20.0, 0, 16.0 / 45.0, 0, 49.0 / 180.0, 49.0 / 180.0, 1.0 / 20.0 + }; + + /** Simple constructor. + * Build a fourth-order Luther integrator with the given step. + * @param step integration step + */ + public LutherIntegrator(final double step) { + super("Luther", STATIC_C, STATIC_A, STATIC_B, new LutherStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java new file mode 100644 index 000000000..12f342918 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 6th order Luther integrator. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme.

    + * + * @see LutherIntegrator + * @since 3.3 + */ + +class LutherStepInterpolator extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20140416L; + + /** Square root. */ + private static final double Q = FastMath.sqrt(21); + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public LutherStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + LutherStepInterpolator(final LutherStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new LutherStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + // the coefficients below have been computed by solving the + // order conditions from a theorem from Butcher (1963), using + // the method explained in Folkmar Bornemann paper "Runge-Kutta + // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich + // University of Technology, February 9, 2001 + // + + // the method is implemented in the rkcheck tool + // . + // Running it for order 5 gives the following order conditions + // for an interpolator: + // order 1 conditions + // \sum_{i=1}^{i=s}\left(b_{i} \right) =1 + // order 2 conditions + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2} + // order 3 conditions + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3} + // order 4 conditions + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4} + // order 5 conditions + // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20} + // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15} + // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5} + + // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve + // are the b_i for the interpolator. They are found by solving the above equations. + // For a given interpolator, some equations are redundant, so in our case when we select + // all equations from order 1 to 4, we still don't have enough independent equations + // to solve from b_1 to b_7. We need to also select one equation from order 5. Here, + // we selected the last equation. It appears this choice implied at least the last 3 equations + // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5. + // At the end, we get the b_i as polynomials in theta. + + final double coeffDot1 = 1 + theta * ( -54 / 5.0 + theta * ( 36 + theta * ( -47 + theta * 21))); + final double coeffDot2 = 0; + final double coeffDot3 = theta * (-208 / 15.0 + theta * ( 320 / 3.0 + theta * (-608 / 3.0 + theta * 112))); + final double coeffDot4 = theta * ( 324 / 25.0 + theta * ( -486 / 5.0 + theta * ( 972 / 5.0 + theta * -567 / 5.0))); + final double coeffDot5 = theta * ((833 + 343 * Q) / 150.0 + theta * ((-637 - 357 * Q) / 30.0 + theta * ((392 + 287 * Q) / 15.0 + theta * (-49 - 49 * Q) / 5.0))); + final double coeffDot6 = theta * ((833 - 343 * Q) / 150.0 + theta * ((-637 + 357 * Q) / 30.0 + theta * ((392 - 287 * Q) / 15.0 + theta * (-49 + 49 * Q) / 5.0))); + final double coeffDot7 = theta * ( 3 / 5.0 + theta * ( -3 + theta * 3)); + + if ((previousState != null) && (theta <= 0.5)) { + + final double coeff1 = 1 + theta * ( -27 / 5.0 + theta * ( 12 + theta * ( -47 / 4.0 + theta * 21 / 5.0))); + final double coeff2 = 0; + final double coeff3 = theta * (-104 / 15.0 + theta * ( 320 / 9.0 + theta * (-152 / 3.0 + theta * 112 / 5.0))); + final double coeff4 = theta * ( 162 / 25.0 + theta * ( -162 / 5.0 + theta * ( 243 / 5.0 + theta * -567 / 25.0))); + final double coeff5 = theta * ((833 + 343 * Q) / 300.0 + theta * ((-637 - 357 * Q) / 90.0 + theta * ((392 + 287 * Q) / 60.0 + theta * (-49 - 49 * Q) / 25.0))); + final double coeff6 = theta * ((833 - 343 * Q) / 300.0 + theta * ((-637 + 357 * Q) / 90.0 + theta * ((392 - 287 * Q) / 60.0 + theta * (-49 + 49 * Q) / 25.0))); + final double coeff7 = theta * ( 3 / 10.0 + theta * ( -1 + theta * ( 3 / 4.0))); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + final double yDot5 = yDotK[4][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + interpolatedState[i] = previousState[i] + + theta * h * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + + coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7); + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + + coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7; + } + } else { + + final double coeff1 = -1 / 20.0 + theta * ( 19 / 20.0 + theta * ( -89 / 20.0 + theta * ( 151 / 20.0 + theta * -21 / 5.0))); + final double coeff2 = 0; + final double coeff3 = -16 / 45.0 + theta * ( -16 / 45.0 + theta * ( -328 / 45.0 + theta * ( 424 / 15.0 + theta * -112 / 5.0))); + final double coeff4 = theta * ( theta * ( 162 / 25.0 + theta * ( -648 / 25.0 + theta * 567 / 25.0))); + final double coeff5 = -49 / 180.0 + theta * ( -49 / 180.0 + theta * ((2254 + 1029 * Q) / 900.0 + theta * ((-1372 - 847 * Q) / 300.0 + theta * ( 49 + 49 * Q) / 25.0))); + final double coeff6 = -49 / 180.0 + theta * ( -49 / 180.0 + theta * ((2254 - 1029 * Q) / 900.0 + theta * ((-1372 + 847 * Q) / 300.0 + theta * ( 49 - 49 * Q) / 25.0))); + final double coeff7 = -1 / 20.0 + theta * ( -1 / 20.0 + theta * ( 1 / 4.0 + theta * ( -3 / 4.0))); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + final double yDot5 = yDotK[4][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + interpolatedState[i] = currentState[i] + + oneMinusThetaH * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + + coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7); + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + + coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java new file mode 100644 index 000000000..5a6bfdd5a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a second order Runge-Kutta integrator for + * Ordinary Differential Equations. + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |  0    0
    + *   1/2 | 1/2   0
    + *       |----------
    + *       |  0    1
    + * 
    + *

    + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * + * @param the type of the field elements + * @since 3.6 + */ + +public class MidpointFieldIntegrator> extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build a midpoint integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public MidpointFieldIntegrator(final Field field, final T step) { + super(field, "midpoint", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 1); + c[0] = getField().getOne().multiply(0.5); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 1, 1); + a[0][0] = fraction(1, 2); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 2); + b[0] = getField().getZero(); + b[1] = getField().getOne(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected MidpointFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new MidpointFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java new file mode 100644 index 000000000..1aa918ea7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for second order + * Runge-Kutta integrator. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + θ h [(1 - θ) y'1 + θ y'2] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + (1-θ) h [θ y'1 - (1+θ) y'2] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 and y'2 are the two + * evaluations of the derivatives already computed during the + * step.

    + * + * @see MidpointFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class MidpointFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + MidpointFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected MidpointFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new MidpointFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T coeffDot2 = theta.multiply(2); + final T coeffDot1 = time.getField().getOne().subtract(coeffDot2); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T coeff1 = theta.multiply(oneMinusThetaH); + final T coeff2 = theta.multiply(thetaH); + interpolatedState = previousStateLinearCombination(coeff1, coeff2); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2); + } else { + final T coeff1 = oneMinusThetaH.multiply(theta); + final T coeff2 = oneMinusThetaH.multiply(theta.add(1)).negate(); + interpolatedState = currentStateLinearCombination(coeff1, coeff2); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java new file mode 100644 index 000000000..c8c30c607 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements a second order Runge-Kutta integrator for + * Ordinary Differential Equations. + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |  0    0
    + *   1/2 | 1/2   0
    + *       |----------
    + *       |  0    1
    + * 
    + *

    + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * + * @since 1.2 + */ + +public class MidpointIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 0.0, 1.0 + }; + + /** Simple constructor. + * Build a midpoint integrator with the given step. + * @param step integration step + */ + public MidpointIntegrator(final double step) { + super("midpoint", STATIC_C, STATIC_A, STATIC_B, new MidpointStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java new file mode 100644 index 000000000..47889da2f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for second order + * Runge-Kutta integrator. + * + *

    This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + θ h [(1 - θ) y'1 + θ y'2] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + (1-θ) h [θ y'1 - (1+θ) y'2] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 and y'2 are the two + * evaluations of the derivatives already computed during the + * step.

    + * + * @see MidpointIntegrator + * @since 1.2 + */ + +class MidpointStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public MidpointStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + MidpointStepInterpolator(final MidpointStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new MidpointStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double coeffDot2 = 2 * theta; + final double coeffDot1 = 1 - coeffDot2; + + if ((previousState != null) && (theta <= 0.5)) { + final double coeff1 = theta * oneMinusThetaH; + final double coeff2 = theta * theta * h; + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + interpolatedState[i] = previousState[i] + coeff1 * yDot1 + coeff2 * yDot2; + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2; + } + } else { + final double coeff1 = oneMinusThetaH * theta; + final double coeff2 = oneMinusThetaH * (1.0 + theta); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + interpolatedState[i] = currentState[i] + coeff1 * yDot1 - coeff2 * yDot2; + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2; + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java new file mode 100644 index 000000000..74b35d346 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.AbstractFieldIntegrator; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldExpandableODE; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderFieldDifferentialEquations; +import com.fr.third.org.apache.commons.math3.ode.FieldODEState; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements the common part of all fixed step Runge-Kutta + * integrators for Ordinary Differential Equations. + * + *

    These methods are explicit Runge-Kutta methods, their Butcher + * arrays are as follows : + *

    + *    0  |
    + *   c2  | a21
    + *   c3  | a31  a32
    + *   ... |        ...
    + *   cs  | as1  as2  ...  ass-1
    + *       |--------------------------
    + *       |  b1   b2  ...   bs-1  bs
    + * 
    + *

    + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public abstract class RungeKuttaFieldIntegrator> + extends AbstractFieldIntegrator + implements FieldButcherArrayProvider { + + /** Time steps from Butcher array (without the first zero). */ + private final T[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final T[][] a; + + /** External weights for the high order method from Butcher array. */ + private final T[] b; + + /** Integration step. */ + private final T step; + + /** Simple constructor. + * Build a Runge-Kutta integrator with the given + * step. The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param step integration step + */ + protected RungeKuttaFieldIntegrator(final Field field, final String name, final T step) { + super(field, name); + this.c = getC(); + this.a = getA(); + this.b = getB(); + this.step = step.abs(); + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final int p, final int q) { + return getField().getZero().add(p).divide(q); + } + + /** Create an interpolator. + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param mapper equations mapper for the all equations + * @return external weights for the high order method from Butcher array + */ + protected abstract RungeKuttaFieldStepInterpolator createInterpolator(boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + FieldEquationsMapper mapper); + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative integrate(final FieldExpandableODE equations, + final FieldODEState initialState, final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y0 = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y0, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // create some internal working arrays + final int stages = c.length + 1; + T[] y = y0; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = MathArrays.buildArray(getField(), y0.length); + + // set up integration control objects + if (forward) { + if (getStepStart().getTime().add(step).subtract(finalTime).getReal() >= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } else { + setStepSize(step); + } + } else { + if (getStepStart().getTime().subtract(step).subtract(finalTime).getReal() <= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } else { + setStepSize(step.negate()); + } + } + + // main integration loop + setIsLastStep(false); + do { + + // first stage + y = equations.getMapper().mapState(getStepStart()); + yDotK[0] = equations.getMapper().mapDerivative(getStepStart()); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + final T stepEnd = getStepStart().getTime().add(getStepSize()); + final T[] yDotTmp = computeDerivatives(stepEnd, yTmp); + final FieldODEStateAndDerivative stateTmp = new FieldODEStateAndDerivative(stepEnd, yTmp, yDotTmp); + + // discrete events handling + System.arraycopy(yTmp, 0, y, 0, y0.length); + setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()), + finalTime)); + + if (!isLastStep()) { + + // stepsize control for next step + final T nextT = getStepStart().getTime().add(getStepSize()); + final boolean nextIsLast = forward ? + (nextT.subtract(finalTime).getReal() >= 0) : + (nextT.subtract(finalTime).getReal() <= 0); + if (nextIsLast) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + + /** Fast computation of a single step of ODE integration. + *

    This method is intended for the limited use case of + * very fast computation of only one step without using any of the + * rich features of general integrators that may take some time + * to set up (i.e. no step handlers, no events handlers, no additional + * states, no interpolators, no error control, no evaluations count, + * no sanity checks ...). It handles the strict minimum of computation, + * so it can be embedded in outer loops.

    + *

    + * This method is not used at all by the {@link #integrate(FieldExpandableODE, + * FieldODEState, RealFieldElement)} method. It also completely ignores the step set at + * construction time, and uses only a single step to go from {@code t0} to {@code t}. + *

    + *

    + * As this method does not use any of the state-dependent features of the integrator, + * it should be reasonably thread-safe if and only if the provided differential + * equations are themselves thread-safe. + *

    + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than {@code t0} for backward integration) + * @return state vector at {@code t} + */ + public T[] singleStep(final FirstOrderFieldDifferentialEquations equations, + final T t0, final T[] y0, final T t) { + + // create some internal working arrays + final T[] y = y0.clone(); + final int stages = c.length + 1; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = y0.clone(); + + // first stage + final T h = t.subtract(t0); + yDotK[0] = equations.computeDerivatives(t0, y); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(h.multiply(sum)); + } + + yDotK[k] = equations.computeDerivatives(t0.add(h.multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + y[j] = y[j].add(h.multiply(sum)); + } + + return y; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java new file mode 100644 index 000000000..2ca1fd3ec --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** This class represents an interpolator over the last step during an + * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators. + * + * @see RungeKuttaFieldIntegrator + * @see EmbeddedRungeKuttaFieldIntegrator + * + * @param the type of the field elements + * @since 3.6 + */ + +abstract class RungeKuttaFieldStepInterpolator> + extends AbstractFieldStepInterpolator { + + /** Field to which the time and state vector elements belong. */ + private final Field field; + + /** Slopes at the intermediate points. */ + private final T[][] yDotK; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + protected RungeKuttaFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(forward, globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, mapper); + this.field = field; + this.yDotK = MathArrays.buildArray(field, yDotK.length, -1); + for (int i = 0; i < yDotK.length; ++i) { + this.yDotK[i] = yDotK[i].clone(); + } + } + + /** {@inheritDoc} */ + @Override + protected RungeKuttaFieldStepInterpolator create(boolean newForward, + FieldODEStateAndDerivative newGlobalPreviousState, + FieldODEStateAndDerivative newGlobalCurrentState, + FieldODEStateAndDerivative newSoftPreviousState, + FieldODEStateAndDerivative newSoftCurrentState, + FieldEquationsMapper newMapper) { + return create(field, newForward, yDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** Create a new instance. + * @param newField field to which the time and state vector elements belong + * @param newForward integration direction indicator + * @param newYDotK slopes at the intermediate points + * @param newGlobalPreviousState start of the global step + * @param newGlobalCurrentState end of the global step + * @param newSoftPreviousState start of the restricted step + * @param newSoftCurrentState end of the restricted step + * @param newMapper equations mapper for the all equations + * @return a new instance + */ + protected abstract RungeKuttaFieldStepInterpolator create(Field newField, boolean newForward, T[][] newYDotK, + FieldODEStateAndDerivative newGlobalPreviousState, + FieldODEStateAndDerivative newGlobalCurrentState, + FieldODEStateAndDerivative newSoftPreviousState, + FieldODEStateAndDerivative newSoftCurrentState, + FieldEquationsMapper newMapper); + + /** Compute a state by linear combination added to previous state. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected final T[] previousStateLinearCombination(final T ... coefficients) { + return combine(getPreviousState().getState(), + coefficients); + } + + /** Compute a state by linear combination added to current state. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected T[] currentStateLinearCombination(final T ... coefficients) { + return combine(getCurrentState().getState(), + coefficients); + } + + /** Compute a state derivative by linear combination. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected T[] derivativeLinearCombination(final T ... coefficients) { + return combine(MathArrays.buildArray(field, yDotK[0].length), coefficients); + } + + /** Linearly combine arrays. + * @param a array to add to + * @param coefficients coefficients to apply to the method staged derivatives + * @return a itself, as a convenience for fluent API + */ + private T[] combine(final T[] a, final T ... coefficients) { + for (int i = 0; i < a.length; ++i) { + for (int k = 0; k < coefficients.length; ++k) { + a[i] = a[i].add(coefficients[k].multiply(yDotK[k][i])); + } + } + return a; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java new file mode 100644 index 000000000..2aae862ea --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoBracketingException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.AbstractIntegrator; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderDifferentialEquations; + +/** + * This class implements the common part of all fixed step Runge-Kutta + * integrators for Ordinary Differential Equations. + * + *

    These methods are explicit Runge-Kutta methods, their Butcher + * arrays are as follows : + *

    + *    0  |
    + *   c2  | a21
    + *   c3  | a31  a32
    + *   ... |        ...
    + *   cs  | as1  as2  ...  ass-1
    + *       |--------------------------
    + *       |  b1   b2  ...   bs-1  bs
    + * 
    + *

    + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @since 1.2 + */ + +public abstract class RungeKuttaIntegrator extends AbstractIntegrator { + + /** Time steps from Butcher array (without the first zero). */ + private final double[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final double[][] a; + + /** External weights for the high order method from Butcher array. */ + private final double[] b; + + /** Prototype of the step interpolator. */ + private final RungeKuttaStepInterpolator prototype; + + /** Integration step. */ + private final double step; + + /** Simple constructor. + * Build a Runge-Kutta integrator with the given + * step. The default step handler does nothing. + * @param name name of the method + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param step integration step + */ + protected RungeKuttaIntegrator(final String name, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double step) { + super(name); + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + this.step = FastMath.abs(step); + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + final double[] yTmp = y0.clone(); + final double[] yDotTmp = new double[y0.length]; + + // set up an interpolator sharing the integrator arrays + final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy(); + interpolator.reinitialize(this, yTmp, yDotK, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + // set up integration control objects + stepStart = equations.getTime(); + if (forward) { + if (stepStart + step >= t) { + stepSize = t - stepStart; + } else { + stepSize = step; + } + } else { + if (stepStart - step <= t) { + stepSize = t - stepStart; + } else { + stepSize = -step; + } + } + initIntegration(equations.getTime(), y0, t); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + + // first stage + computeDerivatives(stepStart, y, yDotK[0]); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + // discrete events handling + interpolator.storeTime(stepStart + stepSize); + System.arraycopy(yTmp, 0, y, 0, y0.length); + System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length); + stepStart = acceptStep(interpolator, y, yDotTmp, t); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + // stepsize control for next step + final double nextT = stepStart + stepSize; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + if (nextIsLast) { + stepSize = t - stepStart; + } + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + stepStart = Double.NaN; + stepSize = Double.NaN; + + } + + /** Fast computation of a single step of ODE integration. + *

    This method is intended for the limited use case of + * very fast computation of only one step without using any of the + * rich features of general integrators that may take some time + * to set up (i.e. no step handlers, no events handlers, no additional + * states, no interpolators, no error control, no evaluations count, + * no sanity checks ...). It handles the strict minimum of computation, + * so it can be embedded in outer loops.

    + *

    + * This method is not used at all by the {@link #integrate(ExpandableStatefulODE, double)} + * method. It also completely ignores the step set at construction time, and + * uses only a single step to go from {@code t0} to {@code t}. + *

    + *

    + * As this method does not use any of the state-dependent features of the integrator, + * it should be reasonably thread-safe if and only if the provided differential + * equations are themselves thread-safe. + *

    + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than {@code t0} for backward integration) + * @return state vector at {@code t} + */ + public double[] singleStep(final FirstOrderDifferentialEquations equations, + final double t0, final double[] y0, final double t) { + + // create some internal working arrays + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + final double[] yTmp = y0.clone(); + + // first stage + final double h = t - t0; + equations.computeDerivatives(t0, y, yDotK[0]); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + equations.computeDerivatives(t0 + c[k-1] * h, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + y[j] += h * sum; + } + + return y; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java new file mode 100644 index 000000000..d7783c04e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.AbstractIntegrator; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; + +/** This class represents an interpolator over the last step during an + * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators. + * + * @see RungeKuttaIntegrator + * @see EmbeddedRungeKuttaIntegrator + * + * @since 1.2 + */ + +abstract class RungeKuttaStepInterpolator + extends AbstractStepInterpolator { + + /** Previous state. */ + protected double[] previousState; + + /** Slopes at the intermediate points */ + protected double[][] yDotK; + + /** Reference to the integrator. */ + protected AbstractIntegrator integrator; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} and {@link + * EmbeddedRungeKuttaIntegrator} classes use the prototyping design + * pattern to create the step interpolators by cloning an + * uninitialized model and latter initializing the copy. + */ + protected RungeKuttaStepInterpolator() { + previousState = null; + yDotK = null; + integrator = null; + } + + /** Copy constructor. + + *

    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 not done automatically, it + * remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + * @param interpolator interpolator to copy from. + + */ + RungeKuttaStepInterpolator(final RungeKuttaStepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState != null) { + + previousState = interpolator.previousState.clone(); + + yDotK = new double[interpolator.yDotK.length][]; + for (int k = 0; k < interpolator.yDotK.length; ++k) { + yDotK[k] = interpolator.yDotK[k].clone(); + } + + } else { + previousState = null; + yDotK = null; + } + + // we cannot keep any reference to the equations in the copy + // the interpolator should have been finalized before + integrator = null; + + } + + /** Reinitialize the instance + *

    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 doFinalize 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.

    + * @param rkIntegrator integrator being used + * @param y reference to the integrator array holding the state at + * the end of the step + * @param yDotArray reference to the integrator array holding all the + * intermediate slopes + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + public void reinitialize(final AbstractIntegrator rkIntegrator, + final double[] y, final double[][] yDotArray, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + reinitialize(y, forward, primaryMapper, secondaryMappers); + this.previousState = null; + this.yDotK = yDotArray; + this.integrator = rkIntegrator; + } + + /** {@inheritDoc} */ + @Override + public void shift() { + previousState = currentState.clone(); + super.shift(); + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes + final int n = (currentState == null) ? -1 : currentState.length; + for (int i = 0; i < n; ++i) { + out.writeDouble(previousState[i]); + } + + final int kMax = (yDotK == null) ? -1 : yDotK.length; + out.writeInt(kMax); + for (int k = 0; k < kMax; ++k) { + for (int i = 0; i < n; ++i) { + out.writeDouble(yDotK[k][i]); + } + } + + // we do not save any reference to the equations + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the base class + final double t = readBaseExternal(in); + + // read the local attributes + final int n = (currentState == null) ? -1 : currentState.length; + if (n < 0) { + previousState = null; + } else { + previousState = new double[n]; + for (int i = 0; i < n; ++i) { + previousState[i] = in.readDouble(); + } + } + + final int kMax = in.readInt(); + yDotK = (kMax < 0) ? null : new double[kMax][]; + for (int k = 0; k < kMax; ++k) { + yDotK[k] = (n < 0) ? null : new double[n]; + for (int i = 0; i < n; ++i) { + yDotK[k][i] = in.readDouble(); + } + } + + integrator = null; + + if (currentState != null) { + // we can now set the interpolated time and state + setInterpolatedTime(t); + } else { + interpolatedTime = t; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java new file mode 100644 index 000000000..3d1b02b44 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements the 3/8 fourth order Runge-Kutta + * integrator for Ordinary Differential Equations. + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + *

    + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see LutherFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +public class ThreeEighthesFieldIntegrator> + extends RungeKuttaFieldIntegrator { + + /** Simple constructor. + * Build a 3/8 integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public ThreeEighthesFieldIntegrator(final Field field, final T step) { + super(field, "3/8", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = fraction(1, 3); + c[1] = c[0].add(c[0]); + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 3); + a[1][0] = a[0][0].negate(); + a[1][1] = getField().getOne(); + a[2][0] = getField().getOne(); + a[2][1] = getField().getOne().negate(); + a[2][2] = getField().getOne(); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 8); + b[1] = fraction(3, 8); + b[2] = b[1]; + b[3] = b[0]; + return b; + } + + /** {@inheritDoc} */ + @Override + protected ThreeEighthesFieldStepInterpolator + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldEquationsMapper mapper) { + return new ThreeEighthesFieldStepInterpolator(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java new file mode 100644 index 000000000..f8a332474 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the 3/8 fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/8) [ (8 - 15 θ + 8 θ2) y'1 + * + 3 * (15 θ - 12 θ2) y'2 + * + 3 θ y'3 + * + (-3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + * - (1 - θ) (h/8) [(1 - 7 θ + 8 θ2) y'1 + * + 3 (1 + θ - 4 θ2) y'2 + * + 3 (1 + θ) y'3 + * + (1 + θ + 4 θ2) y'4 + * ] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 to y'4 are the four + * evaluations of the derivatives already computed during the + * step.

    + * + * @see ThreeEighthesFieldIntegrator + * @param the type of the field elements + * @since 3.6 + */ + +class ThreeEighthesFieldStepInterpolator> + extends RungeKuttaFieldStepInterpolator { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + ThreeEighthesFieldStepInterpolator(final Field field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected ThreeEighthesFieldStepInterpolator create(final Field newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative newGlobalPreviousState, + final FieldODEStateAndDerivative newGlobalCurrentState, + final FieldODEStateAndDerivative newSoftPreviousState, + final FieldODEStateAndDerivative newSoftCurrentState, + final FieldEquationsMapper newMapper) { + return new ThreeEighthesFieldStepInterpolator(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(final FieldEquationsMapper mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T coeffDot3 = theta.multiply(0.75); + final T coeffDot1 = coeffDot3.multiply(theta.multiply(4).subtract(5)).add(1); + final T coeffDot2 = coeffDot3.multiply(theta.multiply(-6).add(5)); + final T coeffDot4 = coeffDot3.multiply(theta.multiply(2).subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T s = thetaH.divide(8); + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T coeff1 = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(15)).add(8)); + final T coeff2 = s.multiply(theta.multiply(5).subtract(fourTheta2)).multiply(3); + final T coeff3 = s.multiply(theta).multiply(3); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } else { + final T s = oneMinusThetaH.divide(-8); + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T thetaPlus1 = theta.add(1); + final T coeff1 = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(7)).add(1)); + final T coeff2 = s.multiply(thetaPlus1.subtract(fourTheta2)).multiply(3); + final T coeff3 = s.multiply(thetaPlus1).multiply(3); + final T coeff4 = s.multiply(thetaPlus1.add(fourTheta2)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } + + return new FieldODEStateAndDerivative(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java new file mode 100644 index 000000000..b6a591b9d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements the 3/8 fourth order Runge-Kutta + * integrator for Ordinary Differential Equations. + * + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    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
    + * 
    + *

    + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class ThreeEighthesIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 3.0, 2.0 / 3.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 3.0 }, + { -1.0 / 3.0, 1.0 }, + { 1.0, -1.0, 1.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 8.0, 3.0 / 8.0, 3.0 / 8.0, 1.0 / 8.0 + }; + + /** Simple constructor. + * Build a 3/8 integrator with the given step. + * @param step integration step + */ + public ThreeEighthesIntegrator(final double step) { + super("3/8", STATIC_C, STATIC_A, STATIC_B, new ThreeEighthesStepInterpolator(), step); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java new file mode 100644 index 000000000..cb5898fb3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.nonstiff; + +import com.fr.third.org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for the 3/8 fourth + * order Runge-Kutta integrator. + * + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + *

      + *
    • Using reference point at step start:
      + * y(tn + θ h) = y (tn) + * + θ (h/8) [ (8 - 15 θ + 8 θ2) y'1 + * + 3 * (15 θ - 12 θ2) y'2 + * + 3 θ y'3 + * + (-3 θ + 4 θ2) y'4 + * ] + *
    • + *
    • Using reference point at step end:
      + * y(tn + θ h) = y (tn + h) + * - (1 - θ) (h/8) [(1 - 7 θ + 8 θ2) y'1 + * + 3 (1 + θ - 4 θ2) y'2 + * + 3 (1 + θ) y'3 + * + (1 + θ + 4 θ2) y'4 + * ] + *
    • + *
    + *

    + * + * where θ belongs to [0 ; 1] and where y'1 to y'4 are the four + * evaluations of the derivatives already computed during the + * step.

    + * + * @see ThreeEighthesIntegrator + * @since 1.2 + */ + +class ThreeEighthesStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public ThreeEighthesStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + ThreeEighthesStepInterpolator(final ThreeEighthesStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new ThreeEighthesStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double coeffDot3 = 0.75 * theta; + final double coeffDot1 = coeffDot3 * (4 * theta - 5) + 1; + final double coeffDot2 = coeffDot3 * (5 - 6 * theta); + final double coeffDot4 = coeffDot3 * (2 * theta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double s = theta * h / 8.0; + final double fourTheta2 = 4 * theta * theta; + final double coeff1 = s * (8 - 15 * theta + 2 * fourTheta2); + final double coeff2 = 3 * s * (5 * theta - fourTheta2); + final double coeff3 = 3 * s * theta; + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + + } + } else { + final double s = oneMinusThetaH / 8.0; + final double fourTheta2 = 4 * theta * theta; + final double coeff1 = s * (1 - 7 * theta + 2 * fourTheta2); + final double coeff2 = 3 * s * (1 + theta - fourTheta2); + final double coeff3 = 3 * s * (1 + theta); + final double coeff4 = s * (1 + theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + + } + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/package-info.java new file mode 100644 index 000000000..0dc774ef4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/nonstiff/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides classes to solve non-stiff Ordinary Differential Equations problems. + *

    + * + * + */ +package com.fr.third.org.apache.commons.math3.ode.nonstiff; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/package-info.java new file mode 100644 index 000000000..b73eb44c6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/package-info.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. + */ +/** + * + *

    + * This package provides classes to solve Ordinary Differential Equations problems. + *

    + * + *

    + * This package solves Initial Value Problems of the form + * y'=f(t,y) with t0 and + * y(t0)=y0 known. The provided + * integrators compute an estimate of y(t) from + * t=t0 to t=t1. + * It is also possible to get thederivatives with respect to the initial state + * dy(t)/dy(t0) or the derivatives with + * respect to some ODE parameters dy(t)/dp. + *

    + * + *

    + * 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 + * com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator StepInterpolator} + * abstract class, which are made available to the user at the end of + * each step. + *

    + * + *

    + * 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. + *

    + * + *

    + * The user should describe his problem in his own classes + * (UserProblem in the diagram below) which should implement + * the {@link com.fr.third.org.apache.commons.math3.ode.FirstOrderDifferentialEquations + * FirstOrderDifferentialEquations} interface. Then he should pass it to + * the integrator he prefers among all the classes that implement the + * {@link com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator + * FirstOrderIntegrator} interface. + *

    + * + *

    + * 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 + * {@link com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator#integrate + * FirstOrderIntegrator.integrate} method. The second one should be used + * when more in-depth information is needed throughout the integration + * process. The user can register an object implementing the {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler StepHandler} interface or a + * {@link com.fr.third.org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer} + * object wrapping a user-specified object implementing the {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler} + * interface into the integrator before calling the {@link + * com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator#integrate + * FirstOrderIntegrator.integrate} method. The user object will be called + * appropriately during the integration process, allowing the user to + * process intermediate results. The default step handler does nothing. + *

    + * + *

    + * {@link com.fr.third.org.apache.commons.math3.ode.ContinuousOutputModel + * ContinuousOutputModel} is a special-purpose step handler that is able + * to store all steps and to provide transparent access to any + * intermediate result once the integration is over. An important feature + * of this class is that it implements the Serializable + * 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. + *

    + * + *

    + * Other default implementations of the {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler StepHandler} interface are + * available for general needs ({@link + * com.fr.third.org.apache.commons.math3.ode.sampling.DummyStepHandler DummyStepHandler}, {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer}) and custom + * implementations can be developed for specific needs. As an example, + * if an application is to be completely driven by the integration + * process, then most of the application code will be run inside a step + * handler specific to this application. + *

    + * + *

    + * 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 + * com.fr.third.org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator + * AdaptiveStepsizeIntegrator} abstract class). In this case, the step + * handler which is called after each successful step shows up the + * variable stepsize. The {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer} class can + * be used to convert the variable stepsize into a fixed stepsize that + * can be handled by classes implementing the {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler} + * interface. Adaptive stepsize integrators can automatically compute the + * initial stepsize by themselves, however the user can specify it if he + * prefers to retain full control over the integration or if the + * automatic guess is wrong. + *

    + * + *

    + * + * + * + * + * + * + * + * + * + *
    Fixed Step Integrators
    NameOrder
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.EulerIntegrator Euler}1
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.MidpointIntegrator Midpoint}2
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.ClassicalRungeKuttaIntegrator Classical Runge-Kutta}4
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.GillIntegrator Gill}4
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.ThreeEighthesIntegrator 3/8}4
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.LutherIntegrator Luther}6
    + *

    + * + * + * + * + * + * + * + * + * + * + *
    Adaptive Stepsize Integrators
    NameIntegration OrderError Estimation Order
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.HighamHall54Integrator Higham and Hall}54
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.DormandPrince54Integrator Dormand-Prince 5(4)}54
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator Dormand-Prince 8(5,3)}85 and 3
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.GraggBulirschStoerIntegrator Gragg-Bulirsch-Stoer}variable (up to 18 by default)variable
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator Adams-Bashforth}variablevariable
    {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator Adams-Moulton}variablevariable
    + *

    + * + *

    + * In the table above, the {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator + * Adams-Bashforth} and {@link com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator + * Adams-Moulton} integrators appear as variable-step ones. This is an experimental extension + * to the classical algorithms using the Nordsieck vector representation. + *

    + * + * + */ +package com.fr.third.org.apache.commons.math3.ode; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.java new file mode 100644 index 000000000..553c4f70b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderFieldIntegrator; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldEquationsMapper; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** This abstract class represents an interpolator over the last step + * during an ODE integration. + * + *

    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).

    + * + * @see FirstOrderFieldIntegrator + * @see StepHandler + * + * @param the type of the field elements + * @since 3.6 + */ + +public abstract class AbstractFieldStepInterpolator> + implements FieldStepInterpolator { + + /** Global previous state. */ + private final FieldODEStateAndDerivative globalPreviousState; + + /** Global current state. */ + private final FieldODEStateAndDerivative globalCurrentState; + + /** Soft previous state. */ + private final FieldODEStateAndDerivative softPreviousState; + + /** Soft current state. */ + private final FieldODEStateAndDerivative softCurrentState; + + /** integration direction. */ + private final boolean forward; + + /** Mapper for ODE equations primary and secondary components. */ + private FieldEquationsMapper mapper; + + /** Simple constructor. + * @param isForward integration direction indicator + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param equationsMapper mapper for ODE equations primary and secondary components + */ + protected AbstractFieldStepInterpolator(final boolean isForward, + final FieldODEStateAndDerivative globalPreviousState, + final FieldODEStateAndDerivative globalCurrentState, + final FieldODEStateAndDerivative softPreviousState, + final FieldODEStateAndDerivative softCurrentState, + final FieldEquationsMapper equationsMapper) { + this.forward = isForward; + this.globalPreviousState = globalPreviousState; + this.globalCurrentState = globalCurrentState; + this.softPreviousState = softPreviousState; + this.softCurrentState = softCurrentState; + this.mapper = equationsMapper; + } + + /** Create a new restricted version of the instance. + *

    + * The instance is not changed at all. + *

    + * @param previousState start of the restricted step + * @param currentState end of the restricted step + * @return restricted version of the instance + * @see #getPreviousState() + * @see #getCurrentState() + */ + public AbstractFieldStepInterpolator restrictStep(final FieldODEStateAndDerivative previousState, + final FieldODEStateAndDerivative currentState) { + return create(forward, globalPreviousState, globalCurrentState, previousState, currentState, mapper); + } + + /** Create a new instance. + * @param newForward integration direction indicator + * @param newGlobalPreviousState start of the global step + * @param newGlobalCurrentState end of the global step + * @param newSoftPreviousState start of the restricted step + * @param newSoftCurrentState end of the restricted step + * @param newMapper equations mapper for the all equations + * @return a new instance + */ + protected abstract AbstractFieldStepInterpolator create(boolean newForward, + FieldODEStateAndDerivative newGlobalPreviousState, + FieldODEStateAndDerivative newGlobalCurrentState, + FieldODEStateAndDerivative newSoftPreviousState, + FieldODEStateAndDerivative newSoftCurrentState, + FieldEquationsMapper newMapper); + + /** + * Get the previous global grid point state. + * @return previous global grid point state + */ + public FieldODEStateAndDerivative getGlobalPreviousState() { + return globalPreviousState; + } + + /** + * Get the current global grid point state. + * @return current global grid point state + */ + public FieldODEStateAndDerivative getGlobalCurrentState() { + return globalCurrentState; + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative getPreviousState() { + return softPreviousState; + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative getCurrentState() { + return softCurrentState; + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative getInterpolatedState(final T time) { + final T thetaH = time.subtract(globalPreviousState.getTime()); + final T oneMinusThetaH = globalCurrentState.getTime().subtract(time); + final T theta = thetaH.divide(globalCurrentState.getTime().subtract(globalPreviousState.getTime())); + return computeInterpolatedStateAndDerivatives(mapper, time, theta, thetaH, oneMinusThetaH); + } + + /** {@inheritDoc} */ + public boolean isForward() { + return forward; + } + + /** Compute the state and derivatives at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param equationsMapper mapper for ODE equations primary and secondary components + * @param time interpolation time + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param thetaH time gap between the previous time and the interpolated time + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @return interpolated state and derivatives + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + protected abstract FieldODEStateAndDerivative computeInterpolatedStateAndDerivatives(FieldEquationsMapper equationsMapper, + T time, T theta, + T thetaH, T oneMinusThetaH) + throws MaxCountExceededException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java new file mode 100644 index 000000000..0e37e0734 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java @@ -0,0 +1,608 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator; +import com.fr.third.org.apache.commons.math3.ode.SecondOrderIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.EmbeddedRungeKuttaIntegrator; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; + +/** This abstract class represents an interpolator over the last step + * during an ODE integration. + * + *

    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).

    + * + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepHandler + * + * @since 1.2 + * + */ + +public abstract class AbstractStepInterpolator + implements StepInterpolator { + + /** current time step */ + protected double h; + + /** current state */ + protected double[] currentState; + + /** interpolated time */ + protected double interpolatedTime; + + /** interpolated state */ + protected double[] interpolatedState; + + /** interpolated derivatives */ + protected double[] interpolatedDerivatives; + + /** interpolated primary state */ + protected double[] interpolatedPrimaryState; + + /** interpolated primary derivatives */ + protected double[] interpolatedPrimaryDerivatives; + + /** interpolated secondary state */ + protected double[][] interpolatedSecondaryState; + + /** interpolated secondary derivatives */ + protected double[][] interpolatedSecondaryDerivatives; + + /** global previous time */ + private double globalPreviousTime; + + /** global current time */ + private double globalCurrentTime; + + /** soft previous time */ + private double softPreviousTime; + + /** soft current time */ + private double softCurrentTime; + + /** indicate if the step has been finalized or not. */ + private boolean finalized; + + /** integration direction. */ + private boolean forward; + + /** indicator for dirty state. */ + private boolean dirtyState; + + /** Equations mapper for the primary equations set. */ + private EquationsMapper primaryMapper; + + /** Equations mappers for the secondary equations sets. */ + private EquationsMapper[] secondaryMappers; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. As an example, the {@link + * EmbeddedRungeKuttaIntegrator} + * class uses the prototyping design pattern to create the step + * interpolators by cloning an uninitialized model and latter + * initializing the copy. + */ + protected AbstractStepInterpolator() { + globalPreviousTime = Double.NaN; + globalCurrentTime = Double.NaN; + softPreviousTime = Double.NaN; + softCurrentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + currentState = null; + finalized = false; + this.forward = true; + this.dirtyState = true; + primaryMapper = null; + secondaryMappers = null; + allocateInterpolatedArrays(-1); + } + + /** Simple constructor. + * @param y reference to the integrator array holding the state at + * the end of the step + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + protected AbstractStepInterpolator(final double[] y, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + + globalPreviousTime = Double.NaN; + globalCurrentTime = Double.NaN; + softPreviousTime = Double.NaN; + softCurrentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + currentState = y; + finalized = false; + this.forward = forward; + this.dirtyState = true; + this.primaryMapper = primaryMapper; + this.secondaryMappers = (secondaryMappers == null) ? null : secondaryMappers.clone(); + allocateInterpolatedArrays(y.length); + + } + + /** Copy constructor. + + *

    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 not done + * automatically, it remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + * @param interpolator interpolator to copy from. + + */ + protected AbstractStepInterpolator(final AbstractStepInterpolator interpolator) { + + globalPreviousTime = interpolator.globalPreviousTime; + globalCurrentTime = interpolator.globalCurrentTime; + softPreviousTime = interpolator.softPreviousTime; + softCurrentTime = interpolator.softCurrentTime; + h = interpolator.h; + interpolatedTime = interpolator.interpolatedTime; + + if (interpolator.currentState == null) { + currentState = null; + primaryMapper = null; + secondaryMappers = null; + allocateInterpolatedArrays(-1); + } else { + currentState = interpolator.currentState.clone(); + interpolatedState = interpolator.interpolatedState.clone(); + interpolatedDerivatives = interpolator.interpolatedDerivatives.clone(); + interpolatedPrimaryState = interpolator.interpolatedPrimaryState.clone(); + interpolatedPrimaryDerivatives = interpolator.interpolatedPrimaryDerivatives.clone(); + interpolatedSecondaryState = new double[interpolator.interpolatedSecondaryState.length][]; + interpolatedSecondaryDerivatives = new double[interpolator.interpolatedSecondaryDerivatives.length][]; + for (int i = 0; i < interpolatedSecondaryState.length; ++i) { + interpolatedSecondaryState[i] = interpolator.interpolatedSecondaryState[i].clone(); + interpolatedSecondaryDerivatives[i] = interpolator.interpolatedSecondaryDerivatives[i].clone(); + } + } + + finalized = interpolator.finalized; + forward = interpolator.forward; + dirtyState = interpolator.dirtyState; + primaryMapper = interpolator.primaryMapper; + secondaryMappers = (interpolator.secondaryMappers == null) ? + null : interpolator.secondaryMappers.clone(); + + } + + /** Allocate the various interpolated states arrays. + * @param dimension total dimension (negative if arrays should be set to null) + */ + private void allocateInterpolatedArrays(final int dimension) { + if (dimension < 0) { + interpolatedState = null; + interpolatedDerivatives = null; + interpolatedPrimaryState = null; + interpolatedPrimaryDerivatives = null; + interpolatedSecondaryState = null; + interpolatedSecondaryDerivatives = null; + } else { + interpolatedState = new double[dimension]; + interpolatedDerivatives = new double[dimension]; + interpolatedPrimaryState = new double[primaryMapper.getDimension()]; + interpolatedPrimaryDerivatives = new double[primaryMapper.getDimension()]; + if (secondaryMappers == null) { + interpolatedSecondaryState = null; + interpolatedSecondaryDerivatives = null; + } else { + interpolatedSecondaryState = new double[secondaryMappers.length][]; + interpolatedSecondaryDerivatives = new double[secondaryMappers.length][]; + for (int i = 0; i < secondaryMappers.length; ++i) { + interpolatedSecondaryState[i] = new double[secondaryMappers[i].getDimension()]; + interpolatedSecondaryDerivatives[i] = new double[secondaryMappers[i].getDimension()]; + } + } + } + } + + /** Reinitialize the instance + * @param y reference to the integrator array holding the state at the end of the step + * @param isForward integration direction indicator + * @param primary equations mapper for the primary equations set + * @param secondary equations mappers for the secondary equations sets + */ + protected void reinitialize(final double[] y, final boolean isForward, + final EquationsMapper primary, + final EquationsMapper[] secondary) { + + globalPreviousTime = Double.NaN; + globalCurrentTime = Double.NaN; + softPreviousTime = Double.NaN; + softCurrentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + currentState = y; + finalized = false; + this.forward = isForward; + this.dirtyState = true; + this.primaryMapper = primary; + this.secondaryMappers = secondary.clone(); + allocateInterpolatedArrays(y.length); + + } + + /** {@inheritDoc} */ + public StepInterpolator copy() throws MaxCountExceededException { + + // finalize the step before performing copy + finalizeStep(); + + // create the new independent instance + return doCopy(); + + } + + /** Really copy the finalized instance. + *

    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. + *

    + * This method can be used to restrict a step and make it appear + * as if the original step was smaller. Calling this method + * only changes the value returned by {@link #getPreviousTime()}, + * it does not change any other property + *

    + * @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. + *

    + * This method can be used to restrict a step and make it appear + * as if the original step was smaller. Calling this method + * only changes the value returned by {@link #getCurrentTime()}, + * it does not change any other property + *

    + * @param softCurrentTime end of the restricted step + * @since 2.2 + */ + public void setSoftCurrentTime(final double softCurrentTime) { + this.softCurrentTime = softCurrentTime; + } + + /** + * Get the previous global grid point time. + * @return previous global grid point time + */ + public double getGlobalPreviousTime() { + return globalPreviousTime; + } + + /** + * Get the current global grid point time. + * @return current global grid point time + */ + public double getGlobalCurrentTime() { + return globalCurrentTime; + } + + /** + * Get the previous soft grid point time. + * @return previous soft grid point time + * @see #setSoftPreviousTime(double) + */ + public double getPreviousTime() { + return softPreviousTime; + } + + /** + * Get the current soft grid point time. + * @return current soft grid point time + * @see #setSoftCurrentTime(double) + */ + public double getCurrentTime() { + return softCurrentTime; + } + + /** {@inheritDoc} */ + public double getInterpolatedTime() { + return interpolatedTime; + } + + /** {@inheritDoc} */ + public void setInterpolatedTime(final double time) { + interpolatedTime = time; + dirtyState = true; + } + + /** {@inheritDoc} */ + public boolean isForward() { + return forward; + } + + /** Compute the state and derivatives at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + protected abstract void computeInterpolatedStateAndDerivatives(double theta, + double oneMinusThetaH) + throws MaxCountExceededException; + + /** Lazy evaluation of complete interpolated state. + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + private void evaluateCompleteInterpolatedState() + throws MaxCountExceededException { + // lazy evaluation of the state + if (dirtyState) { + final double oneMinusThetaH = globalCurrentTime - interpolatedTime; + final double theta = (h == 0) ? 0 : (h - oneMinusThetaH) / h; + computeInterpolatedStateAndDerivatives(theta, oneMinusThetaH); + dirtyState = false; + } + } + + /** {@inheritDoc} */ + public double[] getInterpolatedState() throws MaxCountExceededException { + evaluateCompleteInterpolatedState(); + primaryMapper.extractEquationData(interpolatedState, + interpolatedPrimaryState); + return interpolatedPrimaryState; + } + + /** {@inheritDoc} */ + public double[] getInterpolatedDerivatives() throws MaxCountExceededException { + evaluateCompleteInterpolatedState(); + primaryMapper.extractEquationData(interpolatedDerivatives, + interpolatedPrimaryDerivatives); + return interpolatedPrimaryDerivatives; + } + + /** {@inheritDoc} */ + public double[] getInterpolatedSecondaryState(final int index) throws MaxCountExceededException { + evaluateCompleteInterpolatedState(); + secondaryMappers[index].extractEquationData(interpolatedState, + interpolatedSecondaryState[index]); + return interpolatedSecondaryState[index]; + } + + /** {@inheritDoc} */ + public double[] getInterpolatedSecondaryDerivatives(final int index) throws MaxCountExceededException { + evaluateCompleteInterpolatedState(); + secondaryMappers[index].extractEquationData(interpolatedDerivatives, + interpolatedSecondaryDerivatives[index]); + return interpolatedSecondaryDerivatives[index]; + } + + /** + * Finalize the step. + + *

    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.

    + + *

    Once this method has been called, no 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.

    + + *

    Warning: 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.

    + + *

    This method calls the protected doFinalize method + * if it has never been called during this step and set a flag + * indicating that it has been called once. It is the + * doFinalize method which should perform the evaluations. + * This wrapping prevents from calling doFinalize several + * times and hence evaluating the differential equations too often. + * Therefore, subclasses are not allowed not reimplement it, they + * should rather reimplement doFinalize.

    + + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + + */ + public final void finalizeStep() throws MaxCountExceededException { + if (! finalized) { + doFinalize(); + finalized = true; + } + } + + /** + * Really finalize the step. + * The default implementation of this method does nothing. + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + protected void doFinalize() throws MaxCountExceededException { + } + + /** {@inheritDoc} */ + public abstract void writeExternal(ObjectOutput out) + throws IOException; + + /** {@inheritDoc} */ + public abstract void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException; + + /** Save the base state of the instance. + * This method performs step finalization if it has not been done + * before. + * @param out stream where to save the state + * @exception IOException in case of write error + */ + protected void writeBaseExternal(final ObjectOutput out) + throws IOException { + + if (currentState == null) { + out.writeInt(-1); + } else { + out.writeInt(currentState.length); + } + out.writeDouble(globalPreviousTime); + out.writeDouble(globalCurrentTime); + out.writeDouble(softPreviousTime); + out.writeDouble(softCurrentTime); + out.writeDouble(h); + out.writeBoolean(forward); + out.writeObject(primaryMapper); + out.write(secondaryMappers.length); + for (final EquationsMapper mapper : secondaryMappers) { + out.writeObject(mapper); + } + + if (currentState != null) { + for (int i = 0; i < currentState.length; ++i) { + out.writeDouble(currentState[i]); + } + } + + out.writeDouble(interpolatedTime); + + // we do not store the interpolated state, + // it will be recomputed as needed after reading + + try { + // finalize the step (and don't bother saving the now true flag) + finalizeStep(); + } catch (MaxCountExceededException mcee) { + final IOException ioe = new IOException(mcee.getLocalizedMessage()); + ioe.initCause(mcee); + throw ioe; + } + + } + + /** Read the base state of the instance. + * This method does neither set the interpolated + * time nor state. It is up to the derived class to reset it + * properly calling the {@link #setInterpolatedTime} method later, + * once all rest of the object state has been set up properly. + * @param in stream where to read the state from + * @return interpolated time to be set later by the caller + * @exception IOException in case of read error + * @exception ClassNotFoundException if an equation mapper class + * cannot be found + */ + protected double readBaseExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + final int dimension = in.readInt(); + globalPreviousTime = in.readDouble(); + globalCurrentTime = in.readDouble(); + softPreviousTime = in.readDouble(); + softCurrentTime = in.readDouble(); + h = in.readDouble(); + forward = in.readBoolean(); + primaryMapper = (EquationsMapper) in.readObject(); + secondaryMappers = new EquationsMapper[in.read()]; + for (int i = 0; i < secondaryMappers.length; ++i) { + secondaryMappers[i] = (EquationsMapper) in.readObject(); + } + dirtyState = true; + + if (dimension < 0) { + currentState = null; + } else { + currentState = new double[dimension]; + for (int i = 0; i < currentState.length; ++i) { + currentState[i] = in.readDouble(); + } + } + + // we do NOT handle the interpolated time and state here + interpolatedTime = Double.NaN; + allocateInterpolatedArrays(dimension); + + finalized = true; + + return in.readDouble(); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/DummyStepHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/DummyStepHandler.java new file mode 100644 index 000000000..4fa2a3ca6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/DummyStepHandler.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +/** + * This class is a step handler that does nothing. + + *

    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.

    + * + *

    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.

    + * + * @see StepHandler + * @since 1.2 + */ + +public class DummyStepHandler implements StepHandler { + + /** Private constructor. + * The constructor is private to prevent users from creating + * instances (Singleton design-pattern). + */ + private DummyStepHandler() { + } + + /** Get the only instance. + * @return the only instance + */ + public static DummyStepHandler getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public void init(double t0, double[] y0, double t) { + } + + /** + * Handle the last accepted step. + * This method does nothing in this class. + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range), it + * should build a local copy using the clone method and store this + * copy. + * @param isLast true if the step is the last one + */ + public void handleStep(final StepInterpolator interpolator, final boolean isLast) { + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

    We use here the Initialization On Demand Holder Idiom.

    + */ + 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java new file mode 100644 index 000000000..4597d586a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This interface represents a handler that should be called after + * each successful fixed step. + + *

    This interface should be implemented by anyone who is interested + * in getting the solution of an ordinary differential equation at + * fixed time steps. Objects implementing this interface should be + * wrapped within an instance of {@link FieldStepNormalizer} that itself + * is used as the general {@link FieldStepHandler} by the integrator. The + * {@link FieldStepNormalizer} object is called according to the integrator + * internal algorithms and it calls objects implementing this + * interface as necessary at fixed time steps.

    + * + * @see FieldStepHandler + * @see FieldStepNormalizer + * @see FieldStepInterpolator + * @param the type of the field elements + * @since 3.6 + */ + +public interface FieldFixedStepHandler> { + + /** Initialize step handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the step handler to initialize some internal data + * if needed. + *

    + * @param initialState initial time, state vector and derivative + * @param finalTime target time for the integration + */ + void init(FieldODEStateAndDerivative initialState, T finalTime); + + /** + * Handle the last accepted step + * @param state current value of the independent time variable, + * state vector and derivative + * For efficiency purposes, the {@link FieldStepNormalizer} class reuses + * the same array on each call, so if + * the instance wants to keep it across all calls (for example to + * provide at the end of the integration a complete array of all + * steps), it should build a local copy store this copy. + * @param isLast true if the step is the last one + */ + void handleStep(FieldODEStateAndDerivative state, boolean isLast); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepHandler.java new file mode 100644 index 000000000..fd88120b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepHandler.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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.ContinuousOutputModel; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderFieldIntegrator; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This interface represents a handler that should be called after + * each successful step. + * + *

    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.

    + * + * @see FirstOrderFieldIntegrator + * @see FieldStepInterpolator + * @param the type of the field elements + * @since 3.6 + */ + +public interface FieldStepHandler> { + + /** Initialize step handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the step handler to initialize some internal data + * if needed. + *

    + * @param initialState initial time, state vector and derivative + * @param finalTime target time for the integration + */ + void init(FieldODEStateAndDerivative initialState, T finalTime); + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range, as the + * {@link ContinuousOutputModel + * ContinuousOutputModel} class does), it should build a local copy + * using the clone method of the interpolator and store this copy. + * Keeping only a reference to the interpolator and reusing it will + * result in unpredictable behavior (potentially crashing the application). + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + void handleStep(FieldStepInterpolator interpolator, boolean isLast) + throws MaxCountExceededException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.java new file mode 100644 index 000000000..e47dc0c53 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.ode.FirstOrderFieldIntegrator; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** This interface represents an interpolator over the last step + * during an ODE integration. + * + *

    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).

    + * + * @param the type of the field elements + * @see FirstOrderFieldIntegrator + * @see FieldStepHandler + * @since 3.6 + */ + +public interface FieldStepInterpolator> { + + /** + * Get the state at previous grid point time. + * @return state at previous grid point time + */ + FieldODEStateAndDerivative getPreviousState(); + + /** + * Get the state at current grid point time. + * @return state at current grid point time + */ + FieldODEStateAndDerivative getCurrentState(); + + /** + * Get the state at interpolated time. + *

    Setting the time outside of the current step is allowed, but + * should be used with care since the accuracy of the interpolator will + * probably be very poor far from this step. This allowance has been + * added to simplify implementation of search algorithms near the + * step endpoints.

    + * @param time time of the interpolated point + * @return state at interpolated time + */ + FieldODEStateAndDerivative getInterpolatedState(T time); + + /** Check if the natural integration direction is forward. + *

    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.

    + * @return true if the integration variable (time) increases during + * integration + */ + boolean isForward(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java new file mode 100644 index 000000000..9b667a231 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class wraps an object implementing {@link FieldFixedStepHandler} + * into a {@link FieldStepHandler}. + + *

    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.

    + * + *

    The stepsize used is selected at construction time. The {@link + * FieldFixedStepHandler#handleStep handleStep} method of the underlying + * {@link FieldFixedStepHandler} object is called at normalized times. The + * normalized times can be influenced by the {@link StepNormalizerMode} and + * {@link StepNormalizerBounds}.

    + * + *

    There is no constraint on the integrator, it can use any time step + * it needs (time steps longer or shorter than the fixed time step and + * non-integer ratios are all allowed).

    + * + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Examples (step size = 0.5)
    Start timeEnd timeDirection{@link StepNormalizerMode Mode}{@link StepNormalizerBounds Bounds}Output
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}0.8, 1.3, 1.8, 2.3, 2.8
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}0.3, 0.8, 1.3, 1.8, 2.3, 2.8
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}0.8, 1.3, 1.8, 2.3, 2.8, 3.1
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}0.3, 0.8, 1.3, 1.8, 2.3, 2.8, 3.1
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}2.6, 2.1, 1.6, 1.1, 0.6
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}3.1, 2.6, 2.1, 1.6, 1.1, 0.6
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}2.6, 2.1, 1.6, 1.1, 0.6, 0.3
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}3.1, 2.6, 2.1, 1.6, 1.1, 0.6, 0.3
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}3.0, 2.5, 2.0, 1.5, 1.0, 0.5
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    + *

    + * + * @param the type of the field elements + * @see FieldStepHandler + * @see FieldFixedStepHandler + * @see StepNormalizerMode + * @see StepNormalizerBounds + * @since 3.6 + */ + +public class FieldStepNormalizer> implements FieldStepHandler { + + /** Fixed time step. */ + private double h; + + /** Underlying step handler. */ + private final FieldFixedStepHandler handler; + + /** First step state. */ + private FieldODEStateAndDerivative first; + + /** Last step step. */ + private FieldODEStateAndDerivative last; + + /** Integration direction indicator. */ + private boolean forward; + + /** The step normalizer bounds settings to use. */ + private final StepNormalizerBounds bounds; + + /** The step normalizer mode to use. */ + private final StepNormalizerMode mode; + + /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT} + * mode, and {@link StepNormalizerBounds#FIRST FIRST} bounds setting, for + * backwards compatibility. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + */ + public FieldStepNormalizer(final double h, final FieldFixedStepHandler handler) { + this(h, handler, StepNormalizerMode.INCREMENT, + StepNormalizerBounds.FIRST); + } + + /** Simple constructor. Uses {@link StepNormalizerBounds#FIRST FIRST} + * bounds setting. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param mode step normalizer mode to use + * @since 3.0 + */ + public FieldStepNormalizer(final double h, final FieldFixedStepHandler handler, + final StepNormalizerMode mode) { + this(h, handler, mode, StepNormalizerBounds.FIRST); + } + + /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT} + * mode. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param bounds step normalizer bounds setting to use + * @since 3.0 + */ + public FieldStepNormalizer(final double h, final FieldFixedStepHandler handler, + final StepNormalizerBounds bounds) { + this(h, handler, StepNormalizerMode.INCREMENT, bounds); + } + + /** Simple constructor. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param mode step normalizer mode to use + * @param bounds step normalizer bounds setting to use + * @since 3.0 + */ + public FieldStepNormalizer(final double h, final FieldFixedStepHandler handler, + final StepNormalizerMode mode, final StepNormalizerBounds bounds) { + this.h = FastMath.abs(h); + this.handler = handler; + this.mode = mode; + this.bounds = bounds; + first = null; + last = null; + forward = true; + } + + /** {@inheritDoc} */ + public void init(final FieldODEStateAndDerivative initialState, final T finalTime) { + + first = null; + last = null; + forward = true; + + // initialize the underlying handler + handler.init(initialState, finalTime); + + } + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range), it + * should build a local copy using the clone method and store this + * copy. + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + public void handleStep(final FieldStepInterpolator interpolator, final boolean isLast) + throws MaxCountExceededException { + // The first time, update the last state with the start information. + if (last == null) { + + first = interpolator.getPreviousState(); + last = first; + + // Take the integration direction into account. + forward = interpolator.isForward(); + if (!forward) { + h = -h; + } + } + + // Calculate next normalized step time. + T nextTime = (mode == StepNormalizerMode.INCREMENT) ? + last.getTime().add(h) : + last.getTime().getField().getZero().add((FastMath.floor(last.getTime().getReal() / h) + 1) * h); + if (mode == StepNormalizerMode.MULTIPLES && + Precision.equals(nextTime.getReal(), last.getTime().getReal(), 1)) { + nextTime = nextTime.add(h); + } + + // Process normalized steps as long as they are in the current step. + boolean nextInStep = isNextInStep(nextTime, interpolator); + while (nextInStep) { + // Output the stored previous step. + doNormalizedStep(false); + + // Store the next step as last step. + last = interpolator.getInterpolatedState(nextTime); + + // Move on to the next step. + nextTime = nextTime.add(h); + nextInStep = isNextInStep(nextTime, interpolator); + } + + if (isLast) { + // There will be no more steps. The stored one should be given to + // the handler. We may have to output one more step. Only the last + // one of those should be flagged as being the last. + final boolean addLast = bounds.lastIncluded() && + last.getTime().getReal() != interpolator.getCurrentState().getTime().getReal(); + doNormalizedStep(!addLast); + if (addLast) { + last = interpolator.getCurrentState(); + doNormalizedStep(true); + } + } + } + + /** + * Returns a value indicating whether the next normalized time is in the + * current step. + * @param nextTime the next normalized time + * @param interpolator interpolator for the last accepted step, to use to + * get the end time of the current step + * @return value indicating whether the next normalized time is in the + * current step + */ + private boolean isNextInStep(final T nextTime, final FieldStepInterpolator interpolator) { + return forward ? + nextTime.getReal() <= interpolator.getCurrentState().getTime().getReal() : + nextTime.getReal() >= interpolator.getCurrentState().getTime().getReal(); + } + + /** + * Invokes the underlying step handler for the current normalized step. + * @param isLast true if the step is the last one + */ + private void doNormalizedStep(final boolean isLast) { + if (!bounds.firstIncluded() && first.getTime().getReal() == last.getTime().getReal()) { + return; + } + handler.handleStep(last, isLast); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FixedStepHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FixedStepHandler.java new file mode 100644 index 000000000..eaf6c5e6a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/FixedStepHandler.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + + +/** + * This interface represents a handler that should be called after + * each successful fixed step. + + *

    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.

    + * + * @see StepHandler + * @see StepNormalizer + * @since 1.2 + */ + +public interface FixedStepHandler { + + /** Initialize step handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the step handler to initialize some internal data + * if needed. + *

    + * @param t0 start value of the independent time variable + * @param y0 array containing the start value of the state vector + * @param t target time for the integration + */ + void init(double t0, double[] y0, double t); + + /** + * Handle the last accepted step + * @param t time of the current step + * @param y state vector at t. For efficiency purposes, the {@link + * StepNormalizer} class reuses the same array on each call, so if + * the instance wants to keep it across all calls (for example to + * provide at the end of the integration a complete array of all + * steps), it should build a local copy store this copy. + * @param yDot derivatives of the state vector state vector at t. + * For efficiency purposes, the {@link StepNormalizer} class reuses + * the same array on each call, so if + * the instance wants to keep it across all calls (for example to + * provide at the end of the integration a complete array of all + * steps), it should build a local copy store this copy. + * @param isLast true if the step is the last one + */ + void handleStep(double t, double[] y, double[] yDot, boolean isLast); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.java new file mode 100644 index 000000000..21ec91cf8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator; +import com.fr.third.org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.ode.EquationsMapper; + +/** + * This class implements an interpolator for integrators using Nordsieck representation. + * + *

    This interpolator computes dense output around the current point. + * The interpolation equation is based on Taylor series formulas. + * + * @see AdamsBashforthIntegrator + * @see AdamsMoultonIntegrator + * @since 2.0 + */ + +public class NordsieckStepInterpolator extends AbstractStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = -7179861704951334960L; + + /** State variation. */ + protected double[] stateVariation; + + /** Step size used in the first scaled derivative and Nordsieck vector. */ + private double scalingH; + + /** Reference time for all arrays. + *

    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. + *

    + */ + 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. + *

    Beware that all arrays must be references to integrator + * arrays, in order to ensure proper update without copy.

    + * @param y reference to the integrator array holding the state at + * the end of the step + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + @Override + public void reinitialize(final double[] y, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + super.reinitialize(y, forward, primaryMapper, secondaryMappers); + stateVariation = new double[y.length]; + } + + /** Reinitialize the instance. + *

    Beware that all arrays must be references to integrator + * arrays, in order to ensure proper update without copy.

    + * @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. + *

    Since the scaled and Nordsieck arrays are shared with the caller, + * this method has the side effect of rescaling this arrays in the caller too.

    + * @param 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. + *

    This method is aimed at computing y(tinterpolation) + * -y(tcurrent) accurately by avoiding the cancellation errors + * that would occur if the subtraction were performed explicitly.

    + *

    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.

    + * @return state vector at time {@link #getInterpolatedTime} + * @see #getInterpolatedDerivatives() + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + public double[] getInterpolatedStateVariation() throws MaxCountExceededException { + // compute and ignore interpolated state + // to make sure state variation is computed as a side effect + getInterpolatedState(); + return stateVariation; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, final double oneMinusThetaH) { + + final double x = interpolatedTime - referenceTime; + final double normalizedAbscissa = x / scalingH; + + Arrays.fill(stateVariation, 0.0); + Arrays.fill(interpolatedDerivatives, 0.0); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + final double[][] nData = nordsieck.getDataRef(); + for (int i = nData.length - 1; i >= 0; --i) { + final int order = i + 2; + final double[] nDataI = nData[i]; + final double power = FastMath.pow(normalizedAbscissa, order); + for (int j = 0; j < nDataI.length; ++j) { + final double d = nDataI[j] * power; + stateVariation[j] += d; + interpolatedDerivatives[j] += order * d; + } + } + + for (int j = 0; j < currentState.length; ++j) { + stateVariation[j] += scaled[j] * normalizedAbscissa; + interpolatedState[j] = currentState[j] + stateVariation[j]; + interpolatedDerivatives[j] = + (interpolatedDerivatives[j] + scaled[j] * normalizedAbscissa) / x; + } + + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes + out.writeDouble(scalingH); + out.writeDouble(referenceTime); + + final int n = (currentState == null) ? -1 : currentState.length; + if (scaled == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + for (int j = 0; j < n; ++j) { + out.writeDouble(scaled[j]); + } + } + + if (nordsieck == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeObject(nordsieck); + } + + // we don't save state variation, it will be recomputed + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the base class + final double t = readBaseExternal(in); + + // read the local attributes + scalingH = in.readDouble(); + referenceTime = in.readDouble(); + + final int n = (currentState == null) ? -1 : currentState.length; + final boolean hasScaled = in.readBoolean(); + if (hasScaled) { + scaled = new double[n]; + for (int j = 0; j < n; ++j) { + scaled[j] = in.readDouble(); + } + } else { + scaled = null; + } + + final boolean hasNordsieck = in.readBoolean(); + if (hasNordsieck) { + nordsieck = (Array2DRowRealMatrix) in.readObject(); + } else { + nordsieck = null; + } + + if (hasScaled && hasNordsieck) { + // we can now set the interpolated time and state + stateVariation = new double[n]; + setInterpolatedTime(t); + } else { + stateVariation = null; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepHandler.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepHandler.java new file mode 100644 index 000000000..accbd68b5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.ContinuousOutputModel; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator; +import com.fr.third.org.apache.commons.math3.ode.SecondOrderIntegrator; + + +/** + * This interface represents a handler that should be called after + * each successful step. + * + *

    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.

    + * + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepInterpolator + * @since 1.2 + */ + +public interface StepHandler { + + /** Initialize step handler at the start of an ODE integration. + *

    + * This method is called once at the start of the integration. It + * may be used by the step handler to initialize some internal data + * if needed. + *

    + * @param t0 start value of the independent time variable + * @param y0 array containing the start value of the state vector + * @param t target time for the integration + */ + void init(double t0, double[] y0, double t); + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range, as the + * {@link ContinuousOutputModel + * ContinuousOutputModel} class does), it should build a local copy + * using the clone method of the interpolator and store this copy. + * Keeping only a reference to the interpolator and reusing it will + * result in unpredictable behavior (potentially crashing the application). + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + void handleStep(StepInterpolator interpolator, boolean isLast) + throws MaxCountExceededException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepInterpolator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepInterpolator.java new file mode 100644 index 000000000..08747e1c0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepInterpolator.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 com.fr.third.org.apache.commons.math3.ode.sampling; + +import java.io.Externalizable; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.ode.ExpandableStatefulODE; +import com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator; +import com.fr.third.org.apache.commons.math3.ode.SecondOrderIntegrator; +import com.fr.third.org.apache.commons.math3.ode.SecondaryEquations; + +/** This interface represents an interpolator over the last step + * during an ODE integration. + * + *

    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).

    + *

    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 never 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 must be copied using the dedicated + * {@link #copy()} method. + *

    + * + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepHandler + * @since 1.2 + */ + +public interface StepInterpolator extends Externalizable { + + /** + * Get the previous grid point time. + * @return previous grid point time + */ + double getPreviousTime(); + + /** + * Get the current grid point time. + * @return current grid point time + */ + double getCurrentTime(); + + /** + * Get the time of the interpolated point. + * If {@link #setInterpolatedTime} has not been called, it returns + * the current grid point time. + * @return interpolation point time + */ + double getInterpolatedTime(); + + /** + * Set the time of the interpolated point. + *

    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.

    + *

    Setting the time changes the instance internal state. This includes + * the internal arrays returned in {@link #getInterpolatedState()}, + * {@link #getInterpolatedDerivatives()}, {@link + * #getInterpolatedSecondaryState(int)} and {@link + * #getInterpolatedSecondaryDerivatives(int)}. So if their content must be preserved + * across several calls, user must copy them.

    + * @param time time of the interpolated point + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + * @see #getInterpolatedSecondaryDerivatives(int) + */ + void setInterpolatedTime(double time); + + /** + * Get the state vector of the interpolated point. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @return state vector at time {@link #getInterpolatedTime} + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + * @see #getInterpolatedSecondaryDerivatives(int) + * @see #setInterpolatedTime(double) + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + double[] getInterpolatedState() throws MaxCountExceededException; + + /** + * Get the derivatives of the state vector of the interpolated point. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @return derivatives of the state vector at time {@link #getInterpolatedTime} + * @see #getInterpolatedState() + * @see #getInterpolatedSecondaryState(int) + * @see #getInterpolatedSecondaryDerivatives(int) + * @see #setInterpolatedTime(double) + * @since 2.0 + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + double[] getInterpolatedDerivatives() throws MaxCountExceededException; + + /** Get the interpolated secondary state corresponding to the secondary equations. + *

    The returned vector is a reference to a reused array, so + * it should not be modified and it should be copied if it needs + * to be preserved across several calls to the associated + * {@link #setInterpolatedTime(double)} method.

    + * @param index index of the secondary set, as returned by {@link + * ExpandableStatefulODE#addSecondaryEquations( + *SecondaryEquations) + * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)} + * @return interpolated secondary state at the current interpolation date + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryDerivatives(int) + * @see #setInterpolatedTime(double) + * @since 3.0 + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + double[] getInterpolatedSecondaryState(int index) throws MaxCountExceededException; + + /** Get the interpolated secondary derivatives corresponding to the secondary equations. + *

    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.

    + * @param index index of the secondary set, as returned by {@link + * ExpandableStatefulODE#addSecondaryEquations( + *SecondaryEquations) + * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)} + * @return interpolated secondary derivatives at the current interpolation date + * @see #getInterpolatedState() + * @see #getInterpolatedDerivatives() + * @see #getInterpolatedSecondaryState(int) + * @see #setInterpolatedTime(double) + * @since 3.0 + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + */ + double[] getInterpolatedSecondaryDerivatives(int index) throws MaxCountExceededException; + + /** Check if the natural integration direction is forward. + *

    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.

    + * @return true if the integration variable (time) increases during + * integration + */ + boolean isForward(); + + /** Copy the instance. + *

    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.

    + * @return a deep copy of the instance, which can be used independently. + * @see #setInterpolatedTime(double) + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * during step finalization + */ + StepInterpolator copy() throws MaxCountExceededException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizer.java new file mode 100644 index 000000000..570cc9664 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizer.java @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class wraps an object implementing {@link FixedStepHandler} + * into a {@link StepHandler}. + + *

    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.

    + * + *

    The stepsize used is selected at construction time. The {@link + * FixedStepHandler#handleStep handleStep} method of the underlying + * {@link FixedStepHandler} object is called at normalized times. The + * normalized times can be influenced by the {@link StepNormalizerMode} and + * {@link StepNormalizerBounds}.

    + * + *

    There is no constraint on the integrator, it can use any time step + * it needs (time steps longer or shorter than the fixed time step and + * non-integer ratios are all allowed).

    + * + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Examples (step size = 0.5)
    Start timeEnd timeDirection{@link StepNormalizerMode Mode}{@link StepNormalizerBounds Bounds}Output
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}0.8, 1.3, 1.8, 2.3, 2.8
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}0.3, 0.8, 1.3, 1.8, 2.3, 2.8
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}0.8, 1.3, 1.8, 2.3, 2.8, 3.1
    0.33.1forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}0.3, 0.8, 1.3, 1.8, 2.3, 2.8, 3.1
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1
    0.33.1forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    0.03.0forward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}2.6, 2.1, 1.6, 1.1, 0.6
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}3.1, 2.6, 2.1, 1.6, 1.1, 0.6
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}2.6, 2.1, 1.6, 1.1, 0.6, 0.3
    3.10.3backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}3.1, 2.6, 2.1, 1.6, 1.1, 0.6, 0.3
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}3.0, 2.5, 2.0, 1.5, 1.0, 0.5
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3
    3.10.3backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#NEITHER NEITHER}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#FIRST FIRST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#LAST LAST}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#INCREMENT INCREMENT}{@link StepNormalizerBounds#BOTH BOTH}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#NEITHER NEITHER}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#FIRST FIRST}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#LAST LAST}2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    3.00.0backward{@link StepNormalizerMode#MULTIPLES MULTIPLES}{@link StepNormalizerBounds#BOTH BOTH}3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0
    + *

    + * + * @see StepHandler + * @see FixedStepHandler + * @see StepNormalizerMode + * @see StepNormalizerBounds + * @since 1.2 + */ + +public class StepNormalizer implements StepHandler { + /** Fixed time step. */ + private double h; + + /** Underlying step handler. */ + private final FixedStepHandler handler; + + /** First step time. */ + private double firstTime; + + /** Last step time. */ + private double lastTime; + + /** Last state vector. */ + private double[] lastState; + + /** Last derivatives vector. */ + private double[] lastDerivatives; + + /** Integration direction indicator. */ + private boolean forward; + + /** The step normalizer bounds settings to use. */ + private final StepNormalizerBounds bounds; + + /** The step normalizer mode to use. */ + private final StepNormalizerMode mode; + + /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT} + * mode, and {@link StepNormalizerBounds#FIRST FIRST} bounds setting, for + * backwards compatibility. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + */ + public StepNormalizer(final double h, final FixedStepHandler handler) { + this(h, handler, StepNormalizerMode.INCREMENT, + StepNormalizerBounds.FIRST); + } + + /** Simple constructor. Uses {@link StepNormalizerBounds#FIRST FIRST} + * bounds setting. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param mode step normalizer mode to use + * @since 3.0 + */ + public StepNormalizer(final double h, final FixedStepHandler handler, + final StepNormalizerMode mode) { + this(h, handler, mode, StepNormalizerBounds.FIRST); + } + + /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT} + * mode. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param bounds step normalizer bounds setting to use + * @since 3.0 + */ + public StepNormalizer(final double h, final FixedStepHandler handler, + final StepNormalizerBounds bounds) { + this(h, handler, StepNormalizerMode.INCREMENT, bounds); + } + + /** Simple constructor. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + * @param mode step normalizer mode to use + * @param bounds step normalizer bounds setting to use + * @since 3.0 + */ + public StepNormalizer(final double h, final FixedStepHandler handler, + final StepNormalizerMode mode, + final StepNormalizerBounds bounds) { + this.h = FastMath.abs(h); + this.handler = handler; + this.mode = mode; + this.bounds = bounds; + firstTime = Double.NaN; + lastTime = Double.NaN; + lastState = null; + lastDerivatives = null; + forward = true; + } + + /** {@inheritDoc} */ + public void init(double t0, double[] y0, double t) { + + firstTime = Double.NaN; + lastTime = Double.NaN; + lastState = null; + lastDerivatives = null; + forward = true; + + // initialize the underlying handler + handler.init(t0, y0, t); + + } + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range), it + * should build a local copy using the clone method and store this + * copy. + * @param isLast true if the step is the last one + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + public void handleStep(final StepInterpolator interpolator, final boolean isLast) + throws MaxCountExceededException { + // The first time, update the last state with the start information. + if (lastState == null) { + firstTime = interpolator.getPreviousTime(); + lastTime = interpolator.getPreviousTime(); + interpolator.setInterpolatedTime(lastTime); + lastState = interpolator.getInterpolatedState().clone(); + lastDerivatives = interpolator.getInterpolatedDerivatives().clone(); + + // Take the integration direction into account. + forward = interpolator.getCurrentTime() >= lastTime; + if (!forward) { + h = -h; + } + } + + // Calculate next normalized step time. + double nextTime = (mode == StepNormalizerMode.INCREMENT) ? + lastTime + h : + (FastMath.floor(lastTime / h) + 1) * h; + if (mode == StepNormalizerMode.MULTIPLES && + Precision.equals(nextTime, lastTime, 1)) { + nextTime += h; + } + + // Process normalized steps as long as they are in the current step. + boolean nextInStep = isNextInStep(nextTime, interpolator); + while (nextInStep) { + // Output the stored previous step. + doNormalizedStep(false); + + // Store the next step as last step. + storeStep(interpolator, nextTime); + + // Move on to the next step. + nextTime += h; + nextInStep = isNextInStep(nextTime, interpolator); + } + + if (isLast) { + // There will be no more steps. The stored one should be given to + // the handler. We may have to output one more step. Only the last + // one of those should be flagged as being the last. + boolean addLast = bounds.lastIncluded() && + lastTime != interpolator.getCurrentTime(); + doNormalizedStep(!addLast); + if (addLast) { + storeStep(interpolator, interpolator.getCurrentTime()); + doNormalizedStep(true); + } + } + } + + /** + * Returns a value indicating whether the next normalized time is in the + * current step. + * @param nextTime the next normalized time + * @param interpolator interpolator for the last accepted step, to use to + * get the end time of the current step + * @return value indicating whether the next normalized time is in the + * current step + */ + private boolean isNextInStep(double nextTime, + StepInterpolator interpolator) { + return forward ? + nextTime <= interpolator.getCurrentTime() : + nextTime >= interpolator.getCurrentTime(); + } + + /** + * Invokes the underlying step handler for the current normalized step. + * @param isLast true if the step is the last one + */ + private void doNormalizedStep(boolean isLast) { + if (!bounds.firstIncluded() && firstTime == lastTime) { + return; + } + handler.handleStep(lastTime, lastState, lastDerivatives, isLast); + } + + /** Stores the interpolated information for the given time in the current + * state. + * @param interpolator interpolator for the last accepted step, to use to + * get the interpolated information + * @param t the time for which to store the interpolated information + * @exception MaxCountExceededException if the interpolator throws one because + * the number of functions evaluations is exceeded + */ + private void storeStep(StepInterpolator interpolator, double t) + throws MaxCountExceededException { + lastTime = t; + interpolator.setInterpolatedTime(lastTime); + System.arraycopy(interpolator.getInterpolatedState(), 0, + lastState, 0, lastState.length); + System.arraycopy(interpolator.getInterpolatedDerivatives(), 0, + lastDerivatives, 0, lastDerivatives.length); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java new file mode 100644 index 000000000..96bc02d1d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + +/** {@link StepNormalizer Step normalizer} bounds settings. They influence + * whether the underlying fixed step size step handler is called for the first + * and last points. Note that if the last point coincides with a normalized + * point, then the underlying fixed step size step handler is always called, + * regardless of these settings. + * @see FieldStepNormalizer + * @see StepNormalizer + * @see StepNormalizerMode + * @since 3.0 + */ +public enum StepNormalizerBounds { + /** Do not include the first and last points. */ + NEITHER(false, false), + + /** Include the first point, but not the last point. */ + FIRST(true, false), + + /** Include the last point, but not the first point. */ + LAST(false, true), + + /** Include both the first and last points. */ + BOTH(true, true); + + /** Whether the first point should be passed to the underlying fixed + * step size step handler. + */ + private final boolean first; + + /** Whether the last point should be passed to the underlying fixed + * step size step handler. + */ + private final boolean last; + + /** + * Simple constructor. + * @param first Whether the first point should be passed to the + * underlying fixed step size step handler. + * @param last Whether the last point should be passed to the + * underlying fixed step size step handler. + */ + StepNormalizerBounds(final boolean first, final boolean last) { + this.first = first; + this.last = last; + } + + /** + * Returns a value indicating whether the first point should be passed + * to the underlying fixed step size step handler. + * @return value indicating whether the first point should be passed + * to the underlying fixed step size step handler. + */ + public boolean firstIncluded() { + return first; + } + + /** + * Returns a value indicating whether the last point should be passed + * to the underlying fixed step size step handler. + * @return value indicating whether the last point should be passed + * to the underlying fixed step size step handler. + */ + public boolean lastIncluded() { + return last; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java new file mode 100644 index 000000000..82af25ef3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.ode.sampling; + + +/** {@link StepNormalizer Step normalizer} modes. Determines how the step size + * is interpreted. + * @see FieldStepNormalizer + * @see StepNormalizer + * @see StepNormalizerBounds + * @since 3.0 + */ +public enum StepNormalizerMode { + /** + * Steps are fixed increments of the start value. In other words, they + * are relative to the start value. + * + *

    If the integration start time is t0, then the points handled by + * the underlying fixed step size step handler are t0 (depending on + * the {@link StepNormalizerBounds bounds settings}), t0+h, t0+2h, ...

    + * + *

    If the integration range is an integer multiple of the step size + * (h), then the last point handled will be the end point of the + * integration (tend). If not, the last point may be the end point + * tend, or it may be a point belonging to the interval [tend - h ; + * tend], depending on the {@link StepNormalizerBounds bounds settings}. + *

    + * + * @see StepNormalizer + * @see StepNormalizerBounds + */ + INCREMENT, + + /** Steps are multiples of a fixed value. In other words, they are + * relative to the first multiple of the step size that is encountered + * after the start value. + * + *

    If the integration start time is t0, and the first multiple of + * the fixed step size that is encountered is t1, then the points + * handled by the underlying fixed step size step handler are t0 + * (depending on the {@link StepNormalizerBounds bounds settings}), t1, + * t1+h, t1+2h, ...

    + * + *

    If the end point of the integration range (tend) is an integer + * multiple of the step size (h) added to t1, then the last point + * handled will be the end point of the integration (tend). If not, + * the last point may be the end point tend, or it may be a point + * belonging to the interval [tend - h ; tend], depending on the + * {@link StepNormalizerBounds bounds settings}.

    + * + * @see StepNormalizer + * @see StepNormalizerBounds + */ + MULTIPLES; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/package-info.java new file mode 100644 index 000000000..6ed4e4304 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/ode/sampling/package-info.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

    + * This package provides classes to handle sampling steps during + * Ordinary Differential Equations integration. + *

    + * + *

    + * 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 inside the + * last computed step. If users are interested in these interpolators, they can register a + * {@link com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler StepHandler} instance using the + * {@link com.fr.third.org.apache.commons.math3.ode.FirstOrderIntegrator#addStepHandler addStepHandler} + * method which is supported by all integrators. The integrator will call this instance + * at the end of each accepted step and provide it the interpolator. The user can do + * whatever he wants with this interpolator, which computes both the state and its + * time-derivative. A typical use of step handler is to provide some output to monitor + * the integration process. + *

    + * + *

    + * 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. + *

    + * + *

    + * Since some integrators may use variable step size, the generic {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepHandler StepHandler} interface can be called + * either at regular or irregular rate. This interface allows to navigate to any location + * within the last computed step, thanks to the provided {@link + * com.fr.third.org.apache.commons.math3.ode.sampling.StepInterpolator StepInterpolator} object. + * If regular output is desired (for example in order to write an ephemeris file), then + * the simpler {@link com.fr.third.org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler} + * interface can be used. Objects implementing this interface should be wrapped within a + * {@link com.fr.third.org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer} instance + * in order to be registered to the integrator. + *

    + * + * + */ +package com.fr.third.org.apache.commons.math3.ode.sampling; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractConvergenceChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractConvergenceChecker.java new file mode 100644 index 000000000..334115be0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractConvergenceChecker.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 com.fr.third.org.apache.commons.math3.optim; + +/** + * Base class for all convergence checker implementations. + * + * @param Type of (point, value) pair. + * + * @since 3.0 + */ +public abstract class AbstractConvergenceChecker + implements ConvergenceChecker { + /** + * Relative tolerance threshold. + */ + private final double relativeThreshold; + /** + * Absolute tolerance threshold. + */ + private final double absoluteThreshold; + + /** + * Build an instance with a specified thresholds. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public AbstractConvergenceChecker(final double relativeThreshold, + final double absoluteThreshold) { + this.relativeThreshold = relativeThreshold; + this.absoluteThreshold = absoluteThreshold; + } + + /** + * @return the relative threshold. + */ + public double getRelativeThreshold() { + return relativeThreshold; + } + + /** + * @return the absolute threshold. + */ + public double getAbsoluteThreshold() { + return absoluteThreshold; + } + + /** + * {@inheritDoc} + */ + public abstract boolean converged(int iteration, + PAIR previous, + PAIR current); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractOptimizationProblem.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractOptimizationProblem.java new file mode 100644 index 000000000..6c3d27338 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/AbstractOptimizationProblem.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.TooManyIterationsException; +import com.fr.third.org.apache.commons.math3.util.Incrementor; + +/** + * Base class for implementing optimization problems. It contains the boiler-plate code + * for counting the number of evaluations of the objective function and the number of + * iterations of the algorithm, and storing the convergence checker. + * + * @param Type of the point/value pair returned by the optimization algorithm. + * @since 3.3 + */ +public abstract class AbstractOptimizationProblem + implements OptimizationProblem { + + /** Callback to use for the evaluation counter. */ + private static final MaxEvalCallback MAX_EVAL_CALLBACK = new MaxEvalCallback(); + /** Callback to use for the iteration counter. */ + private static final MaxIterCallback MAX_ITER_CALLBACK = new MaxIterCallback(); + + /** max evaluations */ + private final int maxEvaluations; + /** max iterations */ + private final int maxIterations; + /** Convergence checker. */ + private final ConvergenceChecker checker; + + /** + * Create an {@link AbstractOptimizationProblem} from the given data. + * + * @param maxEvaluations the number of allowed model function evaluations. + * @param maxIterations the number of allowed iterations. + * @param checker the convergence checker. + */ + protected AbstractOptimizationProblem(final int maxEvaluations, + final int maxIterations, + final ConvergenceChecker checker) { + this.maxEvaluations = maxEvaluations; + this.maxIterations = maxIterations; + this.checker = checker; + } + + /** {@inheritDoc} */ + public Incrementor getEvaluationCounter() { + return new Incrementor(this.maxEvaluations, MAX_EVAL_CALLBACK); + } + + /** {@inheritDoc} */ + public Incrementor getIterationCounter() { + return new Incrementor(this.maxIterations, MAX_ITER_CALLBACK); + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return checker; + } + + /** Defines the action to perform when reaching the maximum number of evaluations. */ + private static class MaxEvalCallback + implements Incrementor.MaxCountExceededCallback { + /** + * {@inheritDoc} + * + * @throws TooManyEvaluationsException + */ + public void trigger(int max) { + throw new TooManyEvaluationsException(max); + } + } + + /** Defines the action to perform when reaching the maximum number of evaluations. */ + private static class MaxIterCallback + implements Incrementor.MaxCountExceededCallback { + /** + * {@inheritDoc} + * + * @throws TooManyIterationsException + */ + public void trigger(int max) { + throw new TooManyIterationsException(max); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.java new file mode 100644 index 000000000..d853eacb4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Base class multi-start optimizer for a multivariate function. + *
    + * This class wraps an optimizer in order to use it several times in + * turn with different starting points (trying to avoid being trapped + * in a local extremum when looking for a global one). + * It is not a "user" class. + * + * @param Type of the point/value pair returned by the optimization + * algorithm. + * + * @since 3.0 + */ +public abstract class BaseMultiStartMultivariateOptimizer + extends BaseMultivariateOptimizer { + /** Underlying classical optimizer. */ + private final BaseMultivariateOptimizer optimizer; + /** Number of evaluations already performed for all starts. */ + private int totalEvaluations; + /** Number of starts to go. */ + private int starts; + /** Random generator for multi-start. */ + private RandomVectorGenerator generator; + /** Optimization data. */ + private OptimizationData[] optimData; + /** + * Location in {@link #optimData} where the updated maximum + * number of evaluations will be stored. + */ + private int maxEvalIndex = -1; + /** + * Location in {@link #optimData} where the updated start value + * will be stored. + */ + private int initialGuessIndex = -1; + + /** + * Create a multi-start optimizer from a single-start optimizer. + *

    + * Note that if there are bounds constraints (see {@link #getLowerBound()} + * and {@link #getUpperBound()}), then a simple rejection algorithm is used + * at each restart. This implies that the random vector generator should have + * a good probability to generate vectors in the bounded domain, otherwise the + * rejection algorithm will hit the {@link #getMaxEvaluations()} count without + * generating a proper restart point. Users must be take great care of the curse of dimensionality. + *

    + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. If {@code starts == 1}, + * the {@link #optimize(OptimizationData[]) optimize} will return the + * same solution as the given {@code optimizer} would return. + * @param generator Random vector generator to use for restarts. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + public BaseMultiStartMultivariateOptimizer(final BaseMultivariateOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer.getConvergenceChecker()); + + if (starts < 1) { + throw new NotStrictlyPositiveException(starts); + } + + this.optimizer = optimizer; + this.starts = starts; + this.generator = generator; + } + + /** {@inheritDoc} */ + @Override + public int getEvaluations() { + return totalEvaluations; + } + + /** + * Gets all the optima found during the last call to {@code optimize}. + * The optimizer stores all the optima found during a set of + * restarts. The {@code optimize} method returns the best point only. + * This method returns all the points found at the end of each starts, + * including the best one already returned by the {@code optimize} method. + *
    + * The returned array as one element for each start as specified + * in the constructor. It is ordered with the results from the + * runs that did converge first, sorted from best to worst + * objective value (i.e in ascending order if minimizing and in + * descending order if maximizing), followed by {@code null} elements + * corresponding to the runs that did not converge. This means all + * elements will be {@code null} if the {@code optimize} method did throw + * an exception. + * This also means that if the first element is not {@code null}, it is + * the best point found across all starts. + *
    + * The behaviour is undefined if this method is called before + * {@code optimize}; it will likely throw {@code NullPointerException}. + * + * @return an array containing the optima sorted from best to worst. + */ + public abstract PAIR[] getOptima(); + + /** + * {@inheritDoc} + * + * @throws MathIllegalStateException if {@code optData} does not contain an + * instance of {@link MaxEval} or {@link InitialGuess}. + */ + @Override + public PAIR optimize(OptimizationData... optData) { + // Store arguments in order to pass them to the internal optimizer. + optimData = optData; + // Set up base class and perform computations. + return super.optimize(optData); + } + + /** {@inheritDoc} */ + @Override + protected PAIR doOptimize() { + // Remove all instances of "MaxEval" and "InitialGuess" from the + // array that will be passed to the internal optimizer. + // The former is to enforce smaller numbers of allowed evaluations + // (according to how many have been used up already), and the latter + // to impose a different start value for each start. + for (int i = 0; i < optimData.length; i++) { + if (optimData[i] instanceof MaxEval) { + optimData[i] = null; + maxEvalIndex = i; + } + if (optimData[i] instanceof InitialGuess) { + optimData[i] = null; + initialGuessIndex = i; + continue; + } + } + if (maxEvalIndex == -1) { + throw new MathIllegalStateException(); + } + if (initialGuessIndex == -1) { + throw new MathIllegalStateException(); + } + + RuntimeException lastException = null; + totalEvaluations = 0; + clear(); + + final int maxEval = getMaxEvaluations(); + final double[] min = getLowerBound(); + final double[] max = getUpperBound(); + final double[] startPoint = getStartPoint(); + + // Multi-start loop. + for (int i = 0; i < starts; i++) { + // CHECKSTYLE: stop IllegalCatch + try { + // Decrease number of allowed evaluations. + optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations); + // New start value. + double[] s = null; + if (i == 0) { + s = startPoint; + } else { + int attempts = 0; + while (s == null) { + if (attempts++ >= getMaxEvaluations()) { + throw new TooManyEvaluationsException(getMaxEvaluations()); + } + s = generator.nextVector(); + for (int k = 0; s != null && k < s.length; ++k) { + if ((min != null && s[k] < min[k]) || (max != null && s[k] > max[k])) { + // reject the vector + s = null; + } + } + } + } + optimData[initialGuessIndex] = new InitialGuess(s); + // Optimize. + final PAIR result = optimizer.optimize(optimData); + store(result); + } catch (RuntimeException mue) { + lastException = mue; + } + // CHECKSTYLE: resume IllegalCatch + + totalEvaluations += optimizer.getEvaluations(); + } + + final PAIR[] optima = getOptima(); + if (optima.length == 0) { + // All runs failed. + throw lastException; // Cannot be null if starts >= 1. + } + + // Return the best optimum. + return optima[0]; + } + + /** + * Method that will be called in order to store each found optimum. + * + * @param optimum Result of an optimization run. + */ + protected abstract void store(PAIR optimum); + /** + * Method that will called in order to clear all stored optima. + */ + protected abstract void clear(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultivariateOptimizer.java new file mode 100644 index 000000000..38f294ea2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; + +/** + * Base class for implementing optimizers for multivariate functions. + * It contains the boiler-plate code for initial guess and bounds + * specifications. + * It is not a "user" class. + * + * @param Type of the point/value pair returned by the optimization + * algorithm. + * + * @since 3.1 + */ +public abstract class BaseMultivariateOptimizer + extends BaseOptimizer { + /** Initial guess. */ + private double[] start; + /** Lower bounds. */ + private double[] lowerBound; + /** Upper bounds. */ + private double[] upperBound; + + /** + * @param checker Convergence checker. + */ + protected BaseMultivariateOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link BaseOptimizer#parseOptimizationData(OptimizationData[]) BaseOptimizer}, + * this method will register the following data: + *
      + *
    • {@link InitialGuess}
    • + *
    • {@link SimpleBounds}
    • + *
    + * @return {@inheritDoc} + */ + @Override + public PAIR optimize(OptimizationData... optData) { + // Perform optimization. + return super.optimize(optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
      + *
    • {@link InitialGuess}
    • + *
    • {@link SimpleBounds}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof InitialGuess) { + start = ((InitialGuess) data).getInitialGuess(); + continue; + } + if (data instanceof SimpleBounds) { + final SimpleBounds bounds = (SimpleBounds) data; + lowerBound = bounds.getLower(); + upperBound = bounds.getUpper(); + continue; + } + } + + // Check input consistency. + checkParameters(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess, or {@code null} if not set. + */ + public double[] getStartPoint() { + return start == null ? null : start.clone(); + } + /** + * @return the lower bounds, or {@code null} if not set. + */ + public double[] getLowerBound() { + return lowerBound == null ? null : lowerBound.clone(); + } + /** + * @return the upper bounds, or {@code null} if not set. + */ + public double[] getUpperBound() { + return upperBound == null ? null : upperBound.clone(); + } + + /** + * Check parameters consistency. + */ + private void checkParameters() { + if (start != null) { + final int dim = start.length; + if (lowerBound != null) { + if (lowerBound.length != dim) { + throw new DimensionMismatchException(lowerBound.length, dim); + } + for (int i = 0; i < dim; i++) { + final double v = start[i]; + final double lo = lowerBound[i]; + if (v < lo) { + throw new NumberIsTooSmallException(v, lo, true); + } + } + } + if (upperBound != null) { + if (upperBound.length != dim) { + throw new DimensionMismatchException(upperBound.length, dim); + } + for (int i = 0; i < dim; i++) { + final double v = start[i]; + final double hi = upperBound[i]; + if (v > hi) { + throw new NumberIsTooLargeException(v, hi, true); + } + } + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseOptimizer.java new file mode 100644 index 000000000..4616c1134 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/BaseOptimizer.java @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.TooManyIterationsException; +import com.fr.third.org.apache.commons.math3.util.Incrementor; + +/** + * Base class for implementing optimizers. + * It contains the boiler-plate code for counting the number of evaluations + * of the objective function and the number of iterations of the algorithm, + * and storing the convergence checker. + * It is not a "user" class. + * + * @param Type of the point/value pair returned by the optimization + * algorithm. + * + * @since 3.1 + */ +public abstract class BaseOptimizer { + /** Evaluations counter. */ + protected final Incrementor evaluations; + /** Iterations counter. */ + protected final Incrementor iterations; + /** Convergence checker. */ + private final ConvergenceChecker checker; + + /** + * @param checker Convergence checker. + */ + protected BaseOptimizer(ConvergenceChecker checker) { + this(checker, 0, Integer.MAX_VALUE); + } + + /** + * @param checker Convergence checker. + * @param maxEval Maximum number of objective function evaluations. + * @param maxIter Maximum number of algorithm iterations. + */ + protected BaseOptimizer(ConvergenceChecker checker, + int maxEval, + int maxIter) { + this.checker = checker; + + evaluations = new Incrementor(maxEval, new MaxEvalCallback()); + iterations = new Incrementor(maxIter, new MaxIterCallback()); + } + + /** + * Gets the maximal number of function evaluations. + * + * @return the maximal number of function evaluations. + */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** + * Gets the number of evaluations of the objective function. + * The number of evaluations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** + * Gets the maximal number of iterations. + * + * @return the maximal number of iterations. + */ + public int getMaxIterations() { + return iterations.getMaximalCount(); + } + + /** + * Gets the number of iterations performed by the algorithm. + * The number iterations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + public int getIterations() { + return iterations.getCount(); + } + + /** + * Gets the convergence checker. + * + * @return the object used to check for convergence. + */ + public ConvergenceChecker getConvergenceChecker() { + return checker; + } + + /** + * Stores data and performs the optimization. + *

    + * The list of parameters is open-ended so that sub-classes can extend it + * with arguments specific to their concrete implementations. + *

    + * When the method is called multiple times, instance data is overwritten + * only when actually present in the list of arguments: when not specified, + * data set in a previous call is retained (and thus is optional in + * subsequent calls). + *

    + * Important note: Subclasses must override + * {@link #parseOptimizationData(OptimizationData[])} if they need to register + * their own options; but then, they must also call + * {@code super.parseOptimizationData(optData)} within that method. + * + * @param optData Optimization data. + * This method will register the following data: + *

      + *
    • {@link MaxEval}
    • + *
    • {@link MaxIter}
    • + *
    + * @return a point/value pair that satisfies the convergence criteria. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws TooManyIterationsException if the maximal number of + * iterations is exceeded. + */ + public PAIR optimize(OptimizationData... optData) + throws TooManyEvaluationsException, + TooManyIterationsException { + // Parse options. + parseOptimizationData(optData); + + // Reset counters. + evaluations.resetCount(); + iterations.resetCount(); + // Perform optimization. + return doOptimize(); + } + + /** + * Performs the optimization. + * + * @return a point/value pair that satisfies the convergence criteria. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws TooManyIterationsException if the maximal number of + * iterations is exceeded. + */ + public PAIR optimize() + throws TooManyEvaluationsException, + TooManyIterationsException { + // Reset counters. + evaluations.resetCount(); + iterations.resetCount(); + // Perform optimization. + return doOptimize(); + } + + /** + * Performs the bulk of the optimization algorithm. + * + * @return the point/value pair giving the optimal value of the + * objective function. + */ + protected abstract PAIR doOptimize(); + + /** + * Increment the evaluation count. + * + * @throws TooManyEvaluationsException if the allowed evaluations + * have been exhausted. + */ + protected void incrementEvaluationCount() + throws TooManyEvaluationsException { + evaluations.incrementCount(); + } + + /** + * Increment the iteration count. + * + * @throws TooManyIterationsException if the allowed iterations + * have been exhausted. + */ + protected void incrementIterationCount() + throws TooManyIterationsException { + iterations.incrementCount(); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link MaxEval}
    • + *
    • {@link MaxIter}
    • + *
    + */ + protected void parseOptimizationData(OptimizationData... optData) { + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof MaxEval) { + evaluations.setMaximalCount(((MaxEval) data).getMaxEval()); + continue; + } + if (data instanceof MaxIter) { + iterations.setMaximalCount(((MaxIter) data).getMaxIter()); + continue; + } + } + } + + /** + * Defines the action to perform when reaching the maximum number + * of evaluations. + */ + private static class MaxEvalCallback + implements Incrementor.MaxCountExceededCallback { + /** + * {@inheritDoc} + * @throws TooManyEvaluationsException + */ + public void trigger(int max) { + throw new TooManyEvaluationsException(max); + } + } + + /** + * Defines the action to perform when reaching the maximum number + * of evaluations. + */ + private static class MaxIterCallback + implements Incrementor.MaxCountExceededCallback { + /** + * {@inheritDoc} + * @throws TooManyIterationsException + */ + public void trigger(int max) { + throw new TooManyIterationsException(max); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/ConvergenceChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/ConvergenceChecker.java new file mode 100644 index 000000000..7fde9d997 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/ConvergenceChecker.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 com.fr.third.org.apache.commons.math3.optim; + +/** + * This interface specifies how to check if an optimization algorithm has + * converged. + *
    + * 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. + *
    + * For convenience, three implementations that fit simple needs are already + * provided: {@link SimpleValueChecker}, {@link SimpleVectorValueChecker} and + * {@link SimplePointChecker}. The first two consider that convergence is + * reached when the objective function value does not change much anymore, it + * does not use the point set at all. + * The third one considers that convergence is reached when the input point + * set does not change much anymore, it does not use objective function value + * at all. + * + * @param Type of the (point, objective value) pair. + * + * @see SimplePointChecker + * @see SimpleValueChecker + * @see SimpleVectorValueChecker + * + * @since 3.0 + */ +public interface ConvergenceChecker { + /** + * Check if the optimization algorithm has converged. + * + * @param iteration Current iteration. + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm is considered to have converged. + */ + boolean converged(int iteration, PAIR previous, PAIR current); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/InitialGuess.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/InitialGuess.java new file mode 100644 index 000000000..cafa1dbf7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/InitialGuess.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 com.fr.third.org.apache.commons.math3.optim; + +/** + * Starting point (first guess) of the optimization procedure. + *
    + * Immutable class. + * + * @since 3.1 + */ +public class InitialGuess implements OptimizationData { + /** Initial guess. */ + private final double[] init; + + /** + * @param startPoint Initial guess. + */ + public InitialGuess(double[] startPoint) { + init = startPoint.clone(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public double[] getInitialGuess() { + return init.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxEval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxEval.java new file mode 100644 index 000000000..c88e77f53 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxEval.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Maximum number of evaluations of the function to be optimized. + * + * @since 3.1 + */ +public class MaxEval implements OptimizationData { + /** Allowed number of evalutations. */ + private final int maxEval; + + /** + * @param max Allowed number of evalutations. + * @throws NotStrictlyPositiveException if {@code max <= 0}. + */ + public MaxEval(int max) { + if (max <= 0) { + throw new NotStrictlyPositiveException(max); + } + + maxEval = max; + } + + /** + * Gets the maximum number of evaluations. + * + * @return the allowed number of evaluations. + */ + public int getMaxEval() { + return maxEval; + } + + /** + * Factory method that creates instance of this class that represents + * a virtually unlimited number of evaluations. + * + * @return a new instance suitable for allowing {@link Integer#MAX_VALUE} + * evaluations. + */ + public static MaxEval unlimited() { + return new MaxEval(Integer.MAX_VALUE); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxIter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxIter.java new file mode 100644 index 000000000..f0dbc4e63 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/MaxIter.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Maximum number of iterations performed by an (iterative) algorithm. + * + * @since 3.1 + */ +public class MaxIter implements OptimizationData { + /** Allowed number of evalutations. */ + private final int maxIter; + + /** + * @param max Allowed number of iterations. + * @throws NotStrictlyPositiveException if {@code max <= 0}. + */ + public MaxIter(int max) { + if (max <= 0) { + throw new NotStrictlyPositiveException(max); + } + + maxIter = max; + } + + /** + * Gets the maximum number of evaluations. + * + * @return the allowed number of evaluations. + */ + public int getMaxIter() { + return maxIter; + } + + /** + * Factory method that creates instance of this class that represents + * a virtually unlimited number of iterations. + * + * @return a new instance suitable for allowing {@link Integer#MAX_VALUE} + * evaluations. + */ + public static MaxIter unlimited() { + return new MaxIter(Integer.MAX_VALUE); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationData.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationData.java new file mode 100644 index 000000000..a291bed1f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationData.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim; + +/** + * Marker interface. + * Implementations will provide functionality (optional or required) needed + * by the optimizers, and those will need to check the actual type of the + * arguments and perform the appropriate cast in order to access the data + * they need. + * + * @since 3.1 + */ +public interface OptimizationData {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationProblem.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationProblem.java new file mode 100644 index 000000000..2d6a3bdda --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/OptimizationProblem.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.util.Incrementor; + +/** + * Common settings for all optimization problems. Includes divergence and convergence + * criteria. + * + * @param The type of value the {@link #getConvergenceChecker() convergence + * checker} will operate on. It should include the value of the model + * function and point where it was evaluated. + * @since 3.3 + */ +public interface OptimizationProblem { + /** + * Get a independent Incrementor that counts up to the maximum number of evaluations + * and then throws an exception. + * + * @return a counter for the evaluations. + */ + Incrementor getEvaluationCounter(); + + /** + * Get a independent Incrementor that counts up to the maximum number of iterations + * and then throws an exception. + * + * @return a counter for the evaluations. + */ + Incrementor getIterationCounter(); + + /** + * Gets the convergence checker. + * + * @return the object used to check for convergence. + */ + ConvergenceChecker getConvergenceChecker(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointValuePair.java new file mode 100644 index 000000000..8d0deaa68 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointValuePair.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 com.fr.third.org.apache.commons.math3.optim; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * This class holds a point and the value of an objective function at + * that point. + * + * @see PointVectorValuePair + * @see MultivariateFunction + * @since 3.0 + */ +public class PointValuePair extends Pair implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. This instance will store + * a copy of the array, not the array passed as argument. + * @param value Value of the objective function at the point. + */ + public PointValuePair(final double[] point, + final double value) { + this(point, value, true); + } + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. + * @param value Value of the objective function at the point. + * @param copyArray if {@code true}, the input array will be copied, + * otherwise it will be referenced. + */ + public PointValuePair(final double[] point, + final double value, + final boolean copyArray) { + super(copyArray ? ((point == null) ? null : + point.clone()) : + point, + value); + } + + /** + * Gets the point. + * + * @return a copy of the stored point. + */ + public double[] getPoint() { + final double[] p = getKey(); + return p == null ? null : p.clone(); + } + + /** + * Gets a reference to the point. + * + * @return a reference to the internal array storing the point. + */ + public double[] getPointRef() { + return getKey(); + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(getKey(), getValue()); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + /** + * Point coordinates. + * @Serial + */ + private final double[] point; + /** + * Value of the objective function at the point. + * @Serial + */ + private final double value; + + /** Simple constructor. + * @param point Point coordinates. + * @param value Value of the objective function at the point. + */ + DataTransferObject(final double[] point, final double value) { + this.point = point.clone(); + this.value = value; + } + + /** Replace the deserialized data transfer object with a {@link PointValuePair}. + * @return replacement {@link PointValuePair} + */ + private Object readResolve() { + return new PointValuePair(point, value, false); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointVectorValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointVectorValuePair.java new file mode 100644 index 000000000..ff7c43a95 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/PointVectorValuePair.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 com.fr.third.org.apache.commons.math3.optim; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * This class holds a point and the vectorial value of an objective function at + * that point. + * + * @see PointValuePair + * @see MultivariateVectorFunction + * @since 3.0 + */ +public class PointVectorValuePair extends Pair implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. This instance will store + * a copy of the array, not the array passed as argument. + * @param value Value of the objective function at the point. + */ + public PointVectorValuePair(final double[] point, + final double[] value) { + this(point, value, true); + } + + /** + * Build a point/objective function value pair. + * + * @param point Point coordinates. + * @param value Value of the objective function at the point. + * @param copyArray if {@code true}, the input arrays will be copied, + * otherwise they will be referenced. + */ + public PointVectorValuePair(final double[] point, + final double[] value, + final boolean copyArray) { + super(copyArray ? + ((point == null) ? null : + point.clone()) : + point, + copyArray ? + ((value == null) ? null : + value.clone()) : + value); + } + + /** + * Gets the point. + * + * @return a copy of the stored point. + */ + public double[] getPoint() { + final double[] p = getKey(); + return p == null ? null : p.clone(); + } + + /** + * Gets a reference to the point. + * + * @return a reference to the internal array storing the point. + */ + public double[] getPointRef() { + return getKey(); + } + + /** + * Gets the value of the objective function. + * + * @return a copy of the stored value of the objective function. + */ + @Override + public double[] getValue() { + final double[] v = super.getValue(); + return v == null ? null : v.clone(); + } + + /** + * Gets a reference to the value of the objective function. + * + * @return a reference to the internal array storing the value of + * the objective function. + */ + public double[] getValueRef() { + return super.getValue(); + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(getKey(), getValue()); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + /** + * Point coordinates. + * @Serial + */ + private final double[] point; + /** + * Value of the objective function at the point. + * @Serial + */ + private final double[] value; + + /** Simple constructor. + * @param point Point coordinates. + * @param value Value of the objective function at the point. + */ + DataTransferObject(final double[] point, final double[] value) { + this.point = point.clone(); + this.value = value.clone(); + } + + /** Replace the deserialized data transfer object with a {@link PointValuePair}. + * @return replacement {@link PointValuePair} + */ + private Object readResolve() { + return new PointVectorValuePair(point, value, false); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleBounds.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleBounds.java new file mode 100644 index 000000000..4606eb467 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleBounds.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim; + +import java.util.Arrays; + +/** + * Simple optimization constraints: lower and upper bounds. + * The valid range of the parameters is an interval that can be infinite + * (in one or both directions). + *
    + * Immutable class. + * + * @since 3.1 + */ +public class SimpleBounds implements OptimizationData { + /** Lower bounds. */ + private final double[] lower; + /** Upper bounds. */ + private final double[] upper; + + /** + * @param lB Lower bounds. + * @param uB Upper bounds. + */ + public SimpleBounds(double[] lB, + double[] uB) { + lower = lB.clone(); + upper = uB.clone(); + } + + /** + * Gets the lower bounds. + * + * @return the lower bounds. + */ + public double[] getLower() { + return lower.clone(); + } + /** + * Gets the upper bounds. + * + * @return the upper bounds. + */ + public double[] getUpper() { + return upper.clone(); + } + + /** + * Factory method that creates instance of this class that represents + * unbounded ranges. + * + * @param dim Number of parameters. + * @return a new instance suitable for passing to an optimizer that + * requires bounds specification. + */ + public static SimpleBounds unbounded(int dim) { + final double[] lB = new double[dim]; + Arrays.fill(lB, Double.NEGATIVE_INFINITY); + final double[] uB = new double[dim]; + Arrays.fill(uB, Double.POSITIVE_INFINITY); + + return new SimpleBounds(lB, uB); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimplePointChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimplePointChecker.java new file mode 100644 index 000000000..338e2475d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimplePointChecker.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 com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only point coordinates. + * + * 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. + *
    + * The {@link #converged(int,Pair,Pair) converged} method will also return + * {@code true} if the number of iterations has been set (see + * {@link #SimplePointChecker(double,double,int) this constructor}). + * + * @param Type of the (point, value) pair. + * The type of the "value" part of the pair (not used by this class). + * + * @since 3.0 + */ +public class SimplePointChecker> + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause {@link #converged(int, Pair, Pair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int, Pair, Pair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with specified thresholds. + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimplePointChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold Relative tolerance threshold. + * @param absoluteThreshold Absolute tolerance threshold. + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimplePointChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several times from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the arguments satify the convergence criterion. + */ + @Override + public boolean converged(final int iteration, + final PAIR previous, + final PAIR current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double[] p = previous.getKey(); + final double[] c = current.getKey(); + for (int i = 0; i < p.length; ++i) { + final double pi = p[i]; + final double ci = c[i]; + final double difference = FastMath.abs(pi - ci); + final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci)); + if (difference > size * getRelativeThreshold() && + difference > getAbsoluteThreshold()) { + return false; + } + } + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleValueChecker.java new file mode 100644 index 000000000..d2c438bdd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleValueChecker.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold. + *
    + * The {@link #converged(int,PointValuePair,PointValuePair) converged} + * method will also return {@code true} if the number of iterations has been set + * (see {@link #SimpleValueChecker(double,double,int) this constructor}). + * + * @since 3.0 + */ +public class SimpleValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,PointValuePair,PointValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,PointValuePair,PointValuePair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several time from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm has converged. + */ + @Override + public boolean converged(final int iteration, + final PointValuePair previous, + final PointValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double p = previous.getValue(); + final double c = current.getValue(); + final double difference = FastMath.abs(p - c); + final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c)); + return difference <= size * getRelativeThreshold() || + difference <= getAbsoluteThreshold(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleVectorValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleVectorValueChecker.java new file mode 100644 index 000000000..ac069fe87 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/SimpleVectorValueChecker.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold for all vectors elements. + *
    + * The {@link #converged(int,PointVectorValuePair,PointVectorValuePair) converged} + * method will also return {@code true} if the number of iterations has been set + * (see {@link #SimpleVectorValueChecker(double,double,int) this constructor}). + * + * @since 3.0 + */ +public class SimpleVectorValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleVectorValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified tolerance thresholds and + * iteration count. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold Relative tolerance threshold. + * @param absoluteThreshold Absolute tolerance threshold. + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleVectorValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several times from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the arguments satify the convergence criterion. + */ + @Override + public boolean converged(final int iteration, + final PointVectorValuePair previous, + final PointVectorValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double[] p = previous.getValueRef(); + final double[] c = current.getValueRef(); + for (int i = 0; i < p.length; ++i) { + final double pi = p[i]; + final double ci = c[i]; + final double difference = FastMath.abs(pi - ci); + final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci)); + if (difference > size * getRelativeThreshold() && + difference > getAbsoluteThreshold()) { + return false; + } + } + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraint.java new file mode 100644 index 000000000..6fdd78692 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraint.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 com.fr.third.org.apache.commons.math3.optim.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * A linear constraint for a linear optimization problem. + *

    + * A linear constraint has one of the forms: + *

      + *
    • c1x1 + ... cnxn = v
    • + *
    • c1x1 + ... cnxn <= v
    • + *
    • c1x1 + ... cnxn >= v
    • + *
    • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
    • + *
    + * The ci, li or ri are the coefficients of the constraints, the xi + * are the coordinates of the current point and v is the value of the constraint. + *

    + * + * @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. + *

    + * A linear constraint with a single linear equation has one of the forms: + *

      + *
    • c1x1 + ... cnxn = v
    • + *
    • c1x1 + ... cnxn <= v
    • + *
    • c1x1 + ... cnxn >= v
    • + *
    + *

    + * @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. + *

    + * A linear constraint with a single linear equation has one of the forms: + *

      + *
    • c1x1 + ... cnxn = v
    • + *
    • c1x1 + ... cnxn <= v
    • + *
    • c1x1 + ... cnxn >= v
    • + *
    + *

    + * @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. + *

    + * A linear constraint with two linear equation has one of the forms: + *

      + *
    • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
    • + *
    + *

    + * @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. + *

    + * A linear constraint with two linear equation has one of the forms: + *

      + *
    • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
    • + *
    • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
    • + *
    + *

    + * @param lhsCoefficients The coefficients of the linear expression on the left hand side of the constraint + * @param lhsConstant The constant term of the linear expression on the left hand side of the constraint + * @param relationship The type of (in)equality used in the constraint + * @param rhsCoefficients The coefficients of the linear expression on the right hand side of the constraint + * @param rhsConstant The constant term of the linear expression on the right hand side of the constraint + */ + public LinearConstraint(final RealVector lhsCoefficients, final double lhsConstant, + final Relationship relationship, + final RealVector rhsCoefficients, final double rhsConstant) { + this.coefficients = lhsCoefficients.subtract(rhsCoefficients); + this.relationship = relationship; + this.value = rhsConstant - lhsConstant; + } + + /** + * Gets the coefficients of the constraint (left hand side). + * + * @return the coefficients of the constraint (left hand side). + */ + public RealVector getCoefficients() { + return coefficients; + } + + /** + * Gets the relationship between left and right hand sides. + * + * @return the relationship between left and right hand sides. + */ + public Relationship getRelationship() { + return relationship; + } + + /** + * Gets the value of the constraint (right hand side). + * + * @return the value of the constraint (right hand side). + */ + public double getValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof LinearConstraint) { + LinearConstraint rhs = (LinearConstraint) other; + return relationship == rhs.relationship && + value == rhs.value && + coefficients.equals(rhs.coefficients); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return relationship.hashCode() ^ + Double.valueOf(value).hashCode() ^ + coefficients.hashCode(); + } + + /** + * Serialize the instance. + * @param oos stream where object should be written + * @throws IOException if object cannot be written to stream + */ + private void writeObject(ObjectOutputStream oos) + throws IOException { + oos.defaultWriteObject(); + MatrixUtils.serializeRealVector(coefficients, oos); + } + + /** + * Deserialize the instance. + * @param ois stream from which the object should be read + * @throws ClassNotFoundException if a class in the stream cannot be found + * @throws IOException if object cannot be read from the stream + */ + private void readObject(ObjectInputStream ois) + throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + MatrixUtils.deserializeRealVector(this, "coefficients", ois); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraintSet.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraintSet.java new file mode 100644 index 000000000..763fbd7ae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearConstraintSet.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.Collection; +import java.util.Collections; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Class that represents a set of {@link LinearConstraint linear constraints}. + * + * @since 3.1 + */ +public class LinearConstraintSet implements OptimizationData { + /** Set of constraints. */ + private final Set linearConstraints = new LinkedHashSet(); + + /** + * Creates a set containing the given constraints. + * + * @param constraints Constraints. + */ + public LinearConstraintSet(LinearConstraint... constraints) { + for (LinearConstraint c : constraints) { + linearConstraints.add(c); + } + } + + /** + * Creates a set containing the given constraints. + * + * @param constraints Constraints. + */ + public LinearConstraintSet(Collection constraints) { + linearConstraints.addAll(constraints); + } + + /** + * Gets the set of linear constraints. + * + * @return the constraints. + */ + public Collection getConstraints() { + return Collections.unmodifiableSet(linearConstraints); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.java new file mode 100644 index 000000000..a0af5522e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.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 com.fr.third.org.apache.commons.math3.optim.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * An objective function for a linear optimization problem. + *

    + * A linear objective function has one the form: + *

    + * c1x1 + ... cnxn + d
    + * 
    + * The ci and d are the coefficients of the equation, + * the xi are the coordinates of the current point. + *

    + * + * @since 2.0 + */ +public class LinearObjectiveFunction + implements MultivariateFunction, + OptimizationData, + Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = -4531815507568396090L; + /** Coefficients of the linear equation (ci). */ + private final transient RealVector coefficients; + /** Constant term of the linear equation. */ + private final double constantTerm; + + /** + * @param coefficients Coefficients for the linear equation being optimized. + * @param constantTerm Constant term of the linear equation. + */ + public LinearObjectiveFunction(double[] coefficients, double constantTerm) { + this(new ArrayRealVector(coefficients), constantTerm); + } + + /** + * @param coefficients Coefficients for the linear equation being optimized. + * @param constantTerm Constant term of the linear equation. + */ + public LinearObjectiveFunction(RealVector coefficients, double constantTerm) { + this.coefficients = coefficients; + this.constantTerm = constantTerm; + } + + /** + * Gets the coefficients of the linear equation being optimized. + * + * @return coefficients of the linear equation being optimized. + */ + public RealVector getCoefficients() { + return coefficients; + } + + /** + * Gets the constant of the linear equation being optimized. + * + * @return constant of the linear equation being optimized. + */ + public double getConstantTerm() { + return constantTerm; + } + + /** + * Computes the value of the linear equation at the current point. + * + * @param point Point at which linear equation must be evaluated. + * @return the value of the linear equation at the current point. + */ + public double value(final double[] point) { + return value(new ArrayRealVector(point, false)); + } + + /** + * Computes the value of the linear equation at the current point. + * + * @param point Point at which linear equation must be evaluated. + * @return the value of the linear equation at the current point. + */ + public double value(final RealVector point) { + return coefficients.dotProduct(point) + constantTerm; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof LinearObjectiveFunction) { + LinearObjectiveFunction rhs = (LinearObjectiveFunction) other; + return (constantTerm == rhs.constantTerm) && coefficients.equals(rhs.coefficients); + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Double.valueOf(constantTerm).hashCode() ^ coefficients.hashCode(); + } + + /** + * Serialize the instance. + * @param oos stream where object should be written + * @throws IOException if object cannot be written to stream + */ + private void writeObject(ObjectOutputStream oos) + throws IOException { + oos.defaultWriteObject(); + MatrixUtils.serializeRealVector(coefficients, oos); + } + + /** + * Deserialize the instance. + * @param ois stream from which the object should be read + * @throws ClassNotFoundException if a class in the stream cannot be found + * @throws IOException if object cannot be read from the stream + */ + private void readObject(ObjectInputStream ois) + throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + MatrixUtils.deserializeRealVector(this, "coefficients", ois); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearOptimizer.java new file mode 100644 index 000000000..8bd25ef6e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/LinearOptimizer.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 com.fr.third.org.apache.commons.math3.optim.linear; + +import java.util.Collection; +import java.util.Collections; + +import com.fr.third.org.apache.commons.math3.exception.TooManyIterationsException; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer; + +/** + * Base class for implementing linear optimizers. + * + * @since 3.1 + */ +public abstract class LinearOptimizer + extends MultivariateOptimizer { + /** + * Linear objective function. + */ + private LinearObjectiveFunction function; + /** + * Linear constraints. + */ + private Collection linearConstraints; + /** + * Whether to restrict the variables to non-negative values. + */ + private boolean nonNegative; + + /** + * Simple constructor with default settings. + * + */ + protected LinearOptimizer() { + super(null); // No convergence checker. + } + + /** + * @return {@code true} if the variables are restricted to non-negative values. + */ + protected boolean isRestrictedToNonNegative() { + return nonNegative; + } + + /** + * @return the optimization type. + */ + protected LinearObjectiveFunction getFunction() { + return function; + } + + /** + * @return the optimization type. + */ + protected Collection getConstraints() { + return Collections.unmodifiableCollection(linearConstraints); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * MultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link LinearObjectiveFunction}
    • + *
    • {@link LinearConstraintSet}
    • + *
    • {@link NonNegativeConstraint}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyIterationsException if the maximal number of + * iterations is exceeded. + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyIterationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link LinearObjectiveFunction}
    • + *
    • {@link LinearConstraintSet}
    • + *
    • {@link NonNegativeConstraint}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof LinearObjectiveFunction) { + function = (LinearObjectiveFunction) data; + continue; + } + if (data instanceof LinearConstraintSet) { + linearConstraints = ((LinearConstraintSet) data).getConstraints(); + continue; + } + if (data instanceof NonNegativeConstraint) { + nonNegative = ((NonNegativeConstraint) data).isRestrictedToNonNegative(); + continue; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java new file mode 100644 index 000000000..57c41c445 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * This class represents exceptions thrown by optimizers when no solution fulfills the constraints. + * + * @since 2.0 + */ +public class NoFeasibleSolutionException extends MathIllegalStateException { + /** Serializable version identifier. */ + private static final long serialVersionUID = -3044253632189082760L; + + /** + * Simple constructor using a default message. + */ + public NoFeasibleSolutionException() { + super(LocalizedFormats.NO_FEASIBLE_SOLUTION); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java new file mode 100644 index 000000000..8adfbc0cd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * A constraint for a linear optimization problem indicating whether all + * variables must be restricted to non-negative values. + * + * @since 3.1 + */ +public class NonNegativeConstraint implements OptimizationData { + /** Whether the variables are all positive. */ + private final boolean isRestricted; + + /** + * @param restricted If {@code true}, all the variables must be positive. + */ + public NonNegativeConstraint(boolean restricted) { + isRestricted = restricted; + } + + /** + * Indicates whether all the variables must be restricted to non-negative + * values. + * + * @return {@code true} if all the variables must be positive. + */ + public boolean isRestrictedToNonNegative() { + return isRestricted; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/PivotSelectionRule.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/PivotSelectionRule.java new file mode 100644 index 000000000..ed74a1074 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/PivotSelectionRule.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Pivot selection rule to the use for a Simplex solver. + * + * @since 3.3 + */ +public enum PivotSelectionRule implements OptimizationData { + /** + * The classical rule, the variable with the most negative coefficient + * in the objective function row will be chosen as entering variable. + */ + DANTZIG, + /** + * The first variable with a negative coefficient in the objective function + * row will be chosen as entering variable. This rule guarantees to prevent + * cycles, but may take longer to find an optimal solution. + */ + BLAND +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/Relationship.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/Relationship.java new file mode 100644 index 000000000..abdfeb88c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/Relationship.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +/** + * Types of relationships between two cells in a Solver {@link LinearConstraint}. + * + * @since 2.0 + */ +public enum Relationship { + /** Equality relationship. */ + EQ("="), + /** Lesser than or equal relationship. */ + LEQ("<="), + /** Greater than or equal relationship. */ + GEQ(">="); + + /** Display string for the relationship. */ + private final String stringValue; + + /** + * Simple constructor. + * + * @param stringValue Display string for the relationship. + */ + Relationship(String stringValue) { + this.stringValue = stringValue; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return stringValue; + } + + /** + * Gets the relationship obtained when multiplying all coefficients by -1. + * + * @return the opposite relationship. + */ + public Relationship oppositeRelationship() { + switch (this) { + case LEQ : + return GEQ; + case GEQ : + return LEQ; + default : + return EQ; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexSolver.java new file mode 100644 index 000000000..1da377891 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexSolver.java @@ -0,0 +1,409 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.TooManyIterationsException; +import com.fr.third.org.apache.commons.math3.optim.MaxIter; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Solves a linear problem using the "Two-Phase Simplex" method. + *

    + * The {@link SimplexSolver} supports the following {@link OptimizationData} data provided + * as arguments to {@link #optimize(OptimizationData...)}: + *

      + *
    • objective function: {@link LinearObjectiveFunction} - mandatory
    • + *
    • linear constraints {@link LinearConstraintSet} - mandatory
    • + *
    • type of optimization: {@link GoalType GoalType} + * - optional, default: {@link GoalType#MINIMIZE MINIMIZE}
    • + *
    • whether to allow negative values as solution: {@link NonNegativeConstraint} - optional, default: true
    • + *
    • pivot selection rule: {@link PivotSelectionRule} - optional, default {@link PivotSelectionRule#DANTZIG}
    • + *
    • callback for the best solution: {@link SolutionCallback} - optional
    • + *
    • maximum number of iterations: {@link MaxIter} - optional, default: {@link Integer#MAX_VALUE}
    • + *
    + *

    + * Note: Depending on the problem definition, the default convergence criteria + * may be too strict, resulting in {@link NoFeasibleSolutionException} or + * {@link TooManyIterationsException}. In such a case it is advised to adjust these + * criteria with more appropriate values, e.g. relaxing the epsilon value. + *

    + * Default convergence criteria: + *

      + *
    • Algorithm convergence: 1e-6
    • + *
    • Floating-point comparisons: 10 ulp
    • + *
    • Cut-Off value: 1e-10
    • + *
    + *

    + * The cut-off value has been introduced to handle the case of very small pivot elements + * in the Simplex tableau, as these may lead to numerical instabilities and degeneracy. + * Potential pivot elements smaller than this value will be treated as if they were zero + * and are thus not considered by the pivot selection mechanism. The default value is safe + * for many problems, but may need to be adjusted in case of very small coefficients + * used in either the {@link LinearConstraint} or {@link LinearObjectiveFunction}. + * + * @since 2.0 + */ +public class SimplexSolver extends LinearOptimizer { + /** Default amount of error to accept in floating point comparisons (as ulps). */ + static final int DEFAULT_ULPS = 10; + + /** Default cut-off value. */ + static final double DEFAULT_CUT_OFF = 1e-10; + + /** Default amount of error to accept for algorithm convergence. */ + private static final double DEFAULT_EPSILON = 1.0e-6; + + /** Amount of error to accept for algorithm convergence. */ + private final double epsilon; + + /** Amount of error to accept in floating point comparisons (as ulps). */ + private final int maxUlps; + + /** + * Cut-off value for entries in the tableau: values smaller than the cut-off + * are treated as zero to improve numerical stability. + */ + private final double cutOff; + + /** The pivot selection method to use. */ + private PivotSelectionRule pivotSelection; + + /** + * The solution callback to access the best solution found so far in case + * the optimizer fails to find an optimal solution within the iteration limits. + */ + private SolutionCallback solutionCallback; + + /** + * Builds a simplex solver with default settings. + */ + public SimplexSolver() { + this(DEFAULT_EPSILON, DEFAULT_ULPS, DEFAULT_CUT_OFF); + } + + /** + * Builds a simplex solver with a specified accepted amount of error. + * + * @param epsilon Amount of error to accept for algorithm convergence. + */ + public SimplexSolver(final double epsilon) { + this(epsilon, DEFAULT_ULPS, DEFAULT_CUT_OFF); + } + + /** + * Builds a simplex solver with a specified accepted amount of error. + * + * @param epsilon Amount of error to accept for algorithm convergence. + * @param maxUlps Amount of error to accept in floating point comparisons. + */ + public SimplexSolver(final double epsilon, final int maxUlps) { + this(epsilon, maxUlps, DEFAULT_CUT_OFF); + } + + /** + * Builds a simplex solver with a specified accepted amount of error. + * + * @param epsilon Amount of error to accept for algorithm convergence. + * @param maxUlps Amount of error to accept in floating point comparisons. + * @param cutOff Values smaller than the cutOff are treated as zero. + */ + public SimplexSolver(final double epsilon, final int maxUlps, final double cutOff) { + this.epsilon = epsilon; + this.maxUlps = maxUlps; + this.cutOff = cutOff; + this.pivotSelection = PivotSelectionRule.DANTZIG; + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link LinearOptimizer#optimize(OptimizationData...) + * LinearOptimizer}, this method will register the following data: + *

      + *
    • {@link SolutionCallback}
    • + *
    • {@link PivotSelectionRule}
    • + *
    + * + * @return {@inheritDoc} + * @throws TooManyIterationsException if the maximal number of iterations is exceeded. + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyIterationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. + * In addition to those documented in + * {@link LinearOptimizer#parseOptimizationData(OptimizationData[]) + * LinearOptimizer}, this method will register the following data: + *
      + *
    • {@link SolutionCallback}
    • + *
    • {@link PivotSelectionRule}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // reset the callback before parsing + solutionCallback = null; + + for (OptimizationData data : optData) { + if (data instanceof SolutionCallback) { + solutionCallback = (SolutionCallback) data; + continue; + } + if (data instanceof PivotSelectionRule) { + pivotSelection = (PivotSelectionRule) data; + continue; + } + } + } + + /** + * Returns the column with the most negative coefficient in the objective function row. + * + * @param tableau Simple tableau for the problem. + * @return the column with the most negative coefficient. + */ + private Integer getPivotColumn(SimplexTableau tableau) { + double minValue = 0; + Integer minPos = null; + for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getWidth() - 1; i++) { + final double entry = tableau.getEntry(0, i); + // check if the entry is strictly smaller than the current minimum + // do not use a ulp/epsilon check + if (entry < minValue) { + minValue = entry; + minPos = i; + + // Bland's rule: chose the entering column with the lowest index + if (pivotSelection == PivotSelectionRule.BLAND && isValidPivotColumn(tableau, i)) { + break; + } + } + } + return minPos; + } + + /** + * Checks whether the given column is valid pivot column, i.e. will result + * in a valid pivot row. + *

    + * When applying Bland's rule to select the pivot column, it may happen that + * there is no corresponding pivot row. This method will check if the selected + * pivot column will return a valid pivot row. + * + * @param tableau simplex tableau for the problem + * @param col the column to test + * @return {@code true} if the pivot column is valid, {@code false} otherwise + */ + private boolean isValidPivotColumn(SimplexTableau tableau, int col) { + for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) { + final double entry = tableau.getEntry(i, col); + + // do the same check as in getPivotRow + if (Precision.compareTo(entry, 0d, cutOff) > 0) { + return true; + } + } + return false; + } + + /** + * Returns the row with the minimum ratio as given by the minimum ratio test (MRT). + * + * @param tableau Simplex tableau for the problem. + * @param col Column to test the ratio of (see {@link #getPivotColumn(SimplexTableau)}). + * @return the row with the minimum ratio. + */ + private Integer getPivotRow(SimplexTableau tableau, final int col) { + // create a list of all the rows that tie for the lowest score in the minimum ratio test + List minRatioPositions = new ArrayList(); + double minRatio = Double.MAX_VALUE; + for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) { + final double rhs = tableau.getEntry(i, tableau.getWidth() - 1); + final double entry = tableau.getEntry(i, col); + + // only consider pivot elements larger than the cutOff threshold + // selecting others may lead to degeneracy or numerical instabilities + if (Precision.compareTo(entry, 0d, cutOff) > 0) { + final double ratio = FastMath.abs(rhs / entry); + // check if the entry is strictly equal to the current min ratio + // do not use a ulp/epsilon check + final int cmp = Double.compare(ratio, minRatio); + if (cmp == 0) { + minRatioPositions.add(i); + } else if (cmp < 0) { + minRatio = ratio; + minRatioPositions.clear(); + minRatioPositions.add(i); + } + } + } + + if (minRatioPositions.size() == 0) { + return null; + } else if (minRatioPositions.size() > 1) { + // there's a degeneracy as indicated by a tie in the minimum ratio test + + // 1. check if there's an artificial variable that can be forced out of the basis + if (tableau.getNumArtificialVariables() > 0) { + for (Integer row : minRatioPositions) { + for (int i = 0; i < tableau.getNumArtificialVariables(); i++) { + int column = i + tableau.getArtificialVariableOffset(); + final double entry = tableau.getEntry(row, column); + if (Precision.equals(entry, 1d, maxUlps) && row.equals(tableau.getBasicRow(column))) { + return row; + } + } + } + } + + // 2. apply Bland's rule to prevent cycling: + // take the row for which the corresponding basic variable has the smallest index + // + // see http://www.stanford.edu/class/msande310/blandrule.pdf + // see http://en.wikipedia.org/wiki/Bland%27s_rule (not equivalent to the above paper) + + Integer minRow = null; + int minIndex = tableau.getWidth(); + for (Integer row : minRatioPositions) { + final int basicVar = tableau.getBasicVariable(row); + if (basicVar < minIndex) { + minIndex = basicVar; + minRow = row; + } + } + return minRow; + } + return minRatioPositions.get(0); + } + + /** + * Runs one iteration of the Simplex method on the given model. + * + * @param tableau Simple tableau for the problem. + * @throws TooManyIterationsException if the allowed number of iterations has been exhausted. + * @throws UnboundedSolutionException if the model is found not to have a bounded solution. + */ + protected void doIteration(final SimplexTableau tableau) + throws TooManyIterationsException, + UnboundedSolutionException { + + incrementIterationCount(); + + Integer pivotCol = getPivotColumn(tableau); + Integer pivotRow = getPivotRow(tableau, pivotCol); + if (pivotRow == null) { + throw new UnboundedSolutionException(); + } + + tableau.performRowOperations(pivotCol, pivotRow); + } + + /** + * Solves Phase 1 of the Simplex method. + * + * @param tableau Simple tableau for the problem. + * @throws TooManyIterationsException if the allowed number of iterations has been exhausted. + * @throws UnboundedSolutionException if the model is found not to have a bounded solution. + * @throws NoFeasibleSolutionException if there is no feasible solution? + */ + protected void solvePhase1(final SimplexTableau tableau) + throws TooManyIterationsException, + UnboundedSolutionException, + NoFeasibleSolutionException { + + // make sure we're in Phase 1 + if (tableau.getNumArtificialVariables() == 0) { + return; + } + + while (!tableau.isOptimal()) { + doIteration(tableau); + } + + // if W is not zero then we have no feasible solution + if (!Precision.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0d, epsilon)) { + throw new NoFeasibleSolutionException(); + } + } + + /** {@inheritDoc} */ + @Override + public PointValuePair doOptimize() + throws TooManyIterationsException, + UnboundedSolutionException, + NoFeasibleSolutionException { + + // reset the tableau to indicate a non-feasible solution in case + // we do not pass phase 1 successfully + if (solutionCallback != null) { + solutionCallback.setTableau(null); + } + + final SimplexTableau tableau = + new SimplexTableau(getFunction(), + getConstraints(), + getGoalType(), + isRestrictedToNonNegative(), + epsilon, + maxUlps); + + solvePhase1(tableau); + tableau.dropPhase1Objective(); + + // after phase 1, we are sure to have a feasible solution + if (solutionCallback != null) { + solutionCallback.setTableau(tableau); + } + + while (!tableau.isOptimal()) { + doIteration(tableau); + } + + // check that the solution respects the nonNegative restriction in case + // the epsilon/cutOff values are too large for the actual linear problem + // (e.g. with very small constraint coefficients), the solver might actually + // find a non-valid solution (with negative coefficients). + final PointValuePair solution = tableau.getSolution(); + if (isRestrictedToNonNegative()) { + final double[] coeff = solution.getPoint(); + for (int i = 0; i < coeff.length; i++) { + if (Precision.compareTo(coeff[i], 0, epsilon) < 0) { + throw new NoFeasibleSolutionException(); + } + } + } + return solution; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexTableau.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexTableau.java new file mode 100644 index 000000000..ae6556351 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SimplexTableau.java @@ -0,0 +1,713 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * A tableau for use in the Simplex method. + * + *

    + * Example: + *

    + *   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
    + * 
    + * W: Phase 1 objective function
    + * Z: Phase 2 objective function
    + * x1 & x2: Decision variables
    + * x-: Extra decision variable to allow for negative values
    + * s1 & s2: Slack/Surplus variables
    + * a1: Artificial variable
    + * RHS: Right hand side
    + *

    + * @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 constraints; + + /** Whether to restrict the variables to non-negative values. */ + private final boolean restrictToNonNegative; + + /** The variables each column represents */ + private final List columnLabels = new ArrayList(); + + /** Simple tableau. */ + private transient Array2DRowRealMatrix tableau; + + /** Number of decision variables. */ + private final int numDecisionVariables; + + /** Number of slack variables. */ + private final int numSlackVariables; + + /** Number of artificial variables. */ + private int numArtificialVariables; + + /** Amount of error to accept when checking for optimality. */ + private final double epsilon; + + /** Amount of error to accept in floating point comparisons. */ + private final int maxUlps; + + /** Maps basic variables to row they are basic in. */ + private int[] basicVariables; + + /** Maps rows to their corresponding basic variables. */ + private int[] basicRows; + + /** + * Builds a tableau for a linear problem. + * + * @param f Linear objective function. + * @param constraints Linear constraints. + * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE} + * or {@link GoalType#MINIMIZE}. + * @param restrictToNonNegative Whether to restrict the variables to non-negative values. + * @param epsilon Amount of error to accept when checking for optimality. + */ + SimplexTableau(final LinearObjectiveFunction f, + final Collection constraints, + final GoalType goalType, + final boolean restrictToNonNegative, + final double epsilon) { + this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS); + } + + /** + * Build a tableau for a linear problem. + * @param f linear objective function + * @param constraints linear constraints + * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE} + * @param restrictToNonNegative whether to restrict the variables to non-negative values + * @param epsilon amount of error to accept when checking for optimality + * @param maxUlps amount of error to accept in floating point comparisons + */ + SimplexTableau(final LinearObjectiveFunction f, + final Collection constraints, + final GoalType goalType, + final boolean restrictToNonNegative, + final double epsilon, + final int maxUlps) { + this.f = f; + this.constraints = normalizeConstraints(constraints); + this.restrictToNonNegative = restrictToNonNegative; + this.epsilon = epsilon; + this.maxUlps = maxUlps; + this.numDecisionVariables = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1); + this.numSlackVariables = getConstraintTypeCounts(Relationship.LEQ) + + getConstraintTypeCounts(Relationship.GEQ); + this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) + + getConstraintTypeCounts(Relationship.GEQ); + this.tableau = createTableau(goalType == GoalType.MAXIMIZE); + // initialize the basic variables for phase 1: + // we know that only slack or artificial variables can be basic + initializeBasicVariables(getSlackVariableOffset()); + initializeColumnLabels(); + } + + /** + * Initialize the labels for the columns. + */ + protected void initializeColumnLabels() { + if (getNumObjectiveFunctions() == 2) { + columnLabels.add("W"); + } + columnLabels.add("Z"); + for (int i = 0; i < getOriginalNumDecisionVariables(); i++) { + columnLabels.add("x" + i); + } + if (!restrictToNonNegative) { + columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL); + } + for (int i = 0; i < getNumSlackVariables(); i++) { + columnLabels.add("s" + i); + } + for (int i = 0; i < getNumArtificialVariables(); i++) { + columnLabels.add("a" + i); + } + columnLabels.add("RHS"); + } + + /** + * Create the tableau by itself. + * @param maximize if true, goal is to maximize the objective function + * @return created tableau + */ + protected Array2DRowRealMatrix createTableau(final boolean maximize) { + + // create a matrix of the correct size + int width = numDecisionVariables + numSlackVariables + + numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS + int height = constraints.size() + getNumObjectiveFunctions(); + Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width); + + // initialize the objective function rows + if (getNumObjectiveFunctions() == 2) { + matrix.setEntry(0, 0, -1); + } + + int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1; + matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1); + RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients(); + copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]); + matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm()); + + if (!restrictToNonNegative) { + matrix.setEntry(zIndex, getSlackVariableOffset() - 1, + getInvertedCoefficientSum(objectiveCoefficients)); + } + + // initialize the constraint rows + int slackVar = 0; + int artificialVar = 0; + for (int i = 0; i < constraints.size(); i++) { + LinearConstraint constraint = constraints.get(i); + int row = getNumObjectiveFunctions() + i; + + // decision variable coefficients + copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]); + + // x- + if (!restrictToNonNegative) { + matrix.setEntry(row, getSlackVariableOffset() - 1, + getInvertedCoefficientSum(constraint.getCoefficients())); + } + + // RHS + matrix.setEntry(row, width - 1, constraint.getValue()); + + // slack variables + if (constraint.getRelationship() == Relationship.LEQ) { + matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1); // slack + } else if (constraint.getRelationship() == Relationship.GEQ) { + matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess + } + + // artificial variables + if ((constraint.getRelationship() == Relationship.EQ) || + (constraint.getRelationship() == Relationship.GEQ)) { + matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1); + matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1); + matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row))); + } + } + + return matrix; + } + + /** + * Get new versions of the constraints which have positive right hand sides. + * @param originalConstraints original (not normalized) constraints + * @return new versions of the constraints + */ + public List normalizeConstraints(Collection originalConstraints) { + List normalized = new ArrayList(originalConstraints.size()); + for (LinearConstraint constraint : originalConstraints) { + normalized.add(normalize(constraint)); + } + return normalized; + } + + /** + * Get a new equation equivalent to this one with a positive right hand side. + * @param constraint reference constraint + * @return new equation + */ + private LinearConstraint normalize(final LinearConstraint constraint) { + if (constraint.getValue() < 0) { + return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1), + constraint.getRelationship().oppositeRelationship(), + -1 * constraint.getValue()); + } + return new LinearConstraint(constraint.getCoefficients(), + constraint.getRelationship(), constraint.getValue()); + } + + /** + * Get the number of objective functions in this tableau. + * @return 2 for Phase 1. 1 for Phase 2. + */ + protected final int getNumObjectiveFunctions() { + return this.numArtificialVariables > 0 ? 2 : 1; + } + + /** + * Get a count of constraints corresponding to a specified relationship. + * @param relationship relationship to count + * @return number of constraint with the specified relationship + */ + private int getConstraintTypeCounts(final Relationship relationship) { + int count = 0; + for (final LinearConstraint constraint : constraints) { + if (constraint.getRelationship() == relationship) { + ++count; + } + } + return count; + } + + /** + * Get the -1 times the sum of all coefficients in the given array. + * @param coefficients coefficients to sum + * @return the -1 times the sum of all coefficients in the given array. + */ + protected static double getInvertedCoefficientSum(final RealVector coefficients) { + double sum = 0; + for (double coefficient : coefficients.toArray()) { + sum -= coefficient; + } + return sum; + } + + /** + * Checks whether the given column is basic. + * @param col index of the column to check + * @return the row that the variable is basic in. null if the column is not basic + */ + protected Integer getBasicRow(final int col) { + final int row = basicVariables[col]; + return row == -1 ? null : row; + } + + /** + * Returns the variable that is basic in this row. + * @param row the index of the row to check + * @return the variable that is basic for this row. + */ + protected int getBasicVariable(final int row) { + return basicRows[row]; + } + + /** + * Initializes the basic variable / row mapping. + * @param startColumn the column to start + */ + private void initializeBasicVariables(final int startColumn) { + basicVariables = new int[getWidth() - 1]; + basicRows = new int[getHeight()]; + + Arrays.fill(basicVariables, -1); + + for (int i = startColumn; i < getWidth() - 1; i++) { + Integer row = findBasicRow(i); + if (row != null) { + basicVariables[i] = row; + basicRows[row] = i; + } + } + } + + /** + * Returns the row in which the given column is basic. + * @param col index of the column + * @return the row that the variable is basic in, or {@code null} if the variable is not basic. + */ + private Integer findBasicRow(final int col) { + Integer row = null; + for (int i = 0; i < getHeight(); i++) { + final double entry = getEntry(i, col); + if (Precision.equals(entry, 1d, maxUlps) && (row == null)) { + row = i; + } else if (!Precision.equals(entry, 0d, maxUlps)) { + return null; + } + } + return row; + } + + /** + * Removes the phase 1 objective function, positive cost non-artificial variables, + * and the non-basic artificial variables from this tableau. + */ + protected void dropPhase1Objective() { + if (getNumObjectiveFunctions() == 1) { + return; + } + + final Set columnsToDrop = new TreeSet(); + columnsToDrop.add(0); + + // positive cost non-artificial variables + for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) { + final double entry = getEntry(0, i); + if (Precision.compareTo(entry, 0d, epsilon) > 0) { + columnsToDrop.add(i); + } + } + + // non-basic artificial variables + for (int i = 0; i < getNumArtificialVariables(); i++) { + int col = i + getArtificialVariableOffset(); + if (getBasicRow(col) == null) { + columnsToDrop.add(col); + } + } + + final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()]; + for (int i = 1; i < getHeight(); i++) { + int col = 0; + for (int j = 0; j < getWidth(); j++) { + if (!columnsToDrop.contains(j)) { + matrix[i - 1][col++] = getEntry(i, j); + } + } + } + + // remove the columns in reverse order so the indices are correct + Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]); + for (int i = drop.length - 1; i >= 0; i--) { + columnLabels.remove((int) drop[i]); + } + + this.tableau = new Array2DRowRealMatrix(matrix); + this.numArtificialVariables = 0; + // need to update the basic variable mappings as row/columns have been dropped + initializeBasicVariables(getNumObjectiveFunctions()); + } + + /** + * @param src the source array + * @param dest the destination array + */ + private void copyArray(final double[] src, final double[] dest) { + System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length); + } + + /** + * Returns whether the problem is at an optimal state. + * @return whether the model has been solved + */ + boolean isOptimal() { + final double[] objectiveFunctionRow = getRow(0); + final int end = getRhsOffset(); + for (int i = getNumObjectiveFunctions(); i < end; i++) { + final double entry = objectiveFunctionRow[i]; + if (Precision.compareTo(entry, 0d, epsilon) < 0) { + return false; + } + } + return true; + } + + /** + * Get the current solution. + * @return current solution + */ + protected PointValuePair getSolution() { + int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL); + Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null; + double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset()); + + final Set usedBasicRows = new HashSet(); + final double[] coefficients = new double[getOriginalNumDecisionVariables()]; + for (int i = 0; i < coefficients.length; i++) { + int colIndex = columnLabels.indexOf("x" + i); + if (colIndex < 0) { + coefficients[i] = 0; + continue; + } + Integer basicRow = getBasicRow(colIndex); + if (basicRow != null && basicRow == 0) { + // if the basic row is found to be the objective function row + // set the coefficient to 0 -> this case handles unconstrained + // variables that are still part of the objective function + coefficients[i] = 0; + } else if (usedBasicRows.contains(basicRow)) { + // if multiple variables can take a given value + // then we choose the first and set the rest equal to 0 + coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative); + } else { + usedBasicRows.add(basicRow); + coefficients[i] = + (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) - + (restrictToNonNegative ? 0 : mostNegative); + } + } + return new PointValuePair(coefficients, f.value(coefficients)); + } + + /** + * Perform the row operations of the simplex algorithm with the selected + * pivot column and row. + * @param pivotCol the pivot column + * @param pivotRow the pivot row + */ + protected void performRowOperations(int pivotCol, int pivotRow) { + // set the pivot element to 1 + final double pivotVal = getEntry(pivotRow, pivotCol); + divideRow(pivotRow, pivotVal); + + // set the rest of the pivot column to 0 + for (int i = 0; i < getHeight(); i++) { + if (i != pivotRow) { + final double multiplier = getEntry(i, pivotCol); + if (multiplier != 0.0) { + subtractRow(i, pivotRow, multiplier); + } + } + } + + // update the basic variable mappings + final int previousBasicVariable = getBasicVariable(pivotRow); + basicVariables[previousBasicVariable] = -1; + basicVariables[pivotCol] = pivotRow; + basicRows[pivotRow] = pivotCol; + } + + /** + * Divides one row by a given divisor. + *

    + * After application of this operation, the following will hold: + *

    dividendRow = dividendRow / divisor
    + * + * @param dividendRowIndex index of the row + * @param divisor value of the divisor + */ + protected void divideRow(final int dividendRowIndex, final double divisor) { + final double[] dividendRow = getRow(dividendRowIndex); + for (int j = 0; j < getWidth(); j++) { + dividendRow[j] /= divisor; + } + } + + /** + * Subtracts a multiple of one row from another. + *

    + * After application of this operation, the following will hold: + *

    minuendRow = minuendRow - multiple * subtrahendRow
    + * + * @param minuendRowIndex row index + * @param subtrahendRowIndex row index + * @param multiplier multiplication factor + */ + protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) { + final double[] minuendRow = getRow(minuendRowIndex); + final double[] subtrahendRow = getRow(subtrahendRowIndex); + for (int i = 0; i < getWidth(); i++) { + minuendRow[i] -= subtrahendRow[i] * multiplier; + } + } + + /** + * Get the width of the tableau. + * @return width of the tableau + */ + protected final int getWidth() { + return tableau.getColumnDimension(); + } + + /** + * Get the height of the tableau. + * @return height of the tableau + */ + protected final int getHeight() { + return tableau.getRowDimension(); + } + + /** + * Get an entry of the tableau. + * @param row row index + * @param column column index + * @return entry at (row, column) + */ + protected final double getEntry(final int row, final int column) { + return tableau.getEntry(row, column); + } + + /** + * Set an entry of the tableau. + * @param row row index + * @param column column index + * @param value for the entry + */ + protected final void setEntry(final int row, final int column, final double value) { + tableau.setEntry(row, column, value); + } + + /** + * Get the offset of the first slack variable. + * @return offset of the first slack variable + */ + protected final int getSlackVariableOffset() { + return getNumObjectiveFunctions() + numDecisionVariables; + } + + /** + * Get the offset of the first artificial variable. + * @return offset of the first artificial variable + */ + protected final int getArtificialVariableOffset() { + return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables; + } + + /** + * Get the offset of the right hand side. + * @return offset of the right hand side + */ + protected final int getRhsOffset() { + return getWidth() - 1; + } + + /** + * Get the number of decision variables. + *

    + * If variables are not restricted to positive values, this will include 1 extra decision variable to represent + * the absolute value of the most negative variable. + * + * @return number of decision variables + * @see #getOriginalNumDecisionVariables() + */ + protected final int getNumDecisionVariables() { + return numDecisionVariables; + } + + /** + * Get the original number of decision variables. + * @return original number of decision variables + * @see #getNumDecisionVariables() + */ + protected final int getOriginalNumDecisionVariables() { + return f.getCoefficients().getDimension(); + } + + /** + * Get the number of slack variables. + * @return number of slack variables + */ + protected final int getNumSlackVariables() { + return numSlackVariables; + } + + /** + * Get the number of artificial variables. + * @return number of artificial variables + */ + protected final int getNumArtificialVariables() { + return numArtificialVariables; + } + + /** + * Get the row from the tableau. + * @param row the row index + * @return the reference to the underlying row data + */ + protected final double[] getRow(int row) { + return tableau.getDataRef()[row]; + } + + /** + * Get the tableau data. + * @return tableau data + */ + protected final double[][] getData() { + return tableau.getData(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof SimplexTableau) { + SimplexTableau rhs = (SimplexTableau) other; + return (restrictToNonNegative == rhs.restrictToNonNegative) && + (numDecisionVariables == rhs.numDecisionVariables) && + (numSlackVariables == rhs.numSlackVariables) && + (numArtificialVariables == rhs.numArtificialVariables) && + (epsilon == rhs.epsilon) && + (maxUlps == rhs.maxUlps) && + f.equals(rhs.f) && + constraints.equals(rhs.constraints) && + tableau.equals(rhs.tableau); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Boolean.valueOf(restrictToNonNegative).hashCode() ^ + numDecisionVariables ^ + numSlackVariables ^ + numArtificialVariables ^ + Double.valueOf(epsilon).hashCode() ^ + maxUlps ^ + f.hashCode() ^ + constraints.hashCode() ^ + tableau.hashCode(); + } + + /** + * Serialize the instance. + * @param oos stream where object should be written + * @throws IOException if object cannot be written to stream + */ + private void writeObject(ObjectOutputStream oos) + throws IOException { + oos.defaultWriteObject(); + MatrixUtils.serializeRealMatrix(tableau, oos); + } + + /** + * Deserialize the instance. + * @param ois stream from which the object should be read + * @throws ClassNotFoundException if a class in the stream cannot be found + * @throws IOException if object cannot be read from the stream + */ + private void readObject(ObjectInputStream ois) + throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + MatrixUtils.deserializeRealMatrix(this, "tableau", ois); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SolutionCallback.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SolutionCallback.java new file mode 100644 index 000000000..0772eb831 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/SolutionCallback.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * A callback object that can be provided to a linear optimizer to keep track + * of the best solution found. + * + * @since 3.3 + */ +public class SolutionCallback implements OptimizationData { + /** The SimplexTableau used by the SimplexSolver. */ + private SimplexTableau tableau; + + /** + * Set the simplex tableau used during the optimization once a feasible + * solution has been found. + * + * @param tableau the simplex tableau containing a feasible solution + */ + void setTableau(final SimplexTableau tableau) { + this.tableau = tableau; + } + + /** + * Retrieve the best solution found so far. + *

    + * Note: the returned solution may not be optimal, e.g. in case + * the optimizer did reach the iteration limits. + * + * @return the best solution found so far by the optimizer, or {@code null} if + * no feasible solution could be found + */ + public PointValuePair getSolution() { + return tableau != null ? tableau.getSolution() : null; + } + + /** + * Returns if the found solution is optimal. + * @return {@code true} if the solution is optimal, {@code false} otherwise + */ + public boolean isSolutionOptimal() { + return tableau != null ? tableau.isOptimal() : false; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java new file mode 100644 index 000000000..93b889be9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * This class represents exceptions thrown by optimizers when a solution escapes to infinity. + * + * @since 2.0 + */ +public class UnboundedSolutionException extends MathIllegalStateException { + /** Serializable version identifier. */ + private static final long serialVersionUID = 940539497277290619L; + + /** + * Simple constructor using a default message. + */ + public UnboundedSolutionException() { + super(LocalizedFormats.UNBOUNDED_SOLUTION); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/package-info.java new file mode 100644 index 000000000..31a42c5e2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/linear/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Optimization algorithms for linear constrained problems. + */ +package com.fr.third.org.apache.commons.math3.optim.linear; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java new file mode 100644 index 000000000..cfa1f57b8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Goal type for an optimization problem (minimization or maximization of + * a scalar function. + * + * @since 2.0 + */ +public enum GoalType implements OptimizationData { + /** Maximization. */ + MAXIMIZE, + /** Minimization. */ + MINIMIZE +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java new file mode 100644 index 000000000..57dc18bcb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * Base class for implementing optimizers for multivariate scalar + * differentiable functions. + * It contains boiler-plate code for dealing with gradient evaluation. + * + * @since 3.1 + */ +public abstract class GradientMultivariateOptimizer + extends MultivariateOptimizer { + /** + * Gradient of the objective function. + */ + private MultivariateVectorFunction gradient; + + /** + * @param checker Convergence checker. + */ + protected GradientMultivariateOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Compute the gradient vector. + * + * @param params Point at which the gradient must be evaluated. + * @return the gradient at the specified point. + */ + protected double[] computeObjectiveGradient(final double[] params) { + return gradient.value(params); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * MultivariateOptimizer}, this method will register the following data: + *

      + *
    • {@link ObjectiveFunctionGradient}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations (of the objective function) is exceeded. + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link ObjectiveFunctionGradient}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof ObjectiveFunctionGradient) { + gradient = ((ObjectiveFunctionGradient) data).getObjectiveFunctionGradient(); + // If more data must be parsed, this statement _must_ be + // changed to "continue". + break; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java new file mode 100644 index 000000000..d44605bc3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * This class converts + * {@link MultivariateVectorFunction vectorial objective functions} to + * {@link MultivariateFunction scalar objective functions} + * when the goal is to minimize them. + *
    + * 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. + *
    + * This class computes a possibly weighted squared sum of the residuals, which is + * a scalar value. The residuals are the difference between the theoretical model + * (i.e. the output of the vectorial objective function) and the observations. The + * class implements the {@link MultivariateFunction} interface and can therefore be + * minimized by any optimizer supporting scalar objectives functions.This is one way + * to perform a least square estimation. There are other ways to do this without using + * this converter, as some optimization algorithms directly support vectorial objective + * functions. + *
    + * This class support combination of residuals with or without weights and correlations. + * + * @see MultivariateFunction + * @see MultivariateVectorFunction + * @since 2.0 + */ + +public class LeastSquaresConverter implements MultivariateFunction { + /** Underlying vectorial function. */ + private final MultivariateVectorFunction function; + /** Observations to be compared to objective function to compute residuals. */ + private final double[] observations; + /** Optional weights for the residuals. */ + private final double[] weights; + /** Optional scaling matrix (weight and correlations) for the residuals. */ + private final RealMatrix scale; + + /** + * Builds a simple converter for uncorrelated residuals with identical + * weights. + * + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations) { + this.function = function; + this.observations = observations.clone(); + this.weights = null; + this.scale = null; + } + + /** + * Builds a simple converter for uncorrelated residuals with the + * specified weights. + *

    + * The scalar objective function value is computed as: + *

    +     * objective = ∑weighti(observationi-objectivei)2
    +     * 
    + *

    + *

    + * 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.012) in the even elements and 1.0/(15.02) in the + * odd elements (i.e. reciprocals of variances). + *

    + *

    + * The array computed by the objective function, the observations array and the + * weights array must have consistent sizes or a {@link DimensionMismatchException} + * will be triggered while computing the scalar objective. + *

    + * + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + * @param weights weights to apply to the residuals + * @throws DimensionMismatchException if the observations vector and the weights + * vector dimensions do not match (objective function dimension is checked only when + * the {@link #value(double[])} method is called) + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations, + final double[] weights) { + if (observations.length != weights.length) { + throw new DimensionMismatchException(observations.length, weights.length); + } + this.function = function; + this.observations = observations.clone(); + this.weights = weights.clone(); + this.scale = null; + } + + /** + * Builds a simple converter for correlated residuals with the + * specified weights. + *

    + * The scalar objective function value is computed as: + *

    +     * objective = yTy with y = scale×(observation-objective)
    +     * 
    + *

    + *

    + * The array computed by the objective function, the observations array and the + * the scaling matrix must have consistent sizes or a {@link DimensionMismatchException} + * will be triggered while computing the scalar objective. + *

    + * + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + * @param scale scaling matrix + * @throws DimensionMismatchException if the observations vector and the scale + * matrix dimensions do not match (objective function dimension is checked only when + * the {@link #value(double[])} method is called) + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations, + final RealMatrix scale) { + if (observations.length != scale.getColumnDimension()) { + throw new DimensionMismatchException(observations.length, scale.getColumnDimension()); + } + this.function = function; + this.observations = observations.clone(); + this.weights = null; + this.scale = scale.copy(); + } + + /** {@inheritDoc} */ + public double value(final double[] point) { + // compute residuals + final double[] residuals = function.value(point); + if (residuals.length != observations.length) { + throw new DimensionMismatchException(residuals.length, observations.length); + } + for (int i = 0; i < residuals.length; ++i) { + residuals[i] -= observations[i]; + } + + // compute sum of squares + double sumSquares = 0; + if (weights != null) { + for (int i = 0; i < residuals.length; ++i) { + final double ri = residuals[i]; + sumSquares += weights[i] * ri * ri; + } + } else if (scale != null) { + for (final double yi : scale.operate(residuals)) { + sumSquares += yi * yi; + } + } else { + for (final double ri : residuals) { + sumSquares += ri * ri; + } + } + + return sumSquares; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.java new file mode 100644 index 000000000..38d230489 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.univariate.BracketFinder; +import com.fr.third.org.apache.commons.math3.optim.univariate.BrentOptimizer; +import com.fr.third.org.apache.commons.math3.optim.univariate.SearchInterval; +import com.fr.third.org.apache.commons.math3.optim.univariate.SimpleUnivariateValueChecker; +import com.fr.third.org.apache.commons.math3.optim.univariate.UnivariateObjectiveFunction; +import com.fr.third.org.apache.commons.math3.optim.univariate.UnivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.univariate.UnivariatePointValuePair; +import com.fr.third.org.apache.commons.math3.optim.MaxEval; + +/** + * Class for finding the minimum of the objective function along a given + * direction. + * + * @since 3.3 + */ +public class LineSearch { + /** + * Value that will pass the precondition check for {@link BrentOptimizer} + * but will not pass the convergence check, so that the custom checker + * will always decide when to stop the line search. + */ + private static final double REL_TOL_UNUSED = 1e-15; + /** + * Value that will pass the precondition check for {@link BrentOptimizer} + * but will not pass the convergence check, so that the custom checker + * will always decide when to stop the line search. + */ + private static final double ABS_TOL_UNUSED = Double.MIN_VALUE; + /** + * Optimizer used for line search. + */ + private final UnivariateOptimizer lineOptimizer; + /** + * Automatic bracketing. + */ + private final BracketFinder bracket = new BracketFinder(); + /** + * Extent of the initial interval used to find an interval that + * brackets the optimum. + */ + private final double initialBracketingRange; + /** + * Optimizer on behalf of which the line search must be performed. + */ + private final MultivariateOptimizer mainOptimizer; + + /** + * The {@code BrentOptimizer} default stopping criterion uses the + * tolerances to check the domain (point) values, not the function + * values. + * The {@code relativeTolerance} and {@code absoluteTolerance} + * arguments are thus passed to a {@link SimpleUnivariateValueChecker + * custom checker} that will use the function values. + * + * @param optimizer Optimizer on behalf of which the line search + * be performed. + * Its {@link MultivariateOptimizer#computeObjectiveValue(double[]) + * computeObjectiveValue} method will be called by the + * {@link #search(double[],double[]) search} method. + * @param relativeTolerance Search will stop when the function relative + * difference between successive iterations is below this value. + * @param absoluteTolerance Search will stop when the function absolute + * difference between successive iterations is below this value. + * @param initialBracketingRange Extent of the initial interval used to + * find an interval that brackets the optimum. + * If the optimized function varies a lot in the vicinity of the optimum, + * it may be necessary to provide a value lower than the distance between + * successive local minima. + */ + public LineSearch(MultivariateOptimizer optimizer, + double relativeTolerance, + double absoluteTolerance, + double initialBracketingRange) { + mainOptimizer = optimizer; + lineOptimizer = new BrentOptimizer(REL_TOL_UNUSED, + ABS_TOL_UNUSED, + new SimpleUnivariateValueChecker(relativeTolerance, + absoluteTolerance)); + this.initialBracketingRange = initialBracketingRange; + } + + /** + * Finds the number {@code alpha} that optimizes + * {@code f(startPoint + alpha * direction)}. + * + * @param startPoint Starting point. + * @param direction Search direction. + * @return the optimum. + * @throws TooManyEvaluationsException + * if the number of evaluations is exceeded. + */ + public UnivariatePointValuePair search(final double[] startPoint, + final double[] direction) { + final int n = startPoint.length; + final UnivariateFunction f = new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double alpha) { + final double[] x = new double[n]; + for (int i = 0; i < n; i++) { + x[i] = startPoint[i] + alpha * direction[i]; + } + final double obj = mainOptimizer.computeObjectiveValue(x); + return obj; + } + }; + + final GoalType goal = mainOptimizer.getGoalType(); + bracket.search(f, goal, 0, initialBracketingRange); + // Passing "MAX_VALUE" as a dummy value because it is the enclosing + // class that counts the number of evaluations (and will eventually + // generate the exception). + return lineOptimizer.optimize(new MaxEval(Integer.MAX_VALUE), + new UnivariateObjectiveFunction(f), + goal, + new SearchInterval(bracket.getLo(), + bracket.getHi(), + bracket.getMid())); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.java new file mode 100644 index 000000000..02bcef9ac --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; +import com.fr.third.org.apache.commons.math3.optim.BaseMultiStartMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * Multi-start optimizer. + * + * This class wraps an optimizer in order to use it several times in + * turn with different starting points (trying to avoid being trapped + * in a local extremum when looking for a global one). + * + * @since 3.0 + */ +public class MultiStartMultivariateOptimizer + extends BaseMultiStartMultivariateOptimizer { + /** Underlying optimizer. */ + private final MultivariateOptimizer optimizer; + /** Found optima. */ + private final List optima = new ArrayList(); + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. + * If {@code starts == 1}, the result will be same as if {@code optimizer} + * is called directly. + * @param generator Random vector generator to use for restarts. + * @throws NullArgumentException if {@code optimizer} or {@code generator} + * is {@code null}. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + public MultiStartMultivariateOptimizer(final MultivariateOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) + throws NullArgumentException, + NotStrictlyPositiveException { + super(optimizer, starts, generator); + this.optimizer = optimizer; + } + + /** + * {@inheritDoc} + */ + @Override + public PointValuePair[] getOptima() { + Collections.sort(optima, getPairComparator()); + return optima.toArray(new PointValuePair[0]); + } + + /** + * {@inheritDoc} + */ + @Override + protected void store(PointValuePair optimum) { + optima.add(optimum); + } + + /** + * {@inheritDoc} + */ + @Override + protected void clear() { + optima.clear(); + } + + /** + * @return a comparator for sorting the optima. + */ + private Comparator getPairComparator() { + return new Comparator() { + /** {@inheritDoc} */ + public int compare(final PointValuePair o1, + final PointValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return (optimizer.getGoalType() == GoalType.MINIMIZE) ? + Double.compare(v1, v2) : Double.compare(v2, v1); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.java new file mode 100644 index 000000000..2056ef41e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.function.Logit; +import com.fr.third.org.apache.commons.math3.analysis.function.Sigmoid; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.BOBYQAOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

    Adapter for mapping bounded {@link MultivariateFunction} to unbounded ones.

    + * + *

    + * This adapter can be used to wrap functions subject to simple bounds on + * parameters so they can be used by optimizers that do not directly + * support simple bounds. + *

    + *

    + * The principle is that the user function that will be wrapped will see its + * parameters bounded as required, i.e when its {@code value} method is called + * with argument array {@code point}, the elements array will fulfill requirement + * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components + * may be unbounded or bounded only on one side if the corresponding bound is + * set to an infinite value. The optimizer will not manage the user function by + * itself, but it will handle this adapter and it is this adapter that will take + * care the bounds are fulfilled. The adapter {@link #value(double[])} method will + * be called by the optimizer with unbound parameters, and the adapter will map + * the unbounded value to the bounded range using appropriate functions like + * {@link Sigmoid} for double bounded elements for example. + *

    + *

    + * As the optimizer sees only unbounded parameters, it should be noted that the + * start point or simplex expected by the optimizer should be unbounded, so the + * user is responsible for converting his bounded point to unbounded by calling + * {@link #boundedToUnbounded(double[])} before providing them to the optimizer. + * For the same reason, the point returned by the {@link + * BaseMultivariateOptimizer#optimize(int, + * MultivariateFunction, GoalType, double[])} + * method is unbounded. So to convert this point to bounded, users must call + * {@link #unboundedToBounded(double[])} by themselves!

    + *

    + * This adapter is only a poor man solution to simple bounds optimization constraints + * that can be used with simple optimizers like + * {@link SimplexOptimizer + * SimplexOptimizer}. + * A better solution is to use an optimizer that directly supports simple bounds like + * {@link CMAESOptimizer + * CMAESOptimizer} or + * {@link BOBYQAOptimizer + * BOBYQAOptimizer}. + * One caveat of this poor-man's solution is that behavior near the bounds may be + * numerically unstable as bounds are mapped from infinite values. + * Another caveat is that convergence values are evaluated by the optimizer with + * respect to unbounded variables, so there will be scales differences when + * converted to bounded variables. + *

    + * + * @see MultivariateFunctionPenaltyAdapter + * + * @since 3.0 + */ +public class MultivariateFunctionMappingAdapter + implements MultivariateFunction { + /** Underlying bounded function. */ + private final MultivariateFunction bounded; + /** Mapping functions. */ + private final Mapper[] mappers; + + /** Simple constructor. + * @param bounded bounded function + * @param lower lower bounds for each element of the input parameters array + * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for + * unbounded values) + * @param upper upper bounds for each element of the input parameters array + * (some elements may be set to {@code Double.POSITIVE_INFINITY} for + * unbounded values) + * @exception DimensionMismatchException if lower and upper bounds are not + * consistent, either according to dimension or to values + */ + public MultivariateFunctionMappingAdapter(final MultivariateFunction bounded, + final double[] lower, final double[] upper) { + // safety checks + MathUtils.checkNotNull(lower); + MathUtils.checkNotNull(upper); + if (lower.length != upper.length) { + throw new DimensionMismatchException(lower.length, upper.length); + } + for (int i = 0; i < lower.length; ++i) { + // note the following test is written in such a way it also fails for NaN + if (!(upper[i] >= lower[i])) { + throw new NumberIsTooSmallException(upper[i], lower[i], true); + } + } + + this.bounded = bounded; + this.mappers = new Mapper[lower.length]; + for (int i = 0; i < mappers.length; ++i) { + if (Double.isInfinite(lower[i])) { + if (Double.isInfinite(upper[i])) { + // element is unbounded, no transformation is needed + mappers[i] = new NoBoundsMapper(); + } else { + // element is simple-bounded on the upper side + mappers[i] = new UpperBoundMapper(upper[i]); + } + } else { + if (Double.isInfinite(upper[i])) { + // element is simple-bounded on the lower side + mappers[i] = new LowerBoundMapper(lower[i]); + } else { + // element is double-bounded + mappers[i] = new LowerUpperBoundMapper(lower[i], upper[i]); + } + } + } + } + + /** + * Maps an array from unbounded to bounded. + * + * @param point Unbounded values. + * @return the bounded values. + */ + public double[] unboundedToBounded(double[] point) { + // Map unbounded input point to bounded point. + final double[] mapped = new double[mappers.length]; + for (int i = 0; i < mappers.length; ++i) { + mapped[i] = mappers[i].unboundedToBounded(point[i]); + } + + return mapped; + } + + /** + * Maps an array from bounded to unbounded. + * + * @param point Bounded values. + * @return the unbounded values. + */ + public double[] boundedToUnbounded(double[] point) { + // Map bounded input point to unbounded point. + final double[] mapped = new double[mappers.length]; + for (int i = 0; i < mappers.length; ++i) { + mapped[i] = mappers[i].boundedToUnbounded(point[i]); + } + + return mapped; + } + + /** + * Compute the underlying function value from an unbounded point. + *

    + * This method simply bounds the unbounded point using the mappings + * set up at construction and calls the underlying function using + * the bounded point. + *

    + * @param point unbounded value + * @return underlying function value + * @see #unboundedToBounded(double[]) + */ + public double value(double[] point) { + return bounded.value(unboundedToBounded(point)); + } + + /** Mapping interface. */ + private interface Mapper { + /** + * Maps a value from unbounded to bounded. + * + * @param y Unbounded value. + * @return the bounded value. + */ + double unboundedToBounded(double y); + + /** + * Maps a value from bounded to unbounded. + * + * @param x Bounded value. + * @return the unbounded value. + */ + double boundedToUnbounded(double x); + } + + /** Local class for no bounds mapping. */ + private static class NoBoundsMapper implements Mapper { + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return y; + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return x; + } + } + + /** Local class for lower bounds mapping. */ + private static class LowerBoundMapper implements Mapper { + /** Low bound. */ + private final double lower; + + /** + * Simple constructor. + * + * @param lower lower bound + */ + LowerBoundMapper(final double lower) { + this.lower = lower; + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return lower + FastMath.exp(y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return FastMath.log(x - lower); + } + + } + + /** Local class for upper bounds mapping. */ + private static class UpperBoundMapper implements Mapper { + + /** Upper bound. */ + private final double upper; + + /** Simple constructor. + * @param upper upper bound + */ + UpperBoundMapper(final double upper) { + this.upper = upper; + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return upper - FastMath.exp(-y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return -FastMath.log(upper - x); + } + + } + + /** Local class for lower and bounds mapping. */ + private static class LowerUpperBoundMapper implements Mapper { + /** Function from unbounded to bounded. */ + private final UnivariateFunction boundingFunction; + /** Function from bounded to unbounded. */ + private final UnivariateFunction unboundingFunction; + + /** + * Simple constructor. + * + * @param lower lower bound + * @param upper upper bound + */ + LowerUpperBoundMapper(final double lower, final double upper) { + boundingFunction = new Sigmoid(lower, upper); + unboundingFunction = new Logit(lower, upper); + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return boundingFunction.value(y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return unboundingFunction.value(x); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.java new file mode 100644 index 000000000..a26a49b89 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.BOBYQAOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

    Adapter extending bounded {@link MultivariateFunction} to an unbouded + * domain using a penalty function.

    + * + *

    + * This adapter can be used to wrap functions subject to simple bounds on + * parameters so they can be used by optimizers that do not directly + * support simple bounds. + *

    + *

    + * The principle is that the user function that will be wrapped will see its + * parameters bounded as required, i.e when its {@code value} method is called + * with argument array {@code point}, the elements array will fulfill requirement + * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components + * may be unbounded or bounded only on one side if the corresponding bound is + * set to an infinite value. The optimizer will not manage the user function by + * itself, but it will handle this adapter and it is this adapter that will take + * care the bounds are fulfilled. The adapter {@link #value(double[])} method will + * be called by the optimizer with unbound parameters, and the adapter will check + * if the parameters is within range or not. If it is in range, then the underlying + * user function will be called, and if it is not the value of a penalty function + * will be returned instead. + *

    + *

    + * This adapter is only a poor-man's solution to simple bounds optimization + * constraints that can be used with simple optimizers like + * {@link SimplexOptimizer + * SimplexOptimizer}. + * A better solution is to use an optimizer that directly supports simple bounds like + * {@link CMAESOptimizer + * CMAESOptimizer} or + * {@link BOBYQAOptimizer + * BOBYQAOptimizer}. + * One caveat of this poor-man's solution is that if start point or start simplex + * is completely outside of the allowed range, only the penalty function is used, + * and the optimizer may converge without ever entering the range. + *

    + * + * @see MultivariateFunctionMappingAdapter + * + * @since 3.0 + */ +public class MultivariateFunctionPenaltyAdapter + implements MultivariateFunction { + /** Underlying bounded function. */ + private final MultivariateFunction bounded; + /** Lower bounds. */ + private final double[] lower; + /** Upper bounds. */ + private final double[] upper; + /** Penalty offset. */ + private final double offset; + /** Penalty scales. */ + private final double[] scale; + + /** + * Simple constructor. + *

    + * When the optimizer provided points are out of range, the value of the + * penalty function will be used instead of the value of the underlying + * function. In order for this penalty to be effective in rejecting this + * point during the optimization process, the penalty function value should + * be defined with care. This value is computed as: + *

    +     *   penalty(point) = offset + ∑i[scale[i] * √|point[i]-boundary[i]|]
    +     * 
    + * where indices i correspond to all the components that violates their boundaries. + *

    + *

    + * So when attempting a function minimization, offset should be larger than + * the maximum expected value of the underlying function and scale components + * should all be positive. When attempting a function maximization, offset + * should be lesser than the minimum expected value of the underlying function + * and scale components should all be negative. + * minimization, and lesser than the minimum expected value of the underlying + * function when attempting maximization. + *

    + *

    + * These choices for the penalty function have two properties. First, all out + * of range points will return a function value that is worse than the value + * returned by any in range point. Second, the penalty is worse for large + * boundaries violation than for small violations, so the optimizer has an hint + * about the direction in which it should search for acceptable points. + *

    + * @param bounded bounded function + * @param lower lower bounds for each element of the input parameters array + * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for + * unbounded values) + * @param upper upper bounds for each element of the input parameters array + * (some elements may be set to {@code Double.POSITIVE_INFINITY} for + * unbounded values) + * @param offset base offset of the penalty function + * @param scale scale of the penalty function + * @exception DimensionMismatchException if lower bounds, upper bounds and + * scales are not consistent, either according to dimension or to bounadary + * values + */ + public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded, + final double[] lower, final double[] upper, + final double offset, final double[] scale) { + + // safety checks + MathUtils.checkNotNull(lower); + MathUtils.checkNotNull(upper); + MathUtils.checkNotNull(scale); + if (lower.length != upper.length) { + throw new DimensionMismatchException(lower.length, upper.length); + } + if (lower.length != scale.length) { + throw new DimensionMismatchException(lower.length, scale.length); + } + for (int i = 0; i < lower.length; ++i) { + // note the following test is written in such a way it also fails for NaN + if (!(upper[i] >= lower[i])) { + throw new NumberIsTooSmallException(upper[i], lower[i], true); + } + } + + this.bounded = bounded; + this.lower = lower.clone(); + this.upper = upper.clone(); + this.offset = offset; + this.scale = scale.clone(); + } + + /** + * Computes the underlying function value from an unbounded point. + *

    + * This method simply returns the value of the underlying function + * if the unbounded point already fulfills the bounds, and compute + * a replacement value using the offset and scale if bounds are + * violated, without calling the function at all. + *

    + * @param point unbounded point + * @return either underlying function value or penalty function value + */ + public double value(double[] point) { + + for (int i = 0; i < scale.length; ++i) { + if ((point[i] < lower[i]) || (point[i] > upper[i])) { + // bound violation starting at this component + double sum = 0; + for (int j = i; j < scale.length; ++j) { + final double overshoot; + if (point[j] < lower[j]) { + overshoot = scale[j] * (lower[j] - point[j]); + } else if (point[j] > upper[j]) { + overshoot = scale[j] * (point[j] - upper[j]); + } else { + overshoot = 0; + } + sum += FastMath.sqrt(overshoot); + } + return offset + sum; + } + } + + // all boundaries are fulfilled, we are in the expected + // domain of the underlying function + return bounded.value(point); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java new file mode 100644 index 000000000..c705ba8be --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * Base class for a multivariate scalar function optimizer. + * + * @since 3.1 + */ +public abstract class MultivariateOptimizer + extends BaseMultivariateOptimizer { + /** Objective function. */ + private MultivariateFunction function; + /** Type of optimization. */ + private GoalType goal; + + /** + * @param checker Convergence checker. + */ + protected MultivariateOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link BaseMultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * BaseMultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link ObjectiveFunction}
    • + *
    • {@link GoalType}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link ObjectiveFunction}
    • + *
    • {@link GoalType}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof GoalType) { + goal = (GoalType) data; + continue; + } + if (data instanceof ObjectiveFunction) { + function = ((ObjectiveFunction) data).getObjectiveFunction(); + continue; + } + } + } + + /** + * @return the optimization type. + */ + public GoalType getGoalType() { + return goal; + } + + /** + * Computes the objective function value. + * This method must be called by subclasses to enforce the + * evaluation counter limit. + * + * @param params Point at which the objective function must be evaluated. + * @return the objective function value at the specified point. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + */ + public double computeObjectiveValue(double[] params) { + super.incrementEvaluationCount(); + return function.value(params); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java new file mode 100644 index 000000000..610413d04 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Scalar function to be optimized. + * + * @since 3.1 + */ +public class ObjectiveFunction implements OptimizationData { + /** Function to be optimized. */ + private final MultivariateFunction function; + + /** + * @param f Function to be optimized. + */ + public ObjectiveFunction(MultivariateFunction f) { + function = f; + } + + /** + * Gets the function to be optimized. + * + * @return the objective function. + */ + public MultivariateFunction getObjectiveFunction() { + return function; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java new file mode 100644 index 000000000..561f19141 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Gradient of the scalar function to be optimized. + * + * @since 3.1 + */ +public class ObjectiveFunctionGradient implements OptimizationData { + /** Function to be optimized. */ + private final MultivariateVectorFunction gradient; + + /** + * @param g Gradient of the function to be optimized. + */ + public ObjectiveFunctionGradient(MultivariateVectorFunction g) { + gradient = g; + } + + /** + * Gets the gradient of the function to be optimized. + * + * @return the objective function gradient. + */ + public MultivariateVectorFunction getObjectiveFunctionGradient() { + return gradient; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java new file mode 100644 index 000000000..8c1488e89 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java @@ -0,0 +1,415 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.gradient; + +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GradientMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.LineSearch; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; + + +/** + * Non-linear conjugate gradient optimizer. + *
    + * This class supports both the Fletcher-Reeves and the Polak-Ribière + * update formulas for the conjugate search directions. + * It also supports optional preconditioning. + *
    + * Constraints are not supported: the call to + * {@link #optimize(OptimizationData[]) optimize} will throw + * {@link MathUnsupportedOperationException} if bounds are passed to it. + * + * @since 2.0 + */ +public class NonLinearConjugateGradientOptimizer + extends GradientMultivariateOptimizer { + /** Update formula for the beta parameter. */ + private final Formula updateFormula; + /** Preconditioner (may be null). */ + private final Preconditioner preconditioner; + /** Line search algorithm. */ + private final LineSearch line; + + /** + * Available choices of update formulas for the updating the parameter + * that is used to compute the successive conjugate search directions. + * For non-linear conjugate gradients, there are + * two formulas: + *
      + *
    • Fletcher-Reeves formula
    • + *
    • Polak-Ribière formula
    • + *
    + * + * On the one hand, the Fletcher-Reeves formula is guaranteed to converge + * if the start point is close enough of the optimum whether the + * Polak-Ribière formula may not converge in rare cases. On the + * other hand, the Polak-Ribière formula is often faster when it + * does converge. Polak-Ribière is often used. + * + * @since 2.0 + */ + public enum Formula { + /** Fletcher-Reeves formula. */ + FLETCHER_REEVES, + /** Polak-Ribière formula. */ + POLAK_RIBIERE + } + + /** + * The initial step is a factor with respect to the search direction + * (which itself is roughly related to the gradient of the function). + *
    + * It is used to find an interval that brackets the optimum in line + * search. + * + * @since 3.1 + * @deprecated As of v3.3, this class is not used anymore. + * This setting is replaced by the {@code initialBracketingRange} + * argument to the new constructors. + */ + @Deprecated + public static class BracketingStep implements OptimizationData { + /** Initial step. */ + private final double initialStep; + + /** + * @param step Initial step for the bracket search. + */ + public BracketingStep(double step) { + initialStep = step; + } + + /** + * Gets the initial step. + * + * @return the initial step. + */ + public double getBracketingStep() { + return initialStep; + } + } + + /** + * Constructor with default tolerances for the line search (1e-8) and + * {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link Formula#FLETCHER_REEVES} or + * {@link Formula#POLAK_RIBIERE}. + * @param checker Convergence checker. + */ + public NonLinearConjugateGradientOptimizer(final Formula updateFormula, + ConvergenceChecker checker) { + this(updateFormula, + checker, + 1e-8, + 1e-8, + 1e-8, + new IdentityPreconditioner()); + } + + /** + * Constructor with default {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link Formula#FLETCHER_REEVES} or + * {@link Formula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param lineSearchSolver Solver to use during line search. + * @deprecated as of 3.3. Please use + * {@link #NonLinearConjugateGradientOptimizer(Formula,ConvergenceChecker,double,double,double)} instead. + */ + @Deprecated + public NonLinearConjugateGradientOptimizer(final Formula updateFormula, + ConvergenceChecker checker, + final UnivariateSolver lineSearchSolver) { + this(updateFormula, + checker, + lineSearchSolver, + new IdentityPreconditioner()); + } + + /** + * Constructor with default {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link Formula#FLETCHER_REEVES} or + * {@link Formula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param relativeTolerance Relative threshold for line search. + * @param absoluteTolerance Absolute threshold for line search. + * @param initialBracketingRange Extent of the initial interval used to + * find an interval that brackets the optimum in order to perform the + * line search. + * + * @see LineSearch#LineSearch(MultivariateOptimizer,double,double,double) + * @since 3.3 + */ + public NonLinearConjugateGradientOptimizer(final Formula updateFormula, + ConvergenceChecker checker, + double relativeTolerance, + double absoluteTolerance, + double initialBracketingRange) { + this(updateFormula, + checker, + relativeTolerance, + absoluteTolerance, + initialBracketingRange, + new IdentityPreconditioner()); + } + + /** + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link Formula#FLETCHER_REEVES} or + * {@link Formula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param lineSearchSolver Solver to use during line search. + * @param preconditioner Preconditioner. + * @deprecated as of 3.3. Please use + * {@link #NonLinearConjugateGradientOptimizer(Formula,ConvergenceChecker,double,double,double,Preconditioner)} instead. + */ + @Deprecated + public NonLinearConjugateGradientOptimizer(final Formula updateFormula, + ConvergenceChecker checker, + final UnivariateSolver lineSearchSolver, + final Preconditioner preconditioner) { + this(updateFormula, + checker, + lineSearchSolver.getRelativeAccuracy(), + lineSearchSolver.getAbsoluteAccuracy(), + lineSearchSolver.getAbsoluteAccuracy(), + preconditioner); + } + + /** + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link Formula#FLETCHER_REEVES} or + * {@link Formula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param preconditioner Preconditioner. + * @param relativeTolerance Relative threshold for line search. + * @param absoluteTolerance Absolute threshold for line search. + * @param initialBracketingRange Extent of the initial interval used to + * find an interval that brackets the optimum in order to perform the + * line search. + * + * @see LineSearch#LineSearch(MultivariateOptimizer,double,double,double) + * @since 3.3 + */ + public NonLinearConjugateGradientOptimizer(final Formula updateFormula, + ConvergenceChecker checker, + double relativeTolerance, + double absoluteTolerance, + double initialBracketingRange, + final Preconditioner preconditioner) { + super(checker); + + this.updateFormula = updateFormula; + this.preconditioner = preconditioner; + line = new LineSearch(this, + relativeTolerance, + absoluteTolerance, + initialBracketingRange); + } + + /** + * {@inheritDoc} + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + final ConvergenceChecker checker = getConvergenceChecker(); + final double[] point = getStartPoint(); + final GoalType goal = getGoalType(); + final int n = point.length; + double[] r = computeObjectiveGradient(point); + if (goal == GoalType.MINIMIZE) { + for (int i = 0; i < n; i++) { + r[i] = -r[i]; + } + } + + // Initial search direction. + double[] steepestDescent = preconditioner.precondition(point, r); + double[] searchDirection = steepestDescent.clone(); + + double delta = 0; + for (int i = 0; i < n; ++i) { + delta += r[i] * searchDirection[i]; + } + + PointValuePair current = null; + while (true) { + incrementIterationCount(); + + final double objective = computeObjectiveValue(point); + PointValuePair previous = current; + current = new PointValuePair(point, objective); + if (previous != null && checker.converged(getIterations(), previous, current)) { + // We have found an optimum. + return current; + } + + final double step = line.search(point, searchDirection).getPoint(); + + // Validate new point. + for (int i = 0; i < point.length; ++i) { + point[i] += step * searchDirection[i]; + } + + r = computeObjectiveGradient(point); + if (goal == GoalType.MINIMIZE) { + for (int i = 0; i < n; ++i) { + r[i] = -r[i]; + } + } + + // Compute beta. + final double deltaOld = delta; + final double[] newSteepestDescent = preconditioner.precondition(point, r); + delta = 0; + for (int i = 0; i < n; ++i) { + delta += r[i] * newSteepestDescent[i]; + } + + final double beta; + switch (updateFormula) { + case FLETCHER_REEVES: + beta = delta / deltaOld; + break; + case POLAK_RIBIERE: + double deltaMid = 0; + for (int i = 0; i < r.length; ++i) { + deltaMid += r[i] * steepestDescent[i]; + } + beta = (delta - deltaMid) / deltaOld; + break; + default: + // Should never happen. + throw new MathInternalError(); + } + steepestDescent = newSteepestDescent; + + // Compute conjugate search direction. + if (getIterations() % n == 0 || + beta < 0) { + // Break conjugation: reset search direction. + searchDirection = steepestDescent.clone(); + } else { + // Compute new conjugate search direction. + for (int i = 0; i < n; ++i) { + searchDirection[i] = steepestDescent[i] + beta * searchDirection[i]; + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + checkParameters(); + } + + /** Default identity preconditioner. */ + public static class IdentityPreconditioner implements Preconditioner { + /** {@inheritDoc} */ + public double[] precondition(double[] variables, double[] r) { + return r.clone(); + } + } + + // Class is not used anymore (cf. MATH-1092). However, it might + // be interesting to create a class similar to "LineSearch", but + // that will take advantage that the model's gradient is available. +// /** +// * Internal class for line search. +// *

    +// * 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. +// *

    +// */ +// private class LineSearchFunction implements UnivariateFunction { +// /** Current point. */ +// private final double[] currentPoint; +// /** Search direction. */ +// private final double[] searchDirection; + +// /** +// * @param point Current point. +// * @param direction Search direction. +// */ +// public LineSearchFunction(double[] point, +// double[] direction) { +// currentPoint = point.clone(); +// searchDirection = direction.clone(); +// } + +// /** {@inheritDoc} */ +// public double value(double x) { +// // current point in the search direction +// final double[] shiftedPoint = currentPoint.clone(); +// for (int i = 0; i < shiftedPoint.length; ++i) { +// shiftedPoint[i] += x * searchDirection[i]; +// } + +// // gradient of the objective function +// final double[] gradient = computeObjectiveGradient(shiftedPoint); + +// // dot product with the search direction +// double dotProduct = 0; +// for (int i = 0; i < gradient.length; ++i) { +// dotProduct += gradient[i] * searchDirection[i]; +// } + +// return dotProduct; +// } +// } + + /** + * @throws MathUnsupportedOperationException if bounds were passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + */ + private void checkParameters() { + if (getLowerBound() != null || + getUpperBound() != null) { + throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java new file mode 100644 index 000000000..67cfadcaf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.gradient; + +/** + * This interface represents a preconditioner for differentiable scalar + * objective function optimizers. + * @since 2.0 + */ +public interface Preconditioner { + /** + * Precondition a search direction. + *

    + * 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. + *

    + * @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-1r where H is the objective function hessian + */ + double[] precondition(double[] point, double[] r); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java new file mode 100644 index 000000000..5fe4c2315 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package provides optimization algorithms that require derivatives. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.gradient; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java new file mode 100644 index 000000000..63803ee28 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * This class implements the simplex concept. + * It is intended to be used in conjunction with {@link SimplexOptimizer}. + *
    + * The initial configuration of the simplex is set by the constructors + * {@link #AbstractSimplex(double[])} or {@link #AbstractSimplex(double[][])}. + * The other {@link #AbstractSimplex(int) constructor} will set all steps + * to 1, thus building a default configuration from a unit hypercube. + *
    + * Users must call the {@link #build(double[]) build} method in order + * to create the data structure that will be acted on by the other methods of + * this class. + * + * @see SimplexOptimizer + * @since 3.0 + */ +public abstract class AbstractSimplex implements OptimizationData { + /** Simplex. */ + private PointValuePair[] simplex; + /** Start simplex configuration. */ + private double[][] startConfiguration; + /** Simplex dimension (must be equal to {@code simplex.length - 1}). */ + private final int dimension; + + /** + * Build a unit hypercube simplex. + * + * @param n Dimension of the simplex. + */ + protected AbstractSimplex(int n) { + this(n, 1d); + } + + /** + * Build a hypercube simplex with the given side length. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the hypercube. + */ + protected AbstractSimplex(int n, + double sideLength) { + this(createHypercubeSteps(n, sideLength)); + } + + /** + * The start configuration for simplex is built from a box parallel to + * the canonical axes of the space. The simplex is the subset of vertices + * of a box parallel to the canonical axes. It is built as the path followed + * while traveling from one vertex of the box to the diagonally opposite + * vertex moving only along the box edges. The first vertex of the box will + * be located at the start point of the optimization. + * As an example, in dimension 3 a simplex has 4 vertices. Setting the + * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the + * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }. + * The first vertex would be set to the start point at (1, 1, 1) and the + * last vertex would be set to the diagonally opposite vertex at (2, 11, 3). + * + * @param steps Steps along the canonical axes representing box edges. They + * may be negative but not zero. + * @throws NullArgumentException if {@code steps} is {@code null}. + * @throws ZeroException if one of the steps is zero. + */ + protected AbstractSimplex(final double[] steps) { + if (steps == null) { + throw new NullArgumentException(); + } + if (steps.length == 0) { + throw new ZeroException(); + } + dimension = steps.length; + + // Only the relative position of the n final vertices with respect + // to the first one are stored. + startConfiguration = new double[dimension][dimension]; + for (int i = 0; i < dimension; i++) { + final double[] vertexI = startConfiguration[i]; + for (int j = 0; j < i + 1; j++) { + if (steps[j] == 0) { + throw new ZeroException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX); + } + System.arraycopy(steps, 0, vertexI, 0, j + 1); + } + } + } + + /** + * The real initial simplex will be set up by moving the reference + * simplex such that its first point is located at the start point of the + * optimization. + * + * @param referenceSimplex Reference simplex. + * @throws NotStrictlyPositiveException if the reference simplex does not + * contain at least one point. + * @throws DimensionMismatchException if there is a dimension mismatch + * in the reference simplex. + * @throws IllegalArgumentException if one of its vertices is duplicated. + */ + protected AbstractSimplex(final double[][] referenceSimplex) { + if (referenceSimplex.length <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SIMPLEX_NEED_ONE_POINT, + referenceSimplex.length); + } + dimension = referenceSimplex.length - 1; + + // Only the relative position of the n final vertices with respect + // to the first one are stored. + startConfiguration = new double[dimension][dimension]; + final double[] ref0 = referenceSimplex[0]; + + // Loop over vertices. + for (int i = 0; i < referenceSimplex.length; i++) { + final double[] refI = referenceSimplex[i]; + + // Safety checks. + if (refI.length != dimension) { + throw new DimensionMismatchException(refI.length, dimension); + } + for (int j = 0; j < i; j++) { + final double[] refJ = referenceSimplex[j]; + boolean allEquals = true; + for (int k = 0; k < dimension; k++) { + if (refI[k] != refJ[k]) { + allEquals = false; + break; + } + } + if (allEquals) { + throw new MathIllegalArgumentException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, + i, j); + } + } + + // Store vertex i position relative to vertex 0 position. + if (i > 0) { + final double[] confI = startConfiguration[i - 1]; + for (int k = 0; k < dimension; k++) { + confI[k] = refI[k] - ref0[k]; + } + } + } + } + + /** + * Get simplex dimension. + * + * @return the dimension of the simplex. + */ + public int getDimension() { + return dimension; + } + + /** + * Get simplex size. + * After calling the {@link #build(double[]) build} method, this method will + * will be equivalent to {@code getDimension() + 1}. + * + * @return the size of the simplex. + */ + public int getSize() { + return simplex.length; + } + + /** + * Compute the next simplex of the algorithm. + * + * @param evaluationFunction Evaluation function. + * @param comparator Comparator to use to sort simplex vertices from best + * to worst. + * @throws TooManyEvaluationsException + * if the algorithm fails to converge. + */ + public abstract void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator); + + /** + * Build an initial simplex. + * + * @param startPoint First point of the simplex. + * @throws DimensionMismatchException if the start point does not match + * simplex dimension. + */ + public void build(final double[] startPoint) { + if (dimension != startPoint.length) { + throw new DimensionMismatchException(dimension, startPoint.length); + } + + // Set first vertex. + simplex = new PointValuePair[dimension + 1]; + simplex[0] = new PointValuePair(startPoint, Double.NaN); + + // Set remaining vertices. + for (int i = 0; i < dimension; i++) { + final double[] confI = startConfiguration[i]; + final double[] vertexI = new double[dimension]; + for (int k = 0; k < dimension; k++) { + vertexI[k] = startPoint[k] + confI[k]; + } + simplex[i + 1] = new PointValuePair(vertexI, Double.NaN); + } + } + + /** + * Evaluate all the non-evaluated points of the simplex. + * + * @param evaluationFunction Evaluation function. + * @param comparator Comparator to use to sort simplex vertices from best to worst. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + */ + public void evaluate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // Evaluate the objective function at all non-evaluated simplex points. + for (int i = 0; i < simplex.length; i++) { + final PointValuePair vertex = simplex[i]; + final double[] point = vertex.getPointRef(); + if (Double.isNaN(vertex.getValue())) { + simplex[i] = new PointValuePair(point, evaluationFunction.value(point), false); + } + } + + // Sort the simplex from best to worst. + Arrays.sort(simplex, comparator); + } + + /** + * Replace the worst point of the simplex by a new point. + * + * @param pointValuePair Point to insert. + * @param comparator Comparator to use for sorting the simplex vertices + * from best to worst. + */ + protected void replaceWorstPoint(PointValuePair pointValuePair, + final Comparator comparator) { + for (int i = 0; i < dimension; i++) { + if (comparator.compare(simplex[i], pointValuePair) > 0) { + PointValuePair tmp = simplex[i]; + simplex[i] = pointValuePair; + pointValuePair = tmp; + } + } + simplex[dimension] = pointValuePair; + } + + /** + * Get the points of the simplex. + * + * @return all the simplex points. + */ + public PointValuePair[] getPoints() { + final PointValuePair[] copy = new PointValuePair[simplex.length]; + System.arraycopy(simplex, 0, copy, 0, simplex.length); + return copy; + } + + /** + * Get the simplex point stored at the requested {@code index}. + * + * @param index Location. + * @return the point at location {@code index}. + */ + public PointValuePair getPoint(int index) { + if (index < 0 || + index >= simplex.length) { + throw new OutOfRangeException(index, 0, simplex.length - 1); + } + return simplex[index]; + } + + /** + * Store a new point at location {@code index}. + * Note that no deep-copy of {@code point} is performed. + * + * @param index Location. + * @param point New value. + */ + protected void setPoint(int index, PointValuePair point) { + if (index < 0 || + index >= simplex.length) { + throw new OutOfRangeException(index, 0, simplex.length - 1); + } + simplex[index] = point; + } + + /** + * Replace all points. + * Note that no deep-copy of {@code points} is performed. + * + * @param points New Points. + */ + protected void setPoints(PointValuePair[] points) { + if (points.length != simplex.length) { + throw new DimensionMismatchException(points.length, simplex.length); + } + simplex = points; + } + + /** + * Create steps for a unit hypercube. + * + * @param n Dimension of the hypercube. + * @param sideLength Length of the sides of the hypercube. + * @return the steps. + */ + private static double[] createHypercubeSteps(int n, + double sideLength) { + final double[] steps = new double[n]; + for (int i = 0; i < n; i++) { + steps[i] = sideLength; + } + return steps; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java new file mode 100644 index 000000000..331a3dbd5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java @@ -0,0 +1,2475 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// CHECKSTYLE: stop all +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Powell's BOBYQA algorithm. This implementation is translated and + * adapted from the Fortran version available + * here. + * See + * this paper for an introduction. + *
    + * BOBYQA is particularly well suited for high dimensional problems + * where derivatives are not available. In most cases it outperforms the + * {@link PowellOptimizer} significantly. Stochastic algorithms like + * {@link CMAESOptimizer} succeed more often than BOBYQA, but are more + * expensive. BOBYQA could also be considered as a replacement of any + * derivative-based optimizer when the derivatives are approximated by + * finite differences. + * + * @since 3.0 + */ +public class BOBYQAOptimizer + extends MultivariateOptimizer { + /** Minimum dimension of the problem: {@value} */ + public static final int MINIMUM_PROBLEM_DIMENSION = 2; + /** Default value for {@link #initialTrustRegionRadius}: {@value} . */ + public static final double DEFAULT_INITIAL_RADIUS = 10.0; + /** Default value for {@link #stoppingTrustRegionRadius}: {@value} . */ + public static final double DEFAULT_STOPPING_RADIUS = 1E-8; + /** Constant 0. */ + private static final double ZERO = 0d; + /** Constant 1. */ + private static final double ONE = 1d; + /** Constant 2. */ + private static final double TWO = 2d; + /** Constant 10. */ + private static final double TEN = 10d; + /** Constant 16. */ + private static final double SIXTEEN = 16d; + /** Constant 250. */ + private static final double TWO_HUNDRED_FIFTY = 250d; + /** Constant -1. */ + private static final double MINUS_ONE = -ONE; + /** Constant 1/2. */ + private static final double HALF = ONE / 2; + /** Constant 1/4. */ + private static final double ONE_OVER_FOUR = ONE / 4; + /** Constant 1/8. */ + private static final double ONE_OVER_EIGHT = ONE / 8; + /** Constant 1/10. */ + private static final double ONE_OVER_TEN = ONE / 10; + /** Constant 1/1000. */ + private static final double ONE_OVER_A_THOUSAND = ONE / 1000; + + /** + * numberOfInterpolationPoints XXX + */ + private final int numberOfInterpolationPoints; + /** + * initialTrustRegionRadius XXX + */ + private double initialTrustRegionRadius; + /** + * stoppingTrustRegionRadius XXX + */ + private final double stoppingTrustRegionRadius; + /** Goal type (minimize or maximize). */ + private boolean isMinimize; + /** + * Current best values for the variables to be optimized. + * The vector will be changed in-place to contain the values of the least + * calculated objective function values. + */ + private ArrayRealVector currentBest; + /** Differences between the upper and lower bounds. */ + private double[] boundDifference; + /** + * Index of the interpolation point at the trust region center. + */ + private int trustRegionCenterInterpolationPointIndex; + /** + * Last n columns of matrix H (where n is the dimension + * of the problem). + * XXX "bmat" in the original code. + */ + private Array2DRowRealMatrix bMatrix; + /** + * Factorization of the leading npt square submatrix of H, this + * factorization being Z ZT, which provides both the correct + * rank and positive semi-definiteness. + * XXX "zmat" in the original code. + */ + private Array2DRowRealMatrix zMatrix; + /** + * Coordinates of the interpolation points relative to {@link #originShift}. + * XXX "xpt" in the original code. + */ + private Array2DRowRealMatrix interpolationPoints; + /** + * Shift of origin that should reduce the contributions from rounding + * errors to values of the model and Lagrange functions. + * XXX "xbase" in the original code. + */ + private ArrayRealVector originShift; + /** + * Values of the objective function at the interpolation points. + * XXX "fval" in the original code. + */ + private ArrayRealVector fAtInterpolationPoints; + /** + * Displacement from {@link #originShift} of the trust region center. + * XXX "xopt" in the original code. + */ + private ArrayRealVector trustRegionCenterOffset; + /** + * Gradient of the quadratic model at {@link #originShift} + + * {@link #trustRegionCenterOffset}. + * XXX "gopt" in the original code. + */ + private ArrayRealVector gradientAtTrustRegionCenter; + /** + * Differences {@link #getLowerBound()} - {@link #originShift}. + * All the components of every {@link #trustRegionCenterOffset} are going + * to satisfy the bounds
    + * {@link #getLowerBound() lowerBound}i ≤ + * {@link #trustRegionCenterOffset}i,
    + * with appropriate equalities when {@link #trustRegionCenterOffset} is + * on a constraint boundary. + * XXX "sl" in the original code. + */ + private ArrayRealVector lowerDifference; + /** + * Differences {@link #getUpperBound()} - {@link #originShift} + * All the components of every {@link #trustRegionCenterOffset} are going + * to satisfy the bounds
    + * {@link #trustRegionCenterOffset}i ≤ + * {@link #getUpperBound() upperBound}i,
    + * with appropriate equalities when {@link #trustRegionCenterOffset} is + * on a constraint boundary. + * XXX "su" in the original code. + */ + private ArrayRealVector upperDifference; + /** + * Parameters of the implicit second derivatives of the quadratic model. + * XXX "pq" in the original code. + */ + private ArrayRealVector modelSecondDerivativesParameters; + /** + * Point chosen by function {@link #trsbox(double,ArrayRealVector, + * ArrayRealVector, ArrayRealVector,ArrayRealVector,ArrayRealVector) trsbox} + * or {@link #altmov(int,double) altmov}. + * Usually {@link #originShift} + {@link #newPoint} is the vector of + * variables for the next evaluation of the objective function. + * It also satisfies the constraints indicated in {@link #lowerDifference} + * and {@link #upperDifference}. + * XXX "xnew" in the original code. + */ + private ArrayRealVector newPoint; + /** + * Alternative to {@link #newPoint}, chosen by + * {@link #altmov(int,double) altmov}. + * It may replace {@link #newPoint} in order to increase the denominator + * in the {@link #update(double, double, int) updating procedure}. + * XXX "xalt" in the original code. + */ + private ArrayRealVector alternativeNewPoint; + /** + * Trial step from {@link #trustRegionCenterOffset} which is usually + * {@link #newPoint} - {@link #trustRegionCenterOffset}. + * XXX "d__" in the original code. + */ + private ArrayRealVector trialStepPoint; + /** + * Values of the Lagrange functions at a new point. + * XXX "vlag" in the original code. + */ + private ArrayRealVector lagrangeValuesAtNewPoint; + /** + * Explicit second derivatives of the quadratic model. + * XXX "hq" in the original code. + */ + private ArrayRealVector modelSecondDerivativesValues; + + /** + * @param numberOfInterpolationPoints Number of interpolation conditions. + * For a problem of dimension {@code n}, its value must be in the interval + * {@code [n+2, (n+1)(n+2)/2]}. + * Choices that exceed {@code 2n+1} are not recommended. + */ + public BOBYQAOptimizer(int numberOfInterpolationPoints) { + this(numberOfInterpolationPoints, + DEFAULT_INITIAL_RADIUS, + DEFAULT_STOPPING_RADIUS); + } + + /** + * @param numberOfInterpolationPoints Number of interpolation conditions. + * For a problem of dimension {@code n}, its value must be in the interval + * {@code [n+2, (n+1)(n+2)/2]}. + * Choices that exceed {@code 2n+1} are not recommended. + * @param initialTrustRegionRadius Initial trust region radius. + * @param stoppingTrustRegionRadius Stopping trust region radius. + */ + public BOBYQAOptimizer(int numberOfInterpolationPoints, + double initialTrustRegionRadius, + double stoppingTrustRegionRadius) { + super(null); // No custom convergence criterion. + this.numberOfInterpolationPoints = numberOfInterpolationPoints; + this.initialTrustRegionRadius = initialTrustRegionRadius; + this.stoppingTrustRegionRadius = stoppingTrustRegionRadius; + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + final double[] lowerBound = getLowerBound(); + final double[] upperBound = getUpperBound(); + + // Validity checks. + setup(lowerBound, upperBound); + + isMinimize = (getGoalType() == GoalType.MINIMIZE); + currentBest = new ArrayRealVector(getStartPoint()); + + final double value = bobyqa(lowerBound, upperBound); + + return new PointValuePair(currentBest.getDataRef(), + isMinimize ? value : -value); + } + + /** + * This subroutine seeks the least value of a function of many variables, + * by applying a trust region method that forms quadratic models by + * interpolation. There is usually some freedom in the interpolation + * conditions, which is taken up by minimizing the Frobenius norm of + * the change to the second derivative of the model, beginning with the + * zero matrix. The values of the variables are constrained by upper and + * lower bounds. The arguments of the subroutine are as follows. + * + * N must be set to the number of variables and must be at least two. + * NPT is the number of interpolation conditions. Its value must be in + * the interval [N+2,(N+1)(N+2)/2]. Choices that exceed 2*N+1 are not + * recommended. + * Initial values of the variables must be set in X(1),X(2),...,X(N). They + * will be changed to the values that give the least calculated F. + * For I=1,2,...,N, XL(I) and XU(I) must provide the lower and upper + * bounds, respectively, on X(I). The construction of quadratic models + * requires XL(I) to be strictly less than XU(I) for each I. Further, + * the contribution to a model from changes to the I-th variable is + * damaged severely by rounding errors if XU(I)-XL(I) is too small. + * RHOBEG and RHOEND must be set to the initial and final values of a trust + * region radius, so both must be positive with RHOEND no greater than + * RHOBEG. Typically, RHOBEG should be about one tenth of the greatest + * expected change to a variable, while RHOEND should indicate the + * accuracy that is required in the final values of the variables. An + * error return occurs if any of the differences XU(I)-XL(I), I=1,...,N, + * is less than 2*RHOBEG. + * MAXFUN must be set to an upper bound on the number of calls of CALFUN. + * The array W will be used for working space. Its length must be at least + * (NPT+5)*(NPT+N)+3*N*(N+5)/2. + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + * @return the value of the objective at the optimum. + */ + private double bobyqa(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + + // Return if there is insufficient space between the bounds. Modify the + // initial X if necessary in order to avoid conflicts between the bounds + // and the construction of the first quadratic model. The lower and upper + // bounds on moves from the updated X are set now, in the ISL and ISU + // partitions of W, in order to provide useful and exact information about + // components of X that become within distance RHOBEG from their bounds. + + for (int j = 0; j < n; j++) { + final double boundDiff = boundDifference[j]; + lowerDifference.setEntry(j, lowerBound[j] - currentBest.getEntry(j)); + upperDifference.setEntry(j, upperBound[j] - currentBest.getEntry(j)); + if (lowerDifference.getEntry(j) >= -initialTrustRegionRadius) { + if (lowerDifference.getEntry(j) >= ZERO) { + currentBest.setEntry(j, lowerBound[j]); + lowerDifference.setEntry(j, ZERO); + upperDifference.setEntry(j, boundDiff); + } else { + currentBest.setEntry(j, lowerBound[j] + initialTrustRegionRadius); + lowerDifference.setEntry(j, -initialTrustRegionRadius); + // Computing MAX + final double deltaOne = upperBound[j] - currentBest.getEntry(j); + upperDifference.setEntry(j, FastMath.max(deltaOne, initialTrustRegionRadius)); + } + } else if (upperDifference.getEntry(j) <= initialTrustRegionRadius) { + if (upperDifference.getEntry(j) <= ZERO) { + currentBest.setEntry(j, upperBound[j]); + lowerDifference.setEntry(j, -boundDiff); + upperDifference.setEntry(j, ZERO); + } else { + currentBest.setEntry(j, upperBound[j] - initialTrustRegionRadius); + // Computing MIN + final double deltaOne = lowerBound[j] - currentBest.getEntry(j); + final double deltaTwo = -initialTrustRegionRadius; + lowerDifference.setEntry(j, FastMath.min(deltaOne, deltaTwo)); + upperDifference.setEntry(j, initialTrustRegionRadius); + } + } + } + + // Make the call of BOBYQB. + + return bobyqb(lowerBound, upperBound); + } // bobyqa + + // ---------------------------------------------------------------------------------------- + + /** + * The arguments N, NPT, X, XL, XU, RHOBEG, RHOEND, IPRINT and MAXFUN + * are identical to the corresponding arguments in SUBROUTINE BOBYQA. + * XBASE holds a shift of origin that should reduce the contributions + * from rounding errors to values of the model and Lagrange functions. + * XPT is a two-dimensional array that holds the coordinates of the + * interpolation points relative to XBASE. + * FVAL holds the values of F at the interpolation points. + * XOPT is set to the displacement from XBASE of the trust region centre. + * GOPT holds the gradient of the quadratic model at XBASE+XOPT. + * HQ holds the explicit second derivatives of the quadratic model. + * PQ contains the parameters of the implicit second derivatives of the + * quadratic model. + * BMAT holds the last N columns of H. + * ZMAT holds the factorization of the leading NPT by NPT submatrix of H, + * this factorization being ZMAT times ZMAT^T, which provides both the + * correct rank and positive semi-definiteness. + * NDIM is the first dimension of BMAT and has the value NPT+N. + * SL and SU hold the differences XL-XBASE and XU-XBASE, respectively. + * All the components of every XOPT are going to satisfy the bounds + * SL(I) .LEQ. XOPT(I) .LEQ. SU(I), with appropriate equalities when + * XOPT is on a constraint boundary. + * XNEW is chosen by SUBROUTINE TRSBOX or ALTMOV. Usually XBASE+XNEW is the + * vector of variables for the next call of CALFUN. XNEW also satisfies + * the SL and SU constraints in the way that has just been mentioned. + * XALT is an alternative to XNEW, chosen by ALTMOV, that may replace XNEW + * in order to increase the denominator in the updating of UPDATE. + * D is reserved for a trial step from XOPT, which is usually XNEW-XOPT. + * VLAG contains the values of the Lagrange functions at a new point X. + * They are part of a product that requires VLAG to be of length NDIM. + * W is a one-dimensional array that is used for working space. Its length + * must be at least 3*NDIM = 3*(NPT+N). + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + * @return the value of the objective at the optimum. + */ + private double bobyqb(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int np = n + 1; + final int nptm = npt - np; + final int nh = n * np / 2; + + final ArrayRealVector work1 = new ArrayRealVector(n); + final ArrayRealVector work2 = new ArrayRealVector(npt); + final ArrayRealVector work3 = new ArrayRealVector(npt); + + double cauchy = Double.NaN; + double alpha = Double.NaN; + double dsq = Double.NaN; + double crvmin = Double.NaN; + + // Set some constants. + // Parameter adjustments + + // Function Body + + // The call of PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ, + // BMAT and ZMAT for the first iteration, with the corresponding values of + // of NF and KOPT, which are the number of calls of CALFUN so far and the + // index of the interpolation point at the trust region centre. Then the + // initial XOPT is set too. The branch to label 720 occurs if MAXFUN is + // less than NPT. GOPT will be updated if KOPT is different from KBASE. + + trustRegionCenterInterpolationPointIndex = 0; + + prelim(lowerBound, upperBound); + double xoptsq = ZERO; + for (int i = 0; i < n; i++) { + trustRegionCenterOffset.setEntry(i, interpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex, i)); + // Computing 2nd power + final double deltaOne = trustRegionCenterOffset.getEntry(i); + xoptsq += deltaOne * deltaOne; + } + double fsave = fAtInterpolationPoints.getEntry(0); + final int kbase = 0; + + // Complete the settings that are required for the iterative procedure. + + int ntrits = 0; + int itest = 0; + int knew = 0; + int nfsav = getEvaluations(); + double rho = initialTrustRegionRadius; + double delta = rho; + double diffa = ZERO; + double diffb = ZERO; + double diffc = ZERO; + double f = ZERO; + double beta = ZERO; + double adelt = ZERO; + double denom = ZERO; + double ratio = ZERO; + double dnorm = ZERO; + double scaden = ZERO; + double biglsq = ZERO; + double distsq = ZERO; + + // Update GOPT if necessary before the first iteration and after each + // call of RESCUE that makes a call of CALFUN. + + int state = 20; + for(;;) { + switch (state) { + case 20: { + printState(20); // XXX + if (trustRegionCenterInterpolationPointIndex != kbase) { + int ih = 0; + for (int j = 0; j < n; j++) { + for (int i = 0; i <= j; i++) { + if (i < j) { + gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(i)); + } + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(j)); + ih++; + } + } + if (getEvaluations() > npt) { + for (int k = 0; k < npt; k++) { + double temp = ZERO; + for (int j = 0; j < n; j++) { + temp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + temp *= modelSecondDerivativesParameters.getEntry(k); + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + // throw new PathIsExploredException(); // XXX + } + } + + // Generate the next point in the trust region that provides a small value + // of the quadratic model subject to the constraints on the variables. + // The int NTRITS is set to the number "trust region" iterations that + // have occurred since the last "alternative" iteration. If the length + // of XNEW-XOPT is less than HALF*RHO, however, then there is a branch to + // label 650 or 680 with NTRITS=-1, instead of calculating F at XNEW. + + } + case 60: { + printState(60); // XXX + final ArrayRealVector gnew = new ArrayRealVector(n); + final ArrayRealVector xbdi = new ArrayRealVector(n); + final ArrayRealVector s = new ArrayRealVector(n); + final ArrayRealVector hs = new ArrayRealVector(n); + final ArrayRealVector hred = new ArrayRealVector(n); + + final double[] dsqCrvmin = trsbox(delta, gnew, xbdi, s, + hs, hred); + dsq = dsqCrvmin[0]; + crvmin = dsqCrvmin[1]; + + // Computing MIN + double deltaOne = delta; + double deltaTwo = FastMath.sqrt(dsq); + dnorm = FastMath.min(deltaOne, deltaTwo); + if (dnorm < HALF * rho) { + ntrits = -1; + // Computing 2nd power + deltaOne = TEN * rho; + distsq = deltaOne * deltaOne; + if (getEvaluations() <= nfsav + 2) { + state = 650; break; + } + + // The following choice between labels 650 and 680 depends on whether or + // not our work with the current RHO seems to be complete. Either RHO is + // decreased or termination occurs if the errors in the quadratic model at + // the last three interpolation points compare favourably with predictions + // of likely improvements to the model within distance HALF*RHO of XOPT. + + // Computing MAX + deltaOne = FastMath.max(diffa, diffb); + final double errbig = FastMath.max(deltaOne, diffc); + final double frhosq = rho * ONE_OVER_EIGHT * rho; + if (crvmin > ZERO && + errbig > frhosq * crvmin) { + state = 650; break; + } + final double bdtol = errbig / rho; + for (int j = 0; j < n; j++) { + double bdtest = bdtol; + if (newPoint.getEntry(j) == lowerDifference.getEntry(j)) { + bdtest = work1.getEntry(j); + } + if (newPoint.getEntry(j) == upperDifference.getEntry(j)) { + bdtest = -work1.getEntry(j); + } + if (bdtest < bdtol) { + double curv = modelSecondDerivativesValues.getEntry((j + j * j) / 2); + for (int k = 0; k < npt; k++) { + // Computing 2nd power + final double d1 = interpolationPoints.getEntry(k, j); + curv += modelSecondDerivativesParameters.getEntry(k) * (d1 * d1); + } + bdtest += HALF * curv * rho; + if (bdtest < bdtol) { + state = 650; break; + } + // throw new PathIsExploredException(); // XXX + } + } + state = 680; break; + } + ++ntrits; + + // Severe cancellation is likely to occur if XOPT is too far from XBASE. + // If the following test holds, then XBASE is shifted so that XOPT becomes + // zero. The appropriate changes are made to BMAT and to the second + // derivatives of the current model, beginning with the changes to BMAT + // that do not depend on ZMAT. VLAG is used temporarily for working space. + + } + case 90: { + printState(90); // XXX + if (dsq <= xoptsq * ONE_OVER_A_THOUSAND) { + final double fracsq = xoptsq * ONE_OVER_FOUR; + double sumpq = ZERO; + // final RealVector sumVector + // = new ArrayRealVector(npt, -HALF * xoptsq).add(interpolationPoints.operate(trustRegionCenter)); + for (int k = 0; k < npt; k++) { + sumpq += modelSecondDerivativesParameters.getEntry(k); + double sum = -HALF * xoptsq; + for (int i = 0; i < n; i++) { + sum += interpolationPoints.getEntry(k, i) * trustRegionCenterOffset.getEntry(i); + } + // sum = sumVector.getEntry(k); // XXX "testAckley" and "testDiffPow" fail. + work2.setEntry(k, sum); + final double temp = fracsq - HALF * sum; + for (int i = 0; i < n; i++) { + work1.setEntry(i, bMatrix.getEntry(k, i)); + lagrangeValuesAtNewPoint.setEntry(i, sum * interpolationPoints.getEntry(k, i) + temp * trustRegionCenterOffset.getEntry(i)); + final int ip = npt + i; + for (int j = 0; j <= i; j++) { + bMatrix.setEntry(ip, j, + bMatrix.getEntry(ip, j) + + work1.getEntry(i) * lagrangeValuesAtNewPoint.getEntry(j) + + lagrangeValuesAtNewPoint.getEntry(i) * work1.getEntry(j)); + } + } + } + + // Then the revisions of BMAT that depend on ZMAT are calculated. + + for (int m = 0; m < nptm; m++) { + double sumz = ZERO; + double sumw = ZERO; + for (int k = 0; k < npt; k++) { + sumz += zMatrix.getEntry(k, m); + lagrangeValuesAtNewPoint.setEntry(k, work2.getEntry(k) * zMatrix.getEntry(k, m)); + sumw += lagrangeValuesAtNewPoint.getEntry(k); + } + for (int j = 0; j < n; j++) { + double sum = (fracsq * sumz - HALF * sumw) * trustRegionCenterOffset.getEntry(j); + for (int k = 0; k < npt; k++) { + sum += lagrangeValuesAtNewPoint.getEntry(k) * interpolationPoints.getEntry(k, j); + } + work1.setEntry(j, sum); + for (int k = 0; k < npt; k++) { + bMatrix.setEntry(k, j, + bMatrix.getEntry(k, j) + + sum * zMatrix.getEntry(k, m)); + } + } + for (int i = 0; i < n; i++) { + final int ip = i + npt; + final double temp = work1.getEntry(i); + for (int j = 0; j <= i; j++) { + bMatrix.setEntry(ip, j, + bMatrix.getEntry(ip, j) + + temp * work1.getEntry(j)); + } + } + } + + // The following instructions complete the shift, including the changes + // to the second derivative parameters of the quadratic model. + + int ih = 0; + for (int j = 0; j < n; j++) { + work1.setEntry(j, -HALF * sumpq * trustRegionCenterOffset.getEntry(j)); + for (int k = 0; k < npt; k++) { + work1.setEntry(j, work1.getEntry(j) + modelSecondDerivativesParameters.getEntry(k) * interpolationPoints.getEntry(k, j)); + interpolationPoints.setEntry(k, j, interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j)); + } + for (int i = 0; i <= j; i++) { + modelSecondDerivativesValues.setEntry(ih, + modelSecondDerivativesValues.getEntry(ih) + + work1.getEntry(i) * trustRegionCenterOffset.getEntry(j) + + trustRegionCenterOffset.getEntry(i) * work1.getEntry(j)); + bMatrix.setEntry(npt + i, j, bMatrix.getEntry(npt + j, i)); + ih++; + } + } + for (int i = 0; i < n; i++) { + originShift.setEntry(i, originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i)); + newPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + lowerDifference.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + upperDifference.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + trustRegionCenterOffset.setEntry(i, ZERO); + } + xoptsq = ZERO; + } + if (ntrits == 0) { + state = 210; break; + } + state = 230; break; + + // XBASE is also moved to XOPT by a call of RESCUE. This calculation is + // more expensive than the previous shift, because new matrices BMAT and + // ZMAT are generated from scratch, which may include the replacement of + // interpolation points whose positions seem to be causing near linear + // dependence in the interpolation conditions. Therefore RESCUE is called + // only if rounding errors have reduced by at least a factor of two the + // denominator of the formula for updating the H matrix. It provides a + // useful safeguard, but is not invoked in most applications of BOBYQA. + + } + case 210: { + printState(210); // XXX + // Pick two alternative vectors of variables, relative to XBASE, that + // are suitable as new positions of the KNEW-th interpolation point. + // Firstly, XNEW is set to the point on a line through XOPT and another + // interpolation point that minimizes the predicted value of the next + // denominator, subject to ||XNEW - XOPT|| .LEQ. ADELT and to the SL + // and SU bounds. Secondly, XALT is set to the best feasible point on + // a constrained version of the Cauchy step of the KNEW-th Lagrange + // function, the corresponding value of the square of this function + // being returned in CAUCHY. The choice between these alternatives is + // going to be made when the denominator is calculated. + + final double[] alphaCauchy = altmov(knew, adelt); + alpha = alphaCauchy[0]; + cauchy = alphaCauchy[1]; + + for (int i = 0; i < n; i++) { + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + } + + // Calculate VLAG and BETA for the current choice of D. The scalar + // product of D with XPT(K,.) is going to be held in W(NPT+K) for + // use when VQUAD is calculated. + + } + case 230: { + printState(230); // XXX + for (int k = 0; k < npt; k++) { + double suma = ZERO; + double sumb = ZERO; + double sum = ZERO; + for (int j = 0; j < n; j++) { + suma += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j); + sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + sum += bMatrix.getEntry(k, j) * trialStepPoint.getEntry(j); + } + work3.setEntry(k, suma * (HALF * suma + sumb)); + lagrangeValuesAtNewPoint.setEntry(k, sum); + work2.setEntry(k, suma); + } + beta = ZERO; + for (int m = 0; m < nptm; m++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += zMatrix.getEntry(k, m) * work3.getEntry(k); + } + beta -= sum * sum; + for (int k = 0; k < npt; k++) { + lagrangeValuesAtNewPoint.setEntry(k, lagrangeValuesAtNewPoint.getEntry(k) + sum * zMatrix.getEntry(k, m)); + } + } + dsq = ZERO; + double bsum = ZERO; + double dx = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(j); + dsq += d1 * d1; + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += work3.getEntry(k) * bMatrix.getEntry(k, j); + } + bsum += sum * trialStepPoint.getEntry(j); + final int jp = npt + j; + for (int i = 0; i < n; i++) { + sum += bMatrix.getEntry(jp, i) * trialStepPoint.getEntry(i); + } + lagrangeValuesAtNewPoint.setEntry(jp, sum); + bsum += sum * trialStepPoint.getEntry(j); + dx += trialStepPoint.getEntry(j) * trustRegionCenterOffset.getEntry(j); + } + + beta = dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) + beta - bsum; // Original + // beta += dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) - bsum; // XXX "testAckley" and "testDiffPow" fail. + // beta = dx * dx + dsq * (xoptsq + 2 * dx + HALF * dsq) + beta - bsum; // XXX "testDiffPow" fails. + + lagrangeValuesAtNewPoint.setEntry(trustRegionCenterInterpolationPointIndex, + lagrangeValuesAtNewPoint.getEntry(trustRegionCenterInterpolationPointIndex) + ONE); + + // If NTRITS is zero, the denominator may be increased by replacing + // the step D of ALTMOV by a Cauchy step. Then RESCUE may be called if + // rounding errors have damaged the chosen denominator. + + if (ntrits == 0) { + // Computing 2nd power + final double d1 = lagrangeValuesAtNewPoint.getEntry(knew); + denom = d1 * d1 + alpha * beta; + if (denom < cauchy && cauchy > ZERO) { + for (int i = 0; i < n; i++) { + newPoint.setEntry(i, alternativeNewPoint.getEntry(i)); + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + } + cauchy = ZERO; // XXX Useful statement? + state = 230; break; + } + // Alternatively, if NTRITS is positive, then set KNEW to the index of + // the next interpolation point to be deleted to make room for a trust + // region step. Again RESCUE may be called if rounding errors have damaged_ + // the chosen denominator, which is the reason for attempting to select + // KNEW before calculating the next value of the objective function. + + } else { + final double delsq = delta * delta; + scaden = ZERO; + biglsq = ZERO; + knew = 0; + for (int k = 0; k < npt; k++) { + if (k == trustRegionCenterInterpolationPointIndex) { + continue; + } + double hdiag = ZERO; + for (int m = 0; m < nptm; m++) { + // Computing 2nd power + final double d1 = zMatrix.getEntry(k, m); + hdiag += d1 * d1; + } + // Computing 2nd power + final double d2 = lagrangeValuesAtNewPoint.getEntry(k); + final double den = beta * hdiag + d2 * d2; + distsq = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d3 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j); + distsq += d3 * d3; + } + // Computing MAX + // Computing 2nd power + final double d4 = distsq / delsq; + final double temp = FastMath.max(ONE, d4 * d4); + if (temp * den > scaden) { + scaden = temp * den; + knew = k; + denom = den; + } + // Computing MAX + // Computing 2nd power + final double d5 = lagrangeValuesAtNewPoint.getEntry(k); + biglsq = FastMath.max(biglsq, temp * (d5 * d5)); + } + } + + // Put the variables for the next calculation of the objective function + // in XNEW, with any adjustments for the bounds. + + // Calculate the value of the objective function at XBASE+XNEW, unless + // the limit on the number of calculations of F has been reached. + + } + case 360: { + printState(360); // XXX + for (int i = 0; i < n; i++) { + // Computing MIN + // Computing MAX + final double d3 = lowerBound[i]; + final double d4 = originShift.getEntry(i) + newPoint.getEntry(i); + final double d1 = FastMath.max(d3, d4); + final double d2 = upperBound[i]; + currentBest.setEntry(i, FastMath.min(d1, d2)); + if (newPoint.getEntry(i) == lowerDifference.getEntry(i)) { + currentBest.setEntry(i, lowerBound[i]); + } + if (newPoint.getEntry(i) == upperDifference.getEntry(i)) { + currentBest.setEntry(i, upperBound[i]); + } + } + + f = computeObjectiveValue(currentBest.toArray()); + + if (!isMinimize) { + f = -f; + } + if (ntrits == -1) { + fsave = f; + state = 720; break; + } + + // Use the quadratic model to predict the change in F due to the step D, + // and set DIFF to the error of this prediction. + + final double fopt = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex); + double vquad = ZERO; + int ih = 0; + for (int j = 0; j < n; j++) { + vquad += trialStepPoint.getEntry(j) * gradientAtTrustRegionCenter.getEntry(j); + for (int i = 0; i <= j; i++) { + double temp = trialStepPoint.getEntry(i) * trialStepPoint.getEntry(j); + if (i == j) { + temp *= HALF; + } + vquad += modelSecondDerivativesValues.getEntry(ih) * temp; + ih++; + } + } + for (int k = 0; k < npt; k++) { + // Computing 2nd power + final double d1 = work2.getEntry(k); + final double d2 = d1 * d1; // "d1" must be squared first to prevent test failures. + vquad += HALF * modelSecondDerivativesParameters.getEntry(k) * d2; + } + final double diff = f - fopt - vquad; + diffc = diffb; + diffb = diffa; + diffa = FastMath.abs(diff); + if (dnorm > rho) { + nfsav = getEvaluations(); + } + + // Pick the next value of DELTA after a trust region step. + + if (ntrits > 0) { + if (vquad >= ZERO) { + throw new MathIllegalStateException(LocalizedFormats.TRUST_REGION_STEP_FAILED, vquad); + } + ratio = (f - fopt) / vquad; + final double hDelta = HALF * delta; + if (ratio <= ONE_OVER_TEN) { + // Computing MIN + delta = FastMath.min(hDelta, dnorm); + } else if (ratio <= .7) { + // Computing MAX + delta = FastMath.max(hDelta, dnorm); + } else { + // Computing MAX + delta = FastMath.max(hDelta, 2 * dnorm); + } + if (delta <= rho * 1.5) { + delta = rho; + } + + // Recalculate KNEW and DENOM if the new F is less than FOPT. + + if (f < fopt) { + final int ksav = knew; + final double densav = denom; + final double delsq = delta * delta; + scaden = ZERO; + biglsq = ZERO; + knew = 0; + for (int k = 0; k < npt; k++) { + double hdiag = ZERO; + for (int m = 0; m < nptm; m++) { + // Computing 2nd power + final double d1 = zMatrix.getEntry(k, m); + hdiag += d1 * d1; + } + // Computing 2nd power + final double d1 = lagrangeValuesAtNewPoint.getEntry(k); + final double den = beta * hdiag + d1 * d1; + distsq = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d2 = interpolationPoints.getEntry(k, j) - newPoint.getEntry(j); + distsq += d2 * d2; + } + // Computing MAX + // Computing 2nd power + final double d3 = distsq / delsq; + final double temp = FastMath.max(ONE, d3 * d3); + if (temp * den > scaden) { + scaden = temp * den; + knew = k; + denom = den; + } + // Computing MAX + // Computing 2nd power + final double d4 = lagrangeValuesAtNewPoint.getEntry(k); + final double d5 = temp * (d4 * d4); + biglsq = FastMath.max(biglsq, d5); + } + if (scaden <= HALF * biglsq) { + knew = ksav; + denom = densav; + } + } + } + + // Update BMAT and ZMAT, so that the KNEW-th interpolation point can be + // moved. Also update the second derivative terms of the model. + + update(beta, denom, knew); + + ih = 0; + final double pqold = modelSecondDerivativesParameters.getEntry(knew); + modelSecondDerivativesParameters.setEntry(knew, ZERO); + for (int i = 0; i < n; i++) { + final double temp = pqold * interpolationPoints.getEntry(knew, i); + for (int j = 0; j <= i; j++) { + modelSecondDerivativesValues.setEntry(ih, modelSecondDerivativesValues.getEntry(ih) + temp * interpolationPoints.getEntry(knew, j)); + ih++; + } + } + for (int m = 0; m < nptm; m++) { + final double temp = diff * zMatrix.getEntry(knew, m); + for (int k = 0; k < npt; k++) { + modelSecondDerivativesParameters.setEntry(k, modelSecondDerivativesParameters.getEntry(k) + temp * zMatrix.getEntry(k, m)); + } + } + + // Include the new interpolation point, and make the changes to GOPT at + // the old XOPT that are caused by the updating of the quadratic model. + + fAtInterpolationPoints.setEntry(knew, f); + for (int i = 0; i < n; i++) { + interpolationPoints.setEntry(knew, i, newPoint.getEntry(i)); + work1.setEntry(i, bMatrix.getEntry(knew, i)); + } + for (int k = 0; k < npt; k++) { + double suma = ZERO; + for (int m = 0; m < nptm; m++) { + suma += zMatrix.getEntry(knew, m) * zMatrix.getEntry(k, m); + } + double sumb = ZERO; + for (int j = 0; j < n; j++) { + sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + final double temp = suma * sumb; + for (int i = 0; i < n; i++) { + work1.setEntry(i, work1.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + diff * work1.getEntry(i)); + } + + // Update XOPT, GOPT and KOPT if the new calculated F is less than FOPT. + + if (f < fopt) { + trustRegionCenterInterpolationPointIndex = knew; + xoptsq = ZERO; + ih = 0; + for (int j = 0; j < n; j++) { + trustRegionCenterOffset.setEntry(j, newPoint.getEntry(j)); + // Computing 2nd power + final double d1 = trustRegionCenterOffset.getEntry(j); + xoptsq += d1 * d1; + for (int i = 0; i <= j; i++) { + if (i < j) { + gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(i)); + } + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(j)); + ih++; + } + } + for (int k = 0; k < npt; k++) { + double temp = ZERO; + for (int j = 0; j < n; j++) { + temp += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j); + } + temp *= modelSecondDerivativesParameters.getEntry(k); + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + } + + // Calculate the parameters of the least Frobenius norm interpolant to + // the current data, the gradient of this interpolant at XOPT being put + // into VLAG(NPT+I), I=1,2,...,N. + + if (ntrits > 0) { + for (int k = 0; k < npt; k++) { + lagrangeValuesAtNewPoint.setEntry(k, fAtInterpolationPoints.getEntry(k) - fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)); + work3.setEntry(k, ZERO); + } + for (int j = 0; j < nptm; j++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += zMatrix.getEntry(k, j) * lagrangeValuesAtNewPoint.getEntry(k); + } + for (int k = 0; k < npt; k++) { + work3.setEntry(k, work3.getEntry(k) + sum * zMatrix.getEntry(k, j)); + } + } + for (int k = 0; k < npt; k++) { + double sum = ZERO; + for (int j = 0; j < n; j++) { + sum += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + work2.setEntry(k, work3.getEntry(k)); + work3.setEntry(k, sum * work3.getEntry(k)); + } + double gqsq = ZERO; + double gisq = ZERO; + for (int i = 0; i < n; i++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += bMatrix.getEntry(k, i) * + lagrangeValuesAtNewPoint.getEntry(k) + interpolationPoints.getEntry(k, i) * work3.getEntry(k); + } + if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) { + // Computing MIN + // Computing 2nd power + final double d1 = FastMath.min(ZERO, gradientAtTrustRegionCenter.getEntry(i)); + gqsq += d1 * d1; + // Computing 2nd power + final double d2 = FastMath.min(ZERO, sum); + gisq += d2 * d2; + } else if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) { + // Computing MAX + // Computing 2nd power + final double d1 = FastMath.max(ZERO, gradientAtTrustRegionCenter.getEntry(i)); + gqsq += d1 * d1; + // Computing 2nd power + final double d2 = FastMath.max(ZERO, sum); + gisq += d2 * d2; + } else { + // Computing 2nd power + final double d1 = gradientAtTrustRegionCenter.getEntry(i); + gqsq += d1 * d1; + gisq += sum * sum; + } + lagrangeValuesAtNewPoint.setEntry(npt + i, sum); + } + + // Test whether to replace the new quadratic model by the least Frobenius + // norm interpolant, making the replacement if the test is satisfied. + + ++itest; + if (gqsq < TEN * gisq) { + itest = 0; + } + if (itest >= 3) { + for (int i = 0, max = FastMath.max(npt, nh); i < max; i++) { + if (i < n) { + gradientAtTrustRegionCenter.setEntry(i, lagrangeValuesAtNewPoint.getEntry(npt + i)); + } + if (i < npt) { + modelSecondDerivativesParameters.setEntry(i, work2.getEntry(i)); + } + if (i < nh) { + modelSecondDerivativesValues.setEntry(i, ZERO); + } + itest = 0; + } + } + } + + // If a trust region step has provided a sufficient decrease in F, then + // branch for another trust region calculation. The case NTRITS=0 occurs + // when the new interpolation point was reached by an alternative step. + + if (ntrits == 0) { + state = 60; break; + } + if (f <= fopt + ONE_OVER_TEN * vquad) { + state = 60; break; + } + + // Alternatively, find out if the interpolation points are close enough + // to the best point so far. + + // Computing MAX + // Computing 2nd power + final double d1 = TWO * delta; + // Computing 2nd power + final double d2 = TEN * rho; + distsq = FastMath.max(d1 * d1, d2 * d2); + } + case 650: { + printState(650); // XXX + knew = -1; + for (int k = 0; k < npt; k++) { + double sum = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d1 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j); + sum += d1 * d1; + } + if (sum > distsq) { + knew = k; + distsq = sum; + } + } + + // If KNEW is positive, then ALTMOV finds alternative new positions for + // the KNEW-th interpolation point within distance ADELT of XOPT. It is + // reached via label 90. Otherwise, there is a branch to label 60 for + // another trust region iteration, unless the calculations with the + // current RHO are complete. + + if (knew >= 0) { + final double dist = FastMath.sqrt(distsq); + if (ntrits == -1) { + // Computing MIN + delta = FastMath.min(ONE_OVER_TEN * delta, HALF * dist); + if (delta <= rho * 1.5) { + delta = rho; + } + } + ntrits = 0; + // Computing MAX + // Computing MIN + final double d1 = FastMath.min(ONE_OVER_TEN * dist, delta); + adelt = FastMath.max(d1, rho); + dsq = adelt * adelt; + state = 90; break; + } + if (ntrits == -1) { + state = 680; break; + } + if (ratio > ZERO) { + state = 60; break; + } + if (FastMath.max(delta, dnorm) > rho) { + state = 60; break; + } + + // The calculations with the current value of RHO are complete. Pick the + // next values of RHO and DELTA. + } + case 680: { + printState(680); // XXX + if (rho > stoppingTrustRegionRadius) { + delta = HALF * rho; + ratio = rho / stoppingTrustRegionRadius; + if (ratio <= SIXTEEN) { + rho = stoppingTrustRegionRadius; + } else if (ratio <= TWO_HUNDRED_FIFTY) { + rho = FastMath.sqrt(ratio) * stoppingTrustRegionRadius; + } else { + rho *= ONE_OVER_TEN; + } + delta = FastMath.max(delta, rho); + ntrits = 0; + nfsav = getEvaluations(); + state = 60; break; + } + + // Return from the calculation, after another Newton-Raphson step, if + // it is too short to have been tried before. + + if (ntrits == -1) { + state = 360; break; + } + } + case 720: { + printState(720); // XXX + if (fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex) <= fsave) { + for (int i = 0; i < n; i++) { + // Computing MIN + // Computing MAX + final double d3 = lowerBound[i]; + final double d4 = originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i); + final double d1 = FastMath.max(d3, d4); + final double d2 = upperBound[i]; + currentBest.setEntry(i, FastMath.min(d1, d2)); + if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) { + currentBest.setEntry(i, lowerBound[i]); + } + if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) { + currentBest.setEntry(i, upperBound[i]); + } + } + f = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex); + } + return f; + } + default: { + throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "bobyqb"); + }}} + } // bobyqb + + // ---------------------------------------------------------------------------------------- + + /** + * The arguments N, NPT, XPT, XOPT, BMAT, ZMAT, NDIM, SL and SU all have + * the same meanings as the corresponding arguments of BOBYQB. + * KOPT is the index of the optimal interpolation point. + * KNEW is the index of the interpolation point that is going to be moved. + * ADELT is the current trust region bound. + * XNEW will be set to a suitable new position for the interpolation point + * XPT(KNEW,.). Specifically, it satisfies the SL, SU and trust region + * bounds and it should provide a large denominator in the next call of + * UPDATE. The step XNEW-XOPT from XOPT is restricted to moves along the + * straight lines through XOPT and another interpolation point. + * XALT also provides a large value of the modulus of the KNEW-th Lagrange + * function subject to the constraints that have been mentioned, its main + * difference from XNEW being that XALT-XOPT is a constrained version of + * the Cauchy step within the trust region. An exception is that XALT is + * not calculated if all components of GLAG (see below) are zero. + * ALPHA will be set to the KNEW-th diagonal element of the H matrix. + * CAUCHY will be set to the square of the KNEW-th Lagrange function at + * the step XALT-XOPT from XOPT for the vector XALT that is returned, + * except that CAUCHY is set to zero if XALT is not calculated. + * GLAG is a working space vector of length N for the gradient of the + * KNEW-th Lagrange function at XOPT. + * HCOL is a working space vector of length NPT for the second derivative + * coefficients of the KNEW-th Lagrange function. + * W is a working space vector of length 2N that is going to hold the + * constrained Cauchy step from XOPT of the Lagrange function, followed + * by the downhill version of XALT when the uphill step is calculated. + * + * Set the first NPT components of W to the leading elements of the + * KNEW-th column of the H matrix. + * @param knew + * @param adelt + */ + private double[] altmov( + int knew, + double adelt + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + + final ArrayRealVector glag = new ArrayRealVector(n); + final ArrayRealVector hcol = new ArrayRealVector(npt); + + final ArrayRealVector work1 = new ArrayRealVector(n); + final ArrayRealVector work2 = new ArrayRealVector(n); + + for (int k = 0; k < npt; k++) { + hcol.setEntry(k, ZERO); + } + for (int j = 0, max = npt - n - 1; j < max; j++) { + final double tmp = zMatrix.getEntry(knew, j); + for (int k = 0; k < npt; k++) { + hcol.setEntry(k, hcol.getEntry(k) + tmp * zMatrix.getEntry(k, j)); + } + } + final double alpha = hcol.getEntry(knew); + final double ha = HALF * alpha; + + // Calculate the gradient of the KNEW-th Lagrange function at XOPT. + + for (int i = 0; i < n; i++) { + glag.setEntry(i, bMatrix.getEntry(knew, i)); + } + for (int k = 0; k < npt; k++) { + double tmp = ZERO; + for (int j = 0; j < n; j++) { + tmp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + tmp *= hcol.getEntry(k); + for (int i = 0; i < n; i++) { + glag.setEntry(i, glag.getEntry(i) + tmp * interpolationPoints.getEntry(k, i)); + } + } + + // Search for a large denominator along the straight lines through XOPT + // and another interpolation point. SLBD and SUBD will be lower and upper + // bounds on the step along each of these lines in turn. PREDSQ will be + // set to the square of the predicted denominator for each line. PRESAV + // will be set to the largest admissible value of PREDSQ that occurs. + + double presav = ZERO; + double step = Double.NaN; + int ksav = 0; + int ibdsav = 0; + double stpsav = 0; + for (int k = 0; k < npt; k++) { + if (k == trustRegionCenterInterpolationPointIndex) { + continue; + } + double dderiv = ZERO; + double distsq = ZERO; + for (int i = 0; i < n; i++) { + final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i); + dderiv += glag.getEntry(i) * tmp; + distsq += tmp * tmp; + } + double subd = adelt / FastMath.sqrt(distsq); + double slbd = -subd; + int ilbd = 0; + int iubd = 0; + final double sumin = FastMath.min(ONE, subd); + + // Revise SLBD and SUBD if necessary because of the bounds in SL and SU. + + for (int i = 0; i < n; i++) { + final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i); + if (tmp > ZERO) { + if (slbd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + slbd = (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp; + ilbd = -i - 1; + } + if (subd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + // Computing MAX + subd = FastMath.max(sumin, + (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp); + iubd = i + 1; + } + } else if (tmp < ZERO) { + if (slbd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + slbd = (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp; + ilbd = i + 1; + } + if (subd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + // Computing MAX + subd = FastMath.max(sumin, + (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp); + iubd = -i - 1; + } + } + } + + // Seek a large modulus of the KNEW-th Lagrange function when the index + // of the other interpolation point on the line through XOPT is KNEW. + + step = slbd; + int isbd = ilbd; + double vlag = Double.NaN; + if (k == knew) { + final double diff = dderiv - ONE; + vlag = slbd * (dderiv - slbd * diff); + final double d1 = subd * (dderiv - subd * diff); + if (FastMath.abs(d1) > FastMath.abs(vlag)) { + step = subd; + vlag = d1; + isbd = iubd; + } + final double d2 = HALF * dderiv; + final double d3 = d2 - diff * slbd; + final double d4 = d2 - diff * subd; + if (d3 * d4 < ZERO) { + final double d5 = d2 * d2 / diff; + if (FastMath.abs(d5) > FastMath.abs(vlag)) { + step = d2 / diff; + vlag = d5; + isbd = 0; + } + } + + // Search along each of the other lines through XOPT and another point. + + } else { + vlag = slbd * (ONE - slbd); + final double tmp = subd * (ONE - subd); + if (FastMath.abs(tmp) > FastMath.abs(vlag)) { + step = subd; + vlag = tmp; + isbd = iubd; + } + if (subd > HALF && FastMath.abs(vlag) < ONE_OVER_FOUR) { + step = HALF; + vlag = ONE_OVER_FOUR; + isbd = 0; + } + vlag *= dderiv; + } + + // Calculate PREDSQ for the current line search and maintain PRESAV. + + final double tmp = step * (ONE - step) * distsq; + final double predsq = vlag * vlag * (vlag * vlag + ha * tmp * tmp); + if (predsq > presav) { + presav = predsq; + ksav = k; + stpsav = step; + ibdsav = isbd; + } + } + + // Construct XNEW in a way that satisfies the bound constraints exactly. + + for (int i = 0; i < n; i++) { + final double tmp = trustRegionCenterOffset.getEntry(i) + stpsav * (interpolationPoints.getEntry(ksav, i) - trustRegionCenterOffset.getEntry(i)); + newPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), + FastMath.min(upperDifference.getEntry(i), tmp))); + } + if (ibdsav < 0) { + newPoint.setEntry(-ibdsav - 1, lowerDifference.getEntry(-ibdsav - 1)); + } + if (ibdsav > 0) { + newPoint.setEntry(ibdsav - 1, upperDifference.getEntry(ibdsav - 1)); + } + + // Prepare for the iterative method that assembles the constrained Cauchy + // step in W. The sum of squares of the fixed components of W is formed in + // WFIXSQ, and the free components of W are set to BIGSTP. + + final double bigstp = adelt + adelt; + int iflag = 0; + double cauchy = Double.NaN; + double csave = ZERO; + while (true) { + double wfixsq = ZERO; + double ggfree = ZERO; + for (int i = 0; i < n; i++) { + final double glagValue = glag.getEntry(i); + work1.setEntry(i, ZERO); + if (FastMath.min(trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i), glagValue) > ZERO || + FastMath.max(trustRegionCenterOffset.getEntry(i) - upperDifference.getEntry(i), glagValue) < ZERO) { + work1.setEntry(i, bigstp); + // Computing 2nd power + ggfree += glagValue * glagValue; + } + } + if (ggfree == ZERO) { + return new double[] { alpha, ZERO }; + } + + // Investigate whether more components of W can be fixed. + final double tmp1 = adelt * adelt - wfixsq; + if (tmp1 > ZERO) { + step = FastMath.sqrt(tmp1 / ggfree); + ggfree = ZERO; + for (int i = 0; i < n; i++) { + if (work1.getEntry(i) == bigstp) { + final double tmp2 = trustRegionCenterOffset.getEntry(i) - step * glag.getEntry(i); + if (tmp2 <= lowerDifference.getEntry(i)) { + work1.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = work1.getEntry(i); + wfixsq += d1 * d1; + } else if (tmp2 >= upperDifference.getEntry(i)) { + work1.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = work1.getEntry(i); + wfixsq += d1 * d1; + } else { + // Computing 2nd power + final double d1 = glag.getEntry(i); + ggfree += d1 * d1; + } + } + } + } + + // Set the remaining free components of W and all components of XALT, + // except that W may be scaled later. + + double gw = ZERO; + for (int i = 0; i < n; i++) { + final double glagValue = glag.getEntry(i); + if (work1.getEntry(i) == bigstp) { + work1.setEntry(i, -step * glagValue); + final double min = FastMath.min(upperDifference.getEntry(i), + trustRegionCenterOffset.getEntry(i) + work1.getEntry(i)); + alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), min)); + } else if (work1.getEntry(i) == ZERO) { + alternativeNewPoint.setEntry(i, trustRegionCenterOffset.getEntry(i)); + } else if (glagValue > ZERO) { + alternativeNewPoint.setEntry(i, lowerDifference.getEntry(i)); + } else { + alternativeNewPoint.setEntry(i, upperDifference.getEntry(i)); + } + gw += glagValue * work1.getEntry(i); + } + + // Set CURV to the curvature of the KNEW-th Lagrange function along W. + // Scale W by a factor less than one if that can reduce the modulus of + // the Lagrange function at XOPT+W. Set CAUCHY to the final value of + // the square of this function. + + double curv = ZERO; + for (int k = 0; k < npt; k++) { + double tmp = ZERO; + for (int j = 0; j < n; j++) { + tmp += interpolationPoints.getEntry(k, j) * work1.getEntry(j); + } + curv += hcol.getEntry(k) * tmp * tmp; + } + if (iflag == 1) { + curv = -curv; + } + if (curv > -gw && + curv < -gw * (ONE + FastMath.sqrt(TWO))) { + final double scale = -gw / curv; + for (int i = 0; i < n; i++) { + final double tmp = trustRegionCenterOffset.getEntry(i) + scale * work1.getEntry(i); + alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), + FastMath.min(upperDifference.getEntry(i), tmp))); + } + // Computing 2nd power + final double d1 = HALF * gw * scale; + cauchy = d1 * d1; + } else { + // Computing 2nd power + final double d1 = gw + HALF * curv; + cauchy = d1 * d1; + } + + // If IFLAG is zero, then XALT is calculated as before after reversing + // the sign of GLAG. Thus two XALT vectors become available. The one that + // is chosen is the one that gives the larger value of CAUCHY. + + if (iflag == 0) { + for (int i = 0; i < n; i++) { + glag.setEntry(i, -glag.getEntry(i)); + work2.setEntry(i, alternativeNewPoint.getEntry(i)); + } + csave = cauchy; + iflag = 1; + } else { + break; + } + } + if (csave > cauchy) { + for (int i = 0; i < n; i++) { + alternativeNewPoint.setEntry(i, work2.getEntry(i)); + } + cauchy = csave; + } + + return new double[] { alpha, cauchy }; + } // altmov + + // ---------------------------------------------------------------------------------------- + + /** + * SUBROUTINE PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ, + * BMAT and ZMAT for the first iteration, and it maintains the values of + * NF and KOPT. The vector X is also changed by PRELIM. + * + * The arguments N, NPT, X, XL, XU, RHOBEG, IPRINT and MAXFUN are the + * same as the corresponding arguments in SUBROUTINE BOBYQA. + * The arguments XBASE, XPT, FVAL, HQ, PQ, BMAT, ZMAT, NDIM, SL and SU + * are the same as the corresponding arguments in BOBYQB, the elements + * of SL and SU being set in BOBYQA. + * GOPT is usually the gradient of the quadratic model at XOPT+XBASE, but + * it is set by PRELIM to the gradient of the quadratic model at XBASE. + * If XOPT is nonzero, BOBYQB will change it to its usual value later. + * NF is maintaned as the number of calls of CALFUN so far. + * KOPT will be such that the least calculated value of F so far is at + * the point XPT(KOPT,.)+XBASE in the space of the variables. + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + */ + private void prelim(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int ndim = bMatrix.getRowDimension(); + + final double rhosq = initialTrustRegionRadius * initialTrustRegionRadius; + final double recip = 1d / rhosq; + final int np = n + 1; + + // Set XBASE to the initial vector of variables, and set the initial + // elements of XPT, BMAT, HQ, PQ and ZMAT to zero. + + for (int j = 0; j < n; j++) { + originShift.setEntry(j, currentBest.getEntry(j)); + for (int k = 0; k < npt; k++) { + interpolationPoints.setEntry(k, j, ZERO); + } + for (int i = 0; i < ndim; i++) { + bMatrix.setEntry(i, j, ZERO); + } + } + for (int i = 0, max = n * np / 2; i < max; i++) { + modelSecondDerivativesValues.setEntry(i, ZERO); + } + for (int k = 0; k < npt; k++) { + modelSecondDerivativesParameters.setEntry(k, ZERO); + for (int j = 0, max = npt - np; j < max; j++) { + zMatrix.setEntry(k, j, ZERO); + } + } + + // Begin the initialization procedure. NF becomes one more than the number + // of function values so far. The coordinates of the displacement of the + // next initial interpolation point from XBASE are set in XPT(NF+1,.). + + int ipt = 0; + int jpt = 0; + double fbeg = Double.NaN; + do { + final int nfm = getEvaluations(); + final int nfx = nfm - n; + final int nfmm = nfm - 1; + final int nfxm = nfx - 1; + double stepa = 0; + double stepb = 0; + if (nfm <= 2 * n) { + if (nfm >= 1 && + nfm <= n) { + stepa = initialTrustRegionRadius; + if (upperDifference.getEntry(nfmm) == ZERO) { + stepa = -stepa; + // throw new PathIsExploredException(); // XXX + } + interpolationPoints.setEntry(nfm, nfmm, stepa); + } else if (nfm > n) { + stepa = interpolationPoints.getEntry(nfx, nfxm); + stepb = -initialTrustRegionRadius; + if (lowerDifference.getEntry(nfxm) == ZERO) { + stepb = FastMath.min(TWO * initialTrustRegionRadius, upperDifference.getEntry(nfxm)); + // throw new PathIsExploredException(); // XXX + } + if (upperDifference.getEntry(nfxm) == ZERO) { + stepb = FastMath.max(-TWO * initialTrustRegionRadius, lowerDifference.getEntry(nfxm)); + // throw new PathIsExploredException(); // XXX + } + interpolationPoints.setEntry(nfm, nfxm, stepb); + } + } else { + final int tmp1 = (nfm - np) / n; + jpt = nfm - tmp1 * n - n; + ipt = jpt + tmp1; + if (ipt > n) { + final int tmp2 = jpt; + jpt = ipt - n; + ipt = tmp2; +// throw new PathIsExploredException(); // XXX + } + final int iptMinus1 = ipt - 1; + final int jptMinus1 = jpt - 1; + interpolationPoints.setEntry(nfm, iptMinus1, interpolationPoints.getEntry(ipt, iptMinus1)); + interpolationPoints.setEntry(nfm, jptMinus1, interpolationPoints.getEntry(jpt, jptMinus1)); + } + + // Calculate the next value of F. The least function value so far and + // its index are required. + + for (int j = 0; j < n; j++) { + currentBest.setEntry(j, FastMath.min(FastMath.max(lowerBound[j], + originShift.getEntry(j) + interpolationPoints.getEntry(nfm, j)), + upperBound[j])); + if (interpolationPoints.getEntry(nfm, j) == lowerDifference.getEntry(j)) { + currentBest.setEntry(j, lowerBound[j]); + } + if (interpolationPoints.getEntry(nfm, j) == upperDifference.getEntry(j)) { + currentBest.setEntry(j, upperBound[j]); + } + } + + final double objectiveValue = computeObjectiveValue(currentBest.toArray()); + final double f = isMinimize ? objectiveValue : -objectiveValue; + final int numEval = getEvaluations(); // nfm + 1 + fAtInterpolationPoints.setEntry(nfm, f); + + if (numEval == 1) { + fbeg = f; + trustRegionCenterInterpolationPointIndex = 0; + } else if (f < fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)) { + trustRegionCenterInterpolationPointIndex = nfm; + } + + // Set the nonzero initial elements of BMAT and the quadratic model in the + // cases when NF is at most 2*N+1. If NF exceeds N+1, then the positions + // of the NF-th and (NF-N)-th interpolation points may be switched, in + // order that the function value at the first of them contributes to the + // off-diagonal second derivative terms of the initial quadratic model. + + if (numEval <= 2 * n + 1) { + if (numEval >= 2 && + numEval <= n + 1) { + gradientAtTrustRegionCenter.setEntry(nfmm, (f - fbeg) / stepa); + if (npt < numEval + n) { + final double oneOverStepA = ONE / stepa; + bMatrix.setEntry(0, nfmm, -oneOverStepA); + bMatrix.setEntry(nfm, nfmm, oneOverStepA); + bMatrix.setEntry(npt + nfmm, nfmm, -HALF * rhosq); + // throw new PathIsExploredException(); // XXX + } + } else if (numEval >= n + 2) { + final int ih = nfx * (nfx + 1) / 2 - 1; + final double tmp = (f - fbeg) / stepb; + final double diff = stepb - stepa; + modelSecondDerivativesValues.setEntry(ih, TWO * (tmp - gradientAtTrustRegionCenter.getEntry(nfxm)) / diff); + gradientAtTrustRegionCenter.setEntry(nfxm, (gradientAtTrustRegionCenter.getEntry(nfxm) * stepb - tmp * stepa) / diff); + if (stepa * stepb < ZERO && f < fAtInterpolationPoints.getEntry(nfm - n)) { + fAtInterpolationPoints.setEntry(nfm, fAtInterpolationPoints.getEntry(nfm - n)); + fAtInterpolationPoints.setEntry(nfm - n, f); + if (trustRegionCenterInterpolationPointIndex == nfm) { + trustRegionCenterInterpolationPointIndex = nfm - n; + } + interpolationPoints.setEntry(nfm - n, nfxm, stepb); + interpolationPoints.setEntry(nfm, nfxm, stepa); + } + bMatrix.setEntry(0, nfxm, -(stepa + stepb) / (stepa * stepb)); + bMatrix.setEntry(nfm, nfxm, -HALF / interpolationPoints.getEntry(nfm - n, nfxm)); + bMatrix.setEntry(nfm - n, nfxm, + -bMatrix.getEntry(0, nfxm) - bMatrix.getEntry(nfm, nfxm)); + zMatrix.setEntry(0, nfxm, FastMath.sqrt(TWO) / (stepa * stepb)); + zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) / rhosq); + // zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) * recip); // XXX "testAckley" and "testDiffPow" fail. + zMatrix.setEntry(nfm - n, nfxm, + -zMatrix.getEntry(0, nfxm) - zMatrix.getEntry(nfm, nfxm)); + } + + // Set the off-diagonal second derivatives of the Lagrange functions and + // the initial quadratic model. + + } else { + zMatrix.setEntry(0, nfxm, recip); + zMatrix.setEntry(nfm, nfxm, recip); + zMatrix.setEntry(ipt, nfxm, -recip); + zMatrix.setEntry(jpt, nfxm, -recip); + + final int ih = ipt * (ipt - 1) / 2 + jpt - 1; + final double tmp = interpolationPoints.getEntry(nfm, ipt - 1) * interpolationPoints.getEntry(nfm, jpt - 1); + modelSecondDerivativesValues.setEntry(ih, (fbeg - fAtInterpolationPoints.getEntry(ipt) - fAtInterpolationPoints.getEntry(jpt) + f) / tmp); +// throw new PathIsExploredException(); // XXX + } + } while (getEvaluations() < npt); + } // prelim + + + // ---------------------------------------------------------------------------------------- + + /** + * A version of the truncated conjugate gradient is applied. If a line + * search is restricted by a constraint, then the procedure is restarted, + * the values of the variables that are at their bounds being fixed. If + * the trust region boundary is reached, then further changes may be made + * to D, each one being in the two dimensional space that is spanned + * by the current D and the gradient of Q at XOPT+D, staying on the trust + * region boundary. Termination occurs when the reduction in Q seems to + * be close to the greatest reduction that can be achieved. + * The arguments N, NPT, XPT, XOPT, GOPT, HQ, PQ, SL and SU have the same + * meanings as the corresponding arguments of BOBYQB. + * DELTA is the trust region radius for the present calculation, which + * seeks a small value of the quadratic model within distance DELTA of + * XOPT subject to the bounds on the variables. + * XNEW will be set to a new vector of variables that is approximately + * the one that minimizes the quadratic model within the trust region + * subject to the SL and SU constraints on the variables. It satisfies + * as equations the bounds that become active during the calculation. + * D is the calculated trial step from XOPT, generated iteratively from an + * initial value of zero. Thus XNEW is XOPT+D after the final iteration. + * GNEW holds the gradient of the quadratic model at XOPT+D. It is updated + * when D is updated. + * xbdi.get( is a working space vector. For I=1,2,...,N, the element xbdi.get((I) is + * set to -1.0, 0.0, or 1.0, the value being nonzero if and only if the + * I-th variable has become fixed at a bound, the bound being SL(I) or + * SU(I) in the case xbdi.get((I)=-1.0 or xbdi.get((I)=1.0, respectively. This + * information is accumulated during the construction of XNEW. + * The arrays S, HS and HRED are also used for working space. They hold the + * current search direction, and the changes in the gradient of Q along S + * and the reduced D, respectively, where the reduced D is the same as D, + * except that the components of the fixed variables are zero. + * DSQ will be set to the square of the length of XNEW-XOPT. + * CRVMIN is set to zero if D reaches the trust region boundary. Otherwise + * it is set to the least curvature of H that occurs in the conjugate + * gradient searches that are not restricted by any constraints. The + * value CRVMIN=-1.0D0 is set, however, if all of these searches are + * constrained. + * @param delta + * @param gnew + * @param xbdi + * @param s + * @param hs + * @param hred + */ + private double[] trsbox( + double delta, + ArrayRealVector gnew, + ArrayRealVector xbdi, + ArrayRealVector s, + ArrayRealVector hs, + ArrayRealVector hred + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + + double dsq = Double.NaN; + double crvmin = Double.NaN; + + // Local variables + double ds; + int iu; + double dhd, dhs, cth, shs, sth, ssq, beta=0, sdec, blen; + int iact = -1; + int nact = 0; + double angt = 0, qred; + int isav; + double temp = 0, xsav = 0, xsum = 0, angbd = 0, dredg = 0, sredg = 0; + int iterc; + double resid = 0, delsq = 0, ggsav = 0, tempa = 0, tempb = 0, + redmax = 0, dredsq = 0, redsav = 0, gredsq = 0, rednew = 0; + int itcsav = 0; + double rdprev = 0, rdnext = 0, stplen = 0, stepsq = 0; + int itermax = 0; + + // Set some constants. + + // Function Body + + // The sign of GOPT(I) gives the sign of the change to the I-th variable + // that will reduce Q from its value at XOPT. Thus xbdi.get((I) shows whether + // or not to fix the I-th variable at one of its bounds initially, with + // NACT being set to the number of fixed variables. D and GNEW are also + // set for the first iteration. DELSQ is the upper bound on the sum of + // squares of the free variables. QRED is the reduction in Q so far. + + iterc = 0; + nact = 0; + for (int i = 0; i < n; i++) { + xbdi.setEntry(i, ZERO); + if (trustRegionCenterOffset.getEntry(i) <= lowerDifference.getEntry(i)) { + if (gradientAtTrustRegionCenter.getEntry(i) >= ZERO) { + xbdi.setEntry(i, MINUS_ONE); + } + } else if (trustRegionCenterOffset.getEntry(i) >= upperDifference.getEntry(i) && + gradientAtTrustRegionCenter.getEntry(i) <= ZERO) { + xbdi.setEntry(i, ONE); + } + if (xbdi.getEntry(i) != ZERO) { + ++nact; + } + trialStepPoint.setEntry(i, ZERO); + gnew.setEntry(i, gradientAtTrustRegionCenter.getEntry(i)); + } + delsq = delta * delta; + qred = ZERO; + crvmin = MINUS_ONE; + + // Set the next search direction of the conjugate gradient method. It is + // the steepest descent direction initially and when the iterations are + // restarted because a variable has just been fixed by a bound, and of + // course the components of the fixed variables are zero. ITERMAX is an + // upper bound on the indices of the conjugate gradient iterations. + + int state = 20; + for(;;) { + switch (state) { + case 20: { + printState(20); // XXX + beta = ZERO; + } + case 30: { + printState(30); // XXX + stepsq = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) != ZERO) { + s.setEntry(i, ZERO); + } else if (beta == ZERO) { + s.setEntry(i, -gnew.getEntry(i)); + } else { + s.setEntry(i, beta * s.getEntry(i) - gnew.getEntry(i)); + } + // Computing 2nd power + final double d1 = s.getEntry(i); + stepsq += d1 * d1; + } + if (stepsq == ZERO) { + state = 190; break; + } + if (beta == ZERO) { + gredsq = stepsq; + itermax = iterc + n - nact; + } + if (gredsq * delsq <= qred * 1e-4 * qred) { + state = 190; break; + } + + // Multiply the search direction by the second derivative matrix of Q and + // calculate some scalars for the choice of steplength. Then set BLEN to + // the length of the the step to the trust region boundary and STPLEN to + // the steplength, ignoring the simple bounds. + + state = 210; break; + } + case 50: { + printState(50); // XXX + resid = delsq; + ds = ZERO; + shs = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(i); + resid -= d1 * d1; + ds += s.getEntry(i) * trialStepPoint.getEntry(i); + shs += s.getEntry(i) * hs.getEntry(i); + } + } + if (resid <= ZERO) { + state = 90; break; + } + temp = FastMath.sqrt(stepsq * resid + ds * ds); + if (ds < ZERO) { + blen = (temp - ds) / stepsq; + } else { + blen = resid / (temp + ds); + } + stplen = blen; + if (shs > ZERO) { + // Computing MIN + stplen = FastMath.min(blen, gredsq / shs); + } + + // Reduce STPLEN if necessary in order to preserve the simple bounds, + // letting IACT be the index of the new constrained variable. + + iact = -1; + for (int i = 0; i < n; i++) { + if (s.getEntry(i) != ZERO) { + xsum = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i); + if (s.getEntry(i) > ZERO) { + temp = (upperDifference.getEntry(i) - xsum) / s.getEntry(i); + } else { + temp = (lowerDifference.getEntry(i) - xsum) / s.getEntry(i); + } + if (temp < stplen) { + stplen = temp; + iact = i; + } + } + } + + // Update CRVMIN, GNEW and D. Set SDEC to the decrease that occurs in Q. + + sdec = ZERO; + if (stplen > ZERO) { + ++iterc; + temp = shs / stepsq; + if (iact == -1 && temp > ZERO) { + crvmin = FastMath.min(crvmin,temp); + if (crvmin == MINUS_ONE) { + crvmin = temp; + } + } + ggsav = gredsq; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + gnew.setEntry(i, gnew.getEntry(i) + stplen * hs.getEntry(i)); + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + final double d1 = gnew.getEntry(i); + gredsq += d1 * d1; + } + trialStepPoint.setEntry(i, trialStepPoint.getEntry(i) + stplen * s.getEntry(i)); + } + // Computing MAX + final double d1 = stplen * (ggsav - HALF * stplen * shs); + sdec = FastMath.max(d1, ZERO); + qred += sdec; + } + + // Restart the conjugate gradient method if it has hit a new bound. + + if (iact >= 0) { + ++nact; + xbdi.setEntry(iact, ONE); + if (s.getEntry(iact) < ZERO) { + xbdi.setEntry(iact, MINUS_ONE); + } + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(iact); + delsq -= d1 * d1; + if (delsq <= ZERO) { + state = 190; break; + } + state = 20; break; + } + + // If STPLEN is less than BLEN, then either apply another conjugate + // gradient iteration or RETURN. + + if (stplen < blen) { + if (iterc == itermax) { + state = 190; break; + } + if (sdec <= qred * .01) { + state = 190; break; + } + beta = gredsq / ggsav; + state = 30; break; + } + } + case 90: { + printState(90); // XXX + crvmin = ZERO; + + // Prepare for the alternative iteration by calculating some scalars + // and by multiplying the reduced D by the second derivative matrix of + // Q, where S holds the reduced D in the call of GGMULT. + + } + case 100: { + printState(100); // XXX + if (nact >= n - 1) { + state = 190; break; + } + dredsq = ZERO; + dredg = ZERO; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + double d1 = trialStepPoint.getEntry(i); + dredsq += d1 * d1; + dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i); + // Computing 2nd power + d1 = gnew.getEntry(i); + gredsq += d1 * d1; + s.setEntry(i, trialStepPoint.getEntry(i)); + } else { + s.setEntry(i, ZERO); + } + } + itcsav = iterc; + state = 210; break; + // Let the search direction S be a linear combination of the reduced D + // and the reduced G that is orthogonal to the reduced D. + } + case 120: { + printState(120); // XXX + ++iterc; + temp = gredsq * dredsq - dredg * dredg; + if (temp <= qred * 1e-4 * qred) { + state = 190; break; + } + temp = FastMath.sqrt(temp); + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + s.setEntry(i, (dredg * trialStepPoint.getEntry(i) - dredsq * gnew.getEntry(i)) / temp); + } else { + s.setEntry(i, ZERO); + } + } + sredg = -temp; + + // By considering the simple bounds on the variables, calculate an upper + // bound on the tangent of half the angle of the alternative iteration, + // namely ANGBD, except that, if already a free variable has reached a + // bound, there is a branch back to label 100 after fixing that variable. + + angbd = ONE; + iact = -1; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + tempa = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i) - lowerDifference.getEntry(i); + tempb = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i) - trialStepPoint.getEntry(i); + if (tempa <= ZERO) { + ++nact; + xbdi.setEntry(i, MINUS_ONE); + state = 100; break; + } else if (tempb <= ZERO) { + ++nact; + xbdi.setEntry(i, ONE); + state = 100; break; + } + // Computing 2nd power + double d1 = trialStepPoint.getEntry(i); + // Computing 2nd power + double d2 = s.getEntry(i); + ssq = d1 * d1 + d2 * d2; + // Computing 2nd power + d1 = trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i); + temp = ssq - d1 * d1; + if (temp > ZERO) { + temp = FastMath.sqrt(temp) - s.getEntry(i); + if (angbd * temp > tempa) { + angbd = tempa / temp; + iact = i; + xsav = MINUS_ONE; + } + } + // Computing 2nd power + d1 = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i); + temp = ssq - d1 * d1; + if (temp > ZERO) { + temp = FastMath.sqrt(temp) + s.getEntry(i); + if (angbd * temp > tempb) { + angbd = tempb / temp; + iact = i; + xsav = ONE; + } + } + } + } + + // Calculate HHD and some curvatures for the alternative iteration. + + state = 210; break; + } + case 150: { + printState(150); // XXX + shs = ZERO; + dhs = ZERO; + dhd = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + shs += s.getEntry(i) * hs.getEntry(i); + dhs += trialStepPoint.getEntry(i) * hs.getEntry(i); + dhd += trialStepPoint.getEntry(i) * hred.getEntry(i); + } + } + + // Seek the greatest reduction in Q for a range of equally spaced values + // of ANGT in [0,ANGBD], where ANGT is the tangent of half the angle of + // the alternative iteration. + + redmax = ZERO; + isav = -1; + redsav = ZERO; + iu = (int) (angbd * 17. + 3.1); + for (int i = 0; i < iu; i++) { + angt = angbd * i / iu; + sth = (angt + angt) / (ONE + angt * angt); + temp = shs + angt * (angt * dhd - dhs - dhs); + rednew = sth * (angt * dredg - sredg - HALF * sth * temp); + if (rednew > redmax) { + redmax = rednew; + isav = i; + rdprev = redsav; + } else if (i == isav + 1) { + rdnext = rednew; + } + redsav = rednew; + } + + // Return if the reduction is zero. Otherwise, set the sine and cosine + // of the angle of the alternative iteration, and calculate SDEC. + + if (isav < 0) { + state = 190; break; + } + if (isav < iu) { + temp = (rdnext - rdprev) / (redmax + redmax - rdprev - rdnext); + angt = angbd * (isav + HALF * temp) / iu; + } + cth = (ONE - angt * angt) / (ONE + angt * angt); + sth = (angt + angt) / (ONE + angt * angt); + temp = shs + angt * (angt * dhd - dhs - dhs); + sdec = sth * (angt * dredg - sredg - HALF * sth * temp); + if (sdec <= ZERO) { + state = 190; break; + } + + // Update GNEW, D and HRED. If the angle of the alternative iteration + // is restricted by a bound on a free variable, that variable is fixed + // at the bound. + + dredg = ZERO; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + gnew.setEntry(i, gnew.getEntry(i) + (cth - ONE) * hred.getEntry(i) + sth * hs.getEntry(i)); + if (xbdi.getEntry(i) == ZERO) { + trialStepPoint.setEntry(i, cth * trialStepPoint.getEntry(i) + sth * s.getEntry(i)); + dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i); + // Computing 2nd power + final double d1 = gnew.getEntry(i); + gredsq += d1 * d1; + } + hred.setEntry(i, cth * hred.getEntry(i) + sth * hs.getEntry(i)); + } + qred += sdec; + if (iact >= 0 && isav == iu) { + ++nact; + xbdi.setEntry(iact, xsav); + state = 100; break; + } + + // If SDEC is sufficiently small, then RETURN after setting XNEW to + // XOPT+D, giving careful attention to the bounds. + + if (sdec > qred * .01) { + state = 120; break; + } + } + case 190: { + printState(190); // XXX + dsq = ZERO; + for (int i = 0; i < n; i++) { + // Computing MAX + // Computing MIN + final double min = FastMath.min(trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i), + upperDifference.getEntry(i)); + newPoint.setEntry(i, FastMath.max(min, lowerDifference.getEntry(i))); + if (xbdi.getEntry(i) == MINUS_ONE) { + newPoint.setEntry(i, lowerDifference.getEntry(i)); + } + if (xbdi.getEntry(i) == ONE) { + newPoint.setEntry(i, upperDifference.getEntry(i)); + } + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(i); + dsq += d1 * d1; + } + return new double[] { dsq, crvmin }; + // The following instructions multiply the current S-vector by the second + // derivative matrix of the quadratic model, putting the product in HS. + // They are reached from three different parts of the software above and + // they can be regarded as an external subroutine. + } + case 210: { + printState(210); // XXX + int ih = 0; + for (int j = 0; j < n; j++) { + hs.setEntry(j, ZERO); + for (int i = 0; i <= j; i++) { + if (i < j) { + hs.setEntry(j, hs.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(i)); + } + hs.setEntry(i, hs.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(j)); + ih++; + } + } + final RealVector tmp = interpolationPoints.operate(s).ebeMultiply(modelSecondDerivativesParameters); + for (int k = 0; k < npt; k++) { + if (modelSecondDerivativesParameters.getEntry(k) != ZERO) { + for (int i = 0; i < n; i++) { + hs.setEntry(i, hs.getEntry(i) + tmp.getEntry(k) * interpolationPoints.getEntry(k, i)); + } + } + } + if (crvmin != ZERO) { + state = 50; break; + } + if (iterc > itcsav) { + state = 150; break; + } + for (int i = 0; i < n; i++) { + hred.setEntry(i, hs.getEntry(i)); + } + state = 120; break; + } + default: { + throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "trsbox"); + }} + } + } // trsbox + + // ---------------------------------------------------------------------------------------- + + /** + * The arrays BMAT and ZMAT are updated, as required by the new position + * of the interpolation point that has the index KNEW. The vector VLAG has + * N+NPT components, set on entry to the first NPT and last N components + * of the product Hw in equation (4.11) of the Powell (2006) paper on + * NEWUOA. Further, BETA is set on entry to the value of the parameter + * with that name, and DENOM is set to the denominator of the updating + * formula. Elements of ZMAT may be treated as zero if their moduli are + * at most ZTEST. The first NDIM elements of W are used for working space. + * @param beta + * @param denom + * @param knew + */ + private void update( + double beta, + double denom, + int knew + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int nptm = npt - n - 1; + + // XXX Should probably be split into two arrays. + final ArrayRealVector work = new ArrayRealVector(npt + n); + + double ztest = ZERO; + for (int k = 0; k < npt; k++) { + for (int j = 0; j < nptm; j++) { + // Computing MAX + ztest = FastMath.max(ztest, FastMath.abs(zMatrix.getEntry(k, j))); + } + } + ztest *= 1e-20; + + // Apply the rotations that put zeros in the KNEW-th row of ZMAT. + + for (int j = 1; j < nptm; j++) { + final double d1 = zMatrix.getEntry(knew, j); + if (FastMath.abs(d1) > ztest) { + // Computing 2nd power + final double d2 = zMatrix.getEntry(knew, 0); + // Computing 2nd power + final double d3 = zMatrix.getEntry(knew, j); + final double d4 = FastMath.sqrt(d2 * d2 + d3 * d3); + final double d5 = zMatrix.getEntry(knew, 0) / d4; + final double d6 = zMatrix.getEntry(knew, j) / d4; + for (int i = 0; i < npt; i++) { + final double d7 = d5 * zMatrix.getEntry(i, 0) + d6 * zMatrix.getEntry(i, j); + zMatrix.setEntry(i, j, d5 * zMatrix.getEntry(i, j) - d6 * zMatrix.getEntry(i, 0)); + zMatrix.setEntry(i, 0, d7); + } + } + zMatrix.setEntry(knew, j, ZERO); + } + + // Put the first NPT components of the KNEW-th column of HLAG into W, + // and calculate the parameters of the updating formula. + + for (int i = 0; i < npt; i++) { + work.setEntry(i, zMatrix.getEntry(knew, 0) * zMatrix.getEntry(i, 0)); + } + final double alpha = work.getEntry(knew); + final double tau = lagrangeValuesAtNewPoint.getEntry(knew); + lagrangeValuesAtNewPoint.setEntry(knew, lagrangeValuesAtNewPoint.getEntry(knew) - ONE); + + // Complete the updating of ZMAT. + + final double sqrtDenom = FastMath.sqrt(denom); + final double d1 = tau / sqrtDenom; + final double d2 = zMatrix.getEntry(knew, 0) / sqrtDenom; + for (int i = 0; i < npt; i++) { + zMatrix.setEntry(i, 0, + d1 * zMatrix.getEntry(i, 0) - d2 * lagrangeValuesAtNewPoint.getEntry(i)); + } + + // Finally, update the matrix BMAT. + + for (int j = 0; j < n; j++) { + final int jp = npt + j; + work.setEntry(jp, bMatrix.getEntry(knew, j)); + final double d3 = (alpha * lagrangeValuesAtNewPoint.getEntry(jp) - tau * work.getEntry(jp)) / denom; + final double d4 = (-beta * work.getEntry(jp) - tau * lagrangeValuesAtNewPoint.getEntry(jp)) / denom; + for (int i = 0; i <= jp; i++) { + bMatrix.setEntry(i, j, + bMatrix.getEntry(i, j) + d3 * lagrangeValuesAtNewPoint.getEntry(i) + d4 * work.getEntry(i)); + if (i >= npt) { + bMatrix.setEntry(jp, (i - npt), bMatrix.getEntry(i, j)); + } + } + } + } // update + + /** + * Performs validity checks. + * + * @param lowerBound Lower bounds (constraints) of the objective variables. + * @param upperBound Upperer bounds (constraints) of the objective variables. + */ + private void setup(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + double[] init = getStartPoint(); + final int dimension = init.length; + + // Check problem dimension. + if (dimension < MINIMUM_PROBLEM_DIMENSION) { + throw new NumberIsTooSmallException(dimension, MINIMUM_PROBLEM_DIMENSION, true); + } + // Check number of interpolation points. + final int[] nPointsInterval = { dimension + 2, (dimension + 2) * (dimension + 1) / 2 }; + if (numberOfInterpolationPoints < nPointsInterval[0] || + numberOfInterpolationPoints > nPointsInterval[1]) { + throw new OutOfRangeException(LocalizedFormats.NUMBER_OF_INTERPOLATION_POINTS, + numberOfInterpolationPoints, + nPointsInterval[0], + nPointsInterval[1]); + } + + // Initialize bound differences. + boundDifference = new double[dimension]; + + double requiredMinDiff = 2 * initialTrustRegionRadius; + double minDiff = Double.POSITIVE_INFINITY; + for (int i = 0; i < dimension; i++) { + boundDifference[i] = upperBound[i] - lowerBound[i]; + minDiff = FastMath.min(minDiff, boundDifference[i]); + } + if (minDiff < requiredMinDiff) { + initialTrustRegionRadius = minDiff / 3.0; + } + + // Initialize the data structures used by the "bobyqa" method. + bMatrix = new Array2DRowRealMatrix(dimension + numberOfInterpolationPoints, + dimension); + zMatrix = new Array2DRowRealMatrix(numberOfInterpolationPoints, + numberOfInterpolationPoints - dimension - 1); + interpolationPoints = new Array2DRowRealMatrix(numberOfInterpolationPoints, + dimension); + originShift = new ArrayRealVector(dimension); + fAtInterpolationPoints = new ArrayRealVector(numberOfInterpolationPoints); + trustRegionCenterOffset = new ArrayRealVector(dimension); + gradientAtTrustRegionCenter = new ArrayRealVector(dimension); + lowerDifference = new ArrayRealVector(dimension); + upperDifference = new ArrayRealVector(dimension); + modelSecondDerivativesParameters = new ArrayRealVector(numberOfInterpolationPoints); + newPoint = new ArrayRealVector(dimension); + alternativeNewPoint = new ArrayRealVector(dimension); + trialStepPoint = new ArrayRealVector(dimension); + lagrangeValuesAtNewPoint = new ArrayRealVector(dimension + numberOfInterpolationPoints); + modelSecondDerivativesValues = new ArrayRealVector(dimension * (dimension + 1) / 2); + } + + // XXX utility for figuring out call sequence. + private static String caller(int n) { + final Throwable t = new Throwable(); + final StackTraceElement[] elements = t.getStackTrace(); + final StackTraceElement e = elements[n]; + return e.getMethodName() + " (at line " + e.getLineNumber() + ")"; + } + // XXX utility for figuring out call sequence. + private static void printState(int s) { + // System.out.println(caller(2) + ": state " + s); + } + // XXX utility for figuring out call sequence. + private static void printMethod() { + // System.out.println(caller(2)); + } + + /** + * Marker for code paths that are not explored with the current unit tests. + * If the path becomes explored, it should just be removed from the code. + */ + private static class PathIsExploredException extends RuntimeException { + /** Serializable UID. */ + private static final long serialVersionUID = 745350979634801853L; + + /** Message string. */ + private static final String PATH_IS_EXPLORED + = "If this exception is thrown, just remove it from the code"; + + PathIsExploredException() { + super(PATH_IS_EXPLORED + " " + BOBYQAOptimizer.caller(3)); + } + } +} +//CHECKSTYLE: resume all diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java new file mode 100644 index 000000000..ba6c99e03 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java @@ -0,0 +1,1354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * An implementation of the active Covariance Matrix Adaptation Evolution Strategy (CMA-ES) + * for non-linear, non-convex, non-smooth, global function minimization. + *

    + * The CMA-Evolution Strategy (CMA-ES) is a reliable stochastic optimization method + * which should be applied if derivative-based methods, e.g. quasi-Newton BFGS or + * conjugate gradient, fail due to a rugged search landscape (e.g. noise, local + * optima, outlier, etc.) of the objective function. Like a + * quasi-Newton method, the CMA-ES learns and applies a variable metric + * on the underlying search space. Unlike a quasi-Newton method, the + * CMA-ES neither estimates nor uses gradients, making it considerably more + * reliable in terms of finding a good, or even close to optimal, solution. + *

    + * In general, on smooth objective functions the CMA-ES is roughly ten times + * slower than BFGS (counting objective function evaluations, no gradients provided). + * For up to N=10 variables also the derivative-free simplex + * direct search method (Nelder and Mead) can be faster, but it is + * far less reliable than CMA-ES. + *

    + * The CMA-ES is particularly well suited for non-separable + * and/or badly conditioned problems. To observe the advantage of CMA compared + * to a conventional evolution strategy, it will usually take about + * 30 N function evaluations. On difficult problems the complete + * optimization (a single run) is expected to take roughly between + * 30 N and 300 N2 + * function evaluations. + *

    + * This implementation is translated and adapted from the Matlab version + * of the CMA-ES algorithm as implemented in module {@code cmaes.m} version 3.51. + *

    + * For more information, please refer to the following links: + *

    + * + * @since 3.0 + */ +public class CMAESOptimizer + extends MultivariateOptimizer { + // global search parameters + /** + * Population size, offspring number. The primary strategy parameter to play + * with, which can be increased from its default value. Increasing the + * population size improves global search properties in exchange to speed. + * Speed decreases, as a rule, at most linearly with increasing population + * size. It is advisable to begin with the default small population size. + */ + private int lambda; // population size + /** + * Covariance update mechanism, default is active CMA. isActiveCMA = true + * turns on "active CMA" with a negative update of the covariance matrix and + * checks for positive definiteness. OPTS.CMA.active = 2 does not check for + * pos. def. and is numerically faster. Active CMA usually speeds up the + * adaptation. + */ + private final boolean isActiveCMA; + /** + * Determines how often a new random offspring is generated in case it is + * not feasible / beyond the defined limits, default is 0. + */ + private final int checkFeasableCount; + /** + * @see Sigma + */ + private double[] inputSigma; + /** Number of objective variables/problem dimension */ + private int dimension; + /** + * Defines the number of initial iterations, where the covariance matrix + * remains diagonal and the algorithm has internally linear time complexity. + * diagonalOnly = 1 means keeping the covariance matrix always diagonal and + * this setting also exhibits linear space complexity. This can be + * particularly useful for dimension > 100. + * @see A Simple Modification in CMA-ES + */ + private int diagonalOnly; + /** Number of objective variables/problem dimension */ + private boolean isMinimize = true; + /** Indicates whether statistic data is collected. */ + private final boolean generateStatistics; + + // termination criteria + /** Maximal number of iterations allowed. */ + private final int maxIterations; + /** Limit for fitness value. */ + private final double stopFitness; + /** Stop if x-changes larger stopTolUpX. */ + private double stopTolUpX; + /** Stop if x-change smaller stopTolX. */ + private double stopTolX; + /** Stop if fun-changes smaller stopTolFun. */ + private double stopTolFun; + /** Stop if back fun-changes smaller stopTolHistFun. */ + private double stopTolHistFun; + + // selection strategy parameters + /** Number of parents/points for recombination. */ + private int mu; // + /** log(mu + 0.5), stored for efficiency. */ + private double logMu2; + /** Array for weighted recombination. */ + private RealMatrix weights; + /** Variance-effectiveness of sum w_i x_i. */ + private double mueff; // + + // dynamic strategy parameters and constants + /** Overall standard deviation - search volume. */ + private double sigma; + /** Cumulation constant. */ + private double cc; + /** Cumulation constant for step-size. */ + private double cs; + /** Damping for step-size. */ + private double damps; + /** Learning rate for rank-one update. */ + private double ccov1; + /** Learning rate for rank-mu update' */ + private double ccovmu; + /** Expectation of ||N(0,I)|| == norm(randn(N,1)). */ + private double chiN; + /** Learning rate for rank-one update - diagonalOnly */ + private double ccov1Sep; + /** Learning rate for rank-mu update - diagonalOnly */ + private double ccovmuSep; + + // CMA internal values - updated each generation + /** Objective variables. */ + private RealMatrix xmean; + /** Evolution path. */ + private RealMatrix pc; + /** Evolution path for sigma. */ + private RealMatrix ps; + /** Norm of ps, stored for efficiency. */ + private double normps; + /** Coordinate system. */ + private RealMatrix B; + /** Scaling. */ + private RealMatrix D; + /** B*D, stored for efficiency. */ + private RealMatrix BD; + /** Diagonal of sqrt(D), stored for efficiency. */ + private RealMatrix diagD; + /** Covariance matrix. */ + private RealMatrix C; + /** Diagonal of C, used for diagonalOnly. */ + private RealMatrix diagC; + /** Number of iterations already performed. */ + private int iterations; + + /** History queue of best values. */ + private double[] fitnessHistory; + /** Size of history queue of best values. */ + private int historySize; + + /** Random generator. */ + private final RandomGenerator random; + + /** History of sigma values. */ + private final List statisticsSigmaHistory = new ArrayList(); + /** History of mean matrix. */ + private final List statisticsMeanHistory = new ArrayList(); + /** History of fitness values. */ + private final List statisticsFitnessHistory = new ArrayList(); + /** History of D matrix. */ + private final List statisticsDHistory = new ArrayList(); + + /** + * @param maxIterations Maximal number of iterations. + * @param stopFitness Whether to stop if objective function value is smaller than + * {@code stopFitness}. + * @param isActiveCMA Chooses the covariance matrix update method. + * @param diagonalOnly Number of initial iterations, where the covariance matrix + * remains diagonal. + * @param checkFeasableCount Determines how often new random objective variables are + * generated in case they are out of bounds. + * @param random Random generator. + * @param generateStatistics Whether statistic data is collected. + * @param checker Convergence checker. + * + * @since 3.1 + */ + public CMAESOptimizer(int maxIterations, + double stopFitness, + boolean isActiveCMA, + int diagonalOnly, + int checkFeasableCount, + RandomGenerator random, + boolean generateStatistics, + ConvergenceChecker checker) { + super(checker); + this.maxIterations = maxIterations; + this.stopFitness = stopFitness; + this.isActiveCMA = isActiveCMA; + this.diagonalOnly = diagonalOnly; + this.checkFeasableCount = checkFeasableCount; + this.random = random; + this.generateStatistics = generateStatistics; + } + + /** + * @return History of sigma values. + */ + public List getStatisticsSigmaHistory() { + return statisticsSigmaHistory; + } + + /** + * @return History of mean matrix. + */ + public List getStatisticsMeanHistory() { + return statisticsMeanHistory; + } + + /** + * @return History of fitness values. + */ + public List getStatisticsFitnessHistory() { + return statisticsFitnessHistory; + } + + /** + * @return History of D matrix. + */ + public List getStatisticsDHistory() { + return statisticsDHistory; + } + + /** + * Input sigma values. + * They define the initial coordinate-wise standard deviations for + * sampling new search points around the initial guess. + * It is suggested to set them to the estimated distance from the + * initial to the desired optimum. + * Small values induce the search to be more local (and very small + * values are more likely to find a local optimum close to the initial + * guess). + * Too small values might however lead to early termination. + */ + public static class Sigma implements OptimizationData { + /** Sigma values. */ + private final double[] sigma; + + /** + * @param s Sigma values. + * @throws NotPositiveException if any of the array entries is smaller + * than zero. + */ + public Sigma(double[] s) + throws NotPositiveException { + for (int i = 0; i < s.length; i++) { + if (s[i] < 0) { + throw new NotPositiveException(s[i]); + } + } + + sigma = s.clone(); + } + + /** + * @return the sigma values. + */ + public double[] getSigma() { + return sigma.clone(); + } + } + + /** + * Population size. + * The number of offspring is the primary strategy parameter. + * In the absence of better clues, a good default could be an + * integer close to {@code 4 + 3 ln(n)}, where {@code n} is the + * number of optimized parameters. + * Increasing the population size improves global search properties + * at the expense of speed (which in general decreases at most + * linearly with increasing population size). + */ + public static class PopulationSize implements OptimizationData { + /** Population size. */ + private final int lambda; + + /** + * @param size Population size. + * @throws NotStrictlyPositiveException if {@code size <= 0}. + */ + public PopulationSize(int size) + throws NotStrictlyPositiveException { + if (size <= 0) { + throw new NotStrictlyPositiveException(size); + } + lambda = size; + } + + /** + * @return the population size. + */ + public int getPopulationSize() { + return lambda; + } + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * MultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link Sigma}
    • + *
    • {@link PopulationSize}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + */ + @Override + public PointValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException, + DimensionMismatchException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + // -------------------- Initialization -------------------------------- + isMinimize = getGoalType().equals(GoalType.MINIMIZE); + final FitnessFunction fitfun = new FitnessFunction(); + final double[] guess = getStartPoint(); + // number of objective variables/problem dimension + dimension = guess.length; + initializeCMA(guess); + iterations = 0; + ValuePenaltyPair valuePenalty = fitfun.value(guess); + double bestValue = valuePenalty.value+valuePenalty.penalty; + push(fitnessHistory, bestValue); + PointValuePair optimum + = new PointValuePair(getStartPoint(), + isMinimize ? bestValue : -bestValue); + PointValuePair lastResult = null; + + // -------------------- Generation Loop -------------------------------- + + generationLoop: + for (iterations = 1; iterations <= maxIterations; iterations++) { + incrementIterationCount(); + + // Generate and evaluate lambda offspring + final RealMatrix arz = randn1(dimension, lambda); + final RealMatrix arx = zeros(dimension, lambda); + final double[] fitness = new double[lambda]; + final ValuePenaltyPair[] valuePenaltyPairs = new ValuePenaltyPair[lambda]; + // generate random offspring + for (int k = 0; k < lambda; k++) { + RealMatrix arxk = null; + for (int i = 0; i < checkFeasableCount + 1; i++) { + if (diagonalOnly <= 0) { + arxk = xmean.add(BD.multiply(arz.getColumnMatrix(k)) + .scalarMultiply(sigma)); // m + sig * Normal(0,C) + } else { + arxk = xmean.add(times(diagD,arz.getColumnMatrix(k)) + .scalarMultiply(sigma)); + } + if (i >= checkFeasableCount || + fitfun.isFeasible(arxk.getColumn(0))) { + break; + } + // regenerate random arguments for row + arz.setColumn(k, randn(dimension)); + } + copyColumn(arxk, 0, arx, k); + try { + valuePenaltyPairs[k] = fitfun.value(arx.getColumn(k)); // compute fitness + } catch (TooManyEvaluationsException e) { + break generationLoop; + } + } + + // Compute fitnesses by adding value and penalty after scaling by value range. + double valueRange = valueRange(valuePenaltyPairs); + for (int iValue=0;iValue bestFitness) { + bestValue = bestFitness; + lastResult = optimum; + optimum = new PointValuePair(fitfun.repair(bestArx.getColumn(0)), + isMinimize ? bestFitness : -bestFitness); + if (getConvergenceChecker() != null && lastResult != null && + getConvergenceChecker().converged(iterations, optimum, lastResult)) { + break generationLoop; + } + } + // handle termination criteria + // Break, if fitness is good enough + if (stopFitness != 0 && bestFitness < (isMinimize ? stopFitness : -stopFitness)) { + break generationLoop; + } + final double[] sqrtDiagC = sqrt(diagC).getColumn(0); + final double[] pcCol = pc.getColumn(0); + for (int i = 0; i < dimension; i++) { + if (sigma * FastMath.max(FastMath.abs(pcCol[i]), sqrtDiagC[i]) > stopTolX) { + break; + } + if (i >= dimension - 1) { + break generationLoop; + } + } + for (int i = 0; i < dimension; i++) { + if (sigma * sqrtDiagC[i] > stopTolUpX) { + break generationLoop; + } + } + final double historyBest = min(fitnessHistory); + final double historyWorst = max(fitnessHistory); + if (iterations > 2 && + FastMath.max(historyWorst, worstFitness) - + FastMath.min(historyBest, bestFitness) < stopTolFun) { + break generationLoop; + } + if (iterations > fitnessHistory.length && + historyWorst - historyBest < stopTolHistFun) { + break generationLoop; + } + // condition number of the covariance matrix exceeds 1e14 + if (max(diagD) / min(diagD) > 1e7) { + break generationLoop; + } + // user defined termination + if (getConvergenceChecker() != null) { + final PointValuePair current + = new PointValuePair(bestArx.getColumn(0), + isMinimize ? bestFitness : -bestFitness); + if (lastResult != null && + getConvergenceChecker().converged(iterations, current, lastResult)) { + break generationLoop; + } + lastResult = current; + } + // Adjust step size in case of equal function values (flat fitness) + if (bestValue == fitness[arindex[(int)(0.1+lambda/4.)]]) { + sigma *= FastMath.exp(0.2 + cs / damps); + } + if (iterations > 2 && FastMath.max(historyWorst, bestFitness) - + FastMath.min(historyBest, bestFitness) == 0) { + sigma *= FastMath.exp(0.2 + cs / damps); + } + // store best in history + push(fitnessHistory,bestFitness); + if (generateStatistics) { + statisticsSigmaHistory.add(sigma); + statisticsFitnessHistory.add(bestFitness); + statisticsMeanHistory.add(xmean.transpose()); + statisticsDHistory.add(diagD.transpose().scalarMultiply(1E5)); + } + } + return optimum; + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
      + *
    • {@link Sigma}
    • + *
    • {@link PopulationSize}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof Sigma) { + inputSigma = ((Sigma) data).getSigma(); + continue; + } + if (data instanceof PopulationSize) { + lambda = ((PopulationSize) data).getPopulationSize(); + continue; + } + } + + checkParameters(); + } + + /** + * Checks dimensions and values of boundaries and inputSigma if defined. + */ + private void checkParameters() { + final double[] init = getStartPoint(); + final double[] lB = getLowerBound(); + final double[] uB = getUpperBound(); + + if (inputSigma != null) { + if (inputSigma.length != init.length) { + throw new DimensionMismatchException(inputSigma.length, init.length); + } + for (int i = 0; i < init.length; i++) { + if (inputSigma[i] > uB[i] - lB[i]) { + throw new OutOfRangeException(inputSigma[i], 0, uB[i] - lB[i]); + } + } + } + } + + /** + * Initialization of the dynamic search parameters + * + * @param guess Initial guess for the arguments of the fitness function. + */ + private void initializeCMA(double[] guess) { + if (lambda <= 0) { + throw new NotStrictlyPositiveException(lambda); + } + // initialize sigma + final double[][] sigmaArray = new double[guess.length][1]; + for (int i = 0; i < guess.length; i++) { + sigmaArray[i][0] = inputSigma[i]; + } + final RealMatrix insigma = new Array2DRowRealMatrix(sigmaArray, false); + sigma = max(insigma); // overall standard deviation + + // initialize termination criteria + stopTolUpX = 1e3 * max(insigma); + stopTolX = 1e-11 * max(insigma); + stopTolFun = 1e-12; + stopTolHistFun = 1e-13; + + // initialize selection strategy parameters + mu = lambda / 2; // number of parents/points for recombination + logMu2 = FastMath.log(mu + 0.5); + weights = log(sequence(1, mu, 1)).scalarMultiply(-1).scalarAdd(logMu2); + double sumw = 0; + double sumwq = 0; + for (int i = 0; i < mu; i++) { + double w = weights.getEntry(i, 0); + sumw += w; + sumwq += w * w; + } + weights = weights.scalarMultiply(1 / sumw); + mueff = sumw * sumw / sumwq; // variance-effectiveness of sum w_i x_i + + // initialize dynamic strategy parameters and constants + cc = (4 + mueff / dimension) / + (dimension + 4 + 2 * mueff / dimension); + cs = (mueff + 2) / (dimension + mueff + 3.); + damps = (1 + 2 * FastMath.max(0, FastMath.sqrt((mueff - 1) / + (dimension + 1)) - 1)) * + FastMath.max(0.3, + 1 - dimension / (1e-6 + maxIterations)) + cs; // minor increment + ccov1 = 2 / ((dimension + 1.3) * (dimension + 1.3) + mueff); + ccovmu = FastMath.min(1 - ccov1, 2 * (mueff - 2 + 1 / mueff) / + ((dimension + 2) * (dimension + 2) + mueff)); + ccov1Sep = FastMath.min(1, ccov1 * (dimension + 1.5) / 3); + ccovmuSep = FastMath.min(1 - ccov1, ccovmu * (dimension + 1.5) / 3); + chiN = FastMath.sqrt(dimension) * + (1 - 1 / ((double) 4 * dimension) + 1 / ((double) 21 * dimension * dimension)); + // intialize CMA internal values - updated each generation + xmean = MatrixUtils.createColumnRealMatrix(guess); // objective variables + diagD = insigma.scalarMultiply(1 / sigma); + diagC = square(diagD); + pc = zeros(dimension, 1); // evolution paths for C and sigma + ps = zeros(dimension, 1); // B defines the coordinate system + normps = ps.getFrobeniusNorm(); + + B = eye(dimension, dimension); + D = ones(dimension, 1); // diagonal D defines the scaling + BD = times(B, repmat(diagD.transpose(), dimension, 1)); + C = B.multiply(diag(square(D)).multiply(B.transpose())); // covariance + historySize = 10 + (int) (3 * 10 * dimension / (double) lambda); + fitnessHistory = new double[historySize]; // history of fitness values + for (int i = 0; i < historySize; i++) { + fitnessHistory[i] = Double.MAX_VALUE; + } + } + + /** + * Update of the evolution paths ps and pc. + * + * @param zmean Weighted row matrix of the gaussian random numbers generating + * the current offspring. + * @param xold xmean matrix of the previous generation. + * @return hsig flag indicating a small correction. + */ + private boolean updateEvolutionPaths(RealMatrix zmean, RealMatrix xold) { + ps = ps.scalarMultiply(1 - cs).add( + B.multiply(zmean).scalarMultiply( + FastMath.sqrt(cs * (2 - cs) * mueff))); + normps = ps.getFrobeniusNorm(); + final boolean hsig = normps / + FastMath.sqrt(1 - FastMath.pow(1 - cs, 2 * iterations)) / + chiN < 1.4 + 2 / ((double) dimension + 1); + pc = pc.scalarMultiply(1 - cc); + if (hsig) { + pc = pc.add(xmean.subtract(xold).scalarMultiply(FastMath.sqrt(cc * (2 - cc) * mueff) / sigma)); + } + return hsig; + } + + /** + * Update of the covariance matrix C for diagonalOnly > 0 + * + * @param hsig Flag indicating a small correction. + * @param bestArz Fitness-sorted matrix of the gaussian random values of the + * current offspring. + */ + private void updateCovarianceDiagonalOnly(boolean hsig, + final RealMatrix bestArz) { + // minor correction if hsig==false + double oldFac = hsig ? 0 : ccov1Sep * cc * (2 - cc); + oldFac += 1 - ccov1Sep - ccovmuSep; + diagC = diagC.scalarMultiply(oldFac) // regard old matrix + .add(square(pc).scalarMultiply(ccov1Sep)) // plus rank one update + .add((times(diagC, square(bestArz).multiply(weights))) // plus rank mu update + .scalarMultiply(ccovmuSep)); + diagD = sqrt(diagC); // replaces eig(C) + if (diagonalOnly > 1 && + iterations > diagonalOnly) { + // full covariance matrix from now on + diagonalOnly = 0; + B = eye(dimension, dimension); + BD = diag(diagD); + C = diag(diagC); + } + } + + /** + * Update of the covariance matrix C. + * + * @param hsig Flag indicating a small correction. + * @param bestArx Fitness-sorted matrix of the argument vectors producing the + * current offspring. + * @param arz Unsorted matrix containing the gaussian random values of the + * current offspring. + * @param arindex Indices indicating the fitness-order of the current offspring. + * @param xold xmean matrix of the previous generation. + */ + private void updateCovariance(boolean hsig, final RealMatrix bestArx, + final RealMatrix arz, final int[] arindex, + final RealMatrix xold) { + double negccov = 0; + if (ccov1 + ccovmu > 0) { + final RealMatrix arpos = bestArx.subtract(repmat(xold, 1, mu)) + .scalarMultiply(1 / sigma); // mu difference vectors + final RealMatrix roneu = pc.multiply(pc.transpose()) + .scalarMultiply(ccov1); // rank one update + // minor correction if hsig==false + double oldFac = hsig ? 0 : ccov1 * cc * (2 - cc); + oldFac += 1 - ccov1 - ccovmu; + if (isActiveCMA) { + // Adapt covariance matrix C active CMA + negccov = (1 - ccovmu) * 0.25 * mueff / + (FastMath.pow(dimension + 2, 1.5) + 2 * mueff); + // keep at least 0.66 in all directions, small popsize are most + // critical + final double negminresidualvariance = 0.66; + // where to make up for the variance loss + final double negalphaold = 0.5; + // prepare vectors, compute negative updating matrix Cneg + final int[] arReverseIndex = reverse(arindex); + RealMatrix arzneg = selectColumns(arz, MathArrays.copyOf(arReverseIndex, mu)); + RealMatrix arnorms = sqrt(sumRows(square(arzneg))); + final int[] idxnorms = sortedIndices(arnorms.getRow(0)); + final RealMatrix arnormsSorted = selectColumns(arnorms, idxnorms); + final int[] idxReverse = reverse(idxnorms); + final RealMatrix arnormsReverse = selectColumns(arnorms, idxReverse); + arnorms = divide(arnormsReverse, arnormsSorted); + final int[] idxInv = inverse(idxnorms); + final RealMatrix arnormsInv = selectColumns(arnorms, idxInv); + // check and set learning rate negccov + final double negcovMax = (1 - negminresidualvariance) / + square(arnormsInv).multiply(weights).getEntry(0, 0); + if (negccov > negcovMax) { + negccov = negcovMax; + } + arzneg = times(arzneg, repmat(arnormsInv, dimension, 1)); + final RealMatrix artmp = BD.multiply(arzneg); + final RealMatrix Cneg = artmp.multiply(diag(weights)).multiply(artmp.transpose()); + oldFac += negalphaold * negccov; + C = C.scalarMultiply(oldFac) + .add(roneu) // regard old matrix + .add(arpos.scalarMultiply( // plus rank one update + ccovmu + (1 - negalphaold) * negccov) // plus rank mu update + .multiply(times(repmat(weights, 1, dimension), + arpos.transpose()))) + .subtract(Cneg.scalarMultiply(negccov)); + } else { + // Adapt covariance matrix C - nonactive + C = C.scalarMultiply(oldFac) // regard old matrix + .add(roneu) // plus rank one update + .add(arpos.scalarMultiply(ccovmu) // plus rank mu update + .multiply(times(repmat(weights, 1, dimension), + arpos.transpose()))); + } + } + updateBD(negccov); + } + + /** + * Update B and D from C. + * + * @param negccov Negative covariance factor. + */ + private void updateBD(double negccov) { + if (ccov1 + ccovmu + negccov > 0 && + (iterations % 1. / (ccov1 + ccovmu + negccov) / dimension / 10.) < 1) { + // to achieve O(N^2) + C = triu(C, 0).add(triu(C, 1).transpose()); + // enforce symmetry to prevent complex numbers + final EigenDecomposition eig = new EigenDecomposition(C); + B = eig.getV(); // eigen decomposition, B==normalized eigenvectors + D = eig.getD(); + diagD = diag(D); + if (min(diagD) <= 0) { + for (int i = 0; i < dimension; i++) { + if (diagD.getEntry(i, 0) < 0) { + diagD.setEntry(i, 0, 0); + } + } + final double tfac = max(diagD) / 1e14; + C = C.add(eye(dimension, dimension).scalarMultiply(tfac)); + diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac)); + } + if (max(diagD) > 1e14 * min(diagD)) { + final double tfac = max(diagD) / 1e14 - min(diagD); + C = C.add(eye(dimension, dimension).scalarMultiply(tfac)); + diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac)); + } + diagC = diag(C); + diagD = sqrt(diagD); // D contains standard deviations now + BD = times(B, repmat(diagD.transpose(), dimension, 1)); // O(n^2) + } + } + + /** + * Pushes the current best fitness value in a history queue. + * + * @param vals History queue. + * @param val Current best fitness value. + */ + private static void push(double[] vals, double val) { + for (int i = vals.length-1; i > 0; i--) { + vals[i] = vals[i-1]; + } + vals[0] = val; + } + + /** + * Sorts fitness values. + * + * @param doubles Array of values to be sorted. + * @return a sorted array of indices pointing into doubles. + */ + private int[] sortedIndices(final double[] doubles) { + final DoubleIndex[] dis = new DoubleIndex[doubles.length]; + for (int i = 0; i < doubles.length; i++) { + dis[i] = new DoubleIndex(doubles[i], i); + } + Arrays.sort(dis); + final int[] indices = new int[doubles.length]; + for (int i = 0; i < doubles.length; i++) { + indices[i] = dis[i].index; + } + return indices; + } + /** + * Get range of values. + * + * @param vpPairs Array of valuePenaltyPairs to get range from. + * @return a double equal to maximum value minus minimum value. + */ + private double valueRange(final ValuePenaltyPair[] vpPairs) { + double max = Double.NEGATIVE_INFINITY; + double min = Double.MAX_VALUE; + for (ValuePenaltyPair vpPair:vpPairs) { + if (vpPair.value > max) { + max = vpPair.value; + } + if (vpPair.value < min) { + min = vpPair.value; + } + } + return max-min; + } + + /** + * Used to sort fitness values. Sorting is always in lower value first + * order. + */ + private static class DoubleIndex implements Comparable { + /** Value to compare. */ + private final double value; + /** Index into sorted array. */ + private final int index; + + /** + * @param value Value to compare. + * @param index Index into sorted array. + */ + DoubleIndex(double value, int index) { + this.value = value; + this.index = index; + } + + /** {@inheritDoc} */ + public int compareTo(DoubleIndex o) { + return Double.compare(value, o.value); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof DoubleIndex) { + return Double.compare(value, ((DoubleIndex) other).value) == 0; + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + long bits = Double.doubleToLongBits(value); + return (int) ((1438542 ^ (bits >>> 32) ^ bits) & 0xffffffff); + } + } + /** + * Stores the value and penalty (for repair of out of bounds point). + */ + private static class ValuePenaltyPair { + /** Objective function value. */ + private double value; + /** Penalty value for repair of out out of bounds points. */ + private double penalty; + + /** + * @param value Function value. + * @param penalty Out-of-bounds penalty. + */ + ValuePenaltyPair(final double value, final double penalty) { + this.value = value; + this.penalty = penalty; + } + } + + + /** + * Normalizes fitness values to the range [0,1]. Adds a penalty to the + * fitness value if out of range. + */ + private class FitnessFunction { + /** + * Flag indicating whether the objective variables are forced into their + * bounds if defined + */ + private final boolean isRepairMode; + + /** Simple constructor. + */ + FitnessFunction() { + isRepairMode = true; + } + + /** + * @param point Normalized objective variables. + * @return the objective value + penalty for violated bounds. + */ + public ValuePenaltyPair value(final double[] point) { + double value; + double penalty=0.0; + if (isRepairMode) { + double[] repaired = repair(point); + value = CMAESOptimizer.this.computeObjectiveValue(repaired); + penalty = penalty(point, repaired); + } else { + value = CMAESOptimizer.this.computeObjectiveValue(point); + } + value = isMinimize ? value : -value; + penalty = isMinimize ? penalty : -penalty; + return new ValuePenaltyPair(value,penalty); + } + + /** + * @param x Normalized objective variables. + * @return {@code true} if in bounds. + */ + public boolean isFeasible(final double[] x) { + final double[] lB = CMAESOptimizer.this.getLowerBound(); + final double[] uB = CMAESOptimizer.this.getUpperBound(); + + for (int i = 0; i < x.length; i++) { + if (x[i] < lB[i]) { + return false; + } + if (x[i] > uB[i]) { + return false; + } + } + return true; + } + + /** + * @param x Normalized objective variables. + * @return the repaired (i.e. all in bounds) objective variables. + */ + private double[] repair(final double[] x) { + final double[] lB = CMAESOptimizer.this.getLowerBound(); + final double[] uB = CMAESOptimizer.this.getUpperBound(); + + final double[] repaired = new double[x.length]; + for (int i = 0; i < x.length; i++) { + if (x[i] < lB[i]) { + repaired[i] = lB[i]; + } else if (x[i] > uB[i]) { + repaired[i] = uB[i]; + } else { + repaired[i] = x[i]; + } + } + return repaired; + } + + /** + * @param x Normalized objective variables. + * @param repaired Repaired objective variables. + * @return Penalty value according to the violation of the bounds. + */ + private double penalty(final double[] x, final double[] repaired) { + double penalty = 0; + for (int i = 0; i < x.length; i++) { + double diff = FastMath.abs(x[i] - repaired[i]); + penalty += diff; + } + return isMinimize ? penalty : -penalty; + } + } + + // -----Matrix utility functions similar to the Matlab build in functions------ + + /** + * @param m Input matrix + * @return Matrix representing the element-wise logarithm of m. + */ + private static RealMatrix log(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = FastMath.log(m.getEntry(r, c)); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Matrix representing the element-wise square root of m. + */ + private static RealMatrix sqrt(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = FastMath.sqrt(m.getEntry(r, c)); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Matrix representing the element-wise square of m. + */ + private static RealMatrix square(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + d[r][c] = e * e; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix 1. + * @param n Input matrix 2. + * @return the matrix where the elements of m and n are element-wise multiplied. + */ + private static RealMatrix times(final RealMatrix m, final RealMatrix n) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = m.getEntry(r, c) * n.getEntry(r, c); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix 1. + * @param n Input matrix 2. + * @return Matrix where the elements of m and n are element-wise divided. + */ + private static RealMatrix divide(final RealMatrix m, final RealMatrix n) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = m.getEntry(r, c) / n.getEntry(r, c); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @param cols Columns to select. + * @return Matrix representing the selected columns. + */ + private static RealMatrix selectColumns(final RealMatrix m, final int[] cols) { + final double[][] d = new double[m.getRowDimension()][cols.length]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < cols.length; c++) { + d[r][c] = m.getEntry(r, cols[c]); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @param k Diagonal position. + * @return Upper triangular part of matrix. + */ + private static RealMatrix triu(final RealMatrix m, int k) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = r <= c - k ? m.getEntry(r, c) : 0; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Row matrix representing the sums of the rows. + */ + private static RealMatrix sumRows(final RealMatrix m) { + final double[][] d = new double[1][m.getColumnDimension()]; + for (int c = 0; c < m.getColumnDimension(); c++) { + double sum = 0; + for (int r = 0; r < m.getRowDimension(); r++) { + sum += m.getEntry(r, c); + } + d[0][c] = sum; + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return the diagonal n-by-n matrix if m is a column matrix or the column + * matrix representing the diagonal if m is a n-by-n matrix. + */ + private static RealMatrix diag(final RealMatrix m) { + if (m.getColumnDimension() == 1) { + final double[][] d = new double[m.getRowDimension()][m.getRowDimension()]; + for (int i = 0; i < m.getRowDimension(); i++) { + d[i][i] = m.getEntry(i, 0); + } + return new Array2DRowRealMatrix(d, false); + } else { + final double[][] d = new double[m.getRowDimension()][1]; + for (int i = 0; i < m.getColumnDimension(); i++) { + d[i][0] = m.getEntry(i, i); + } + return new Array2DRowRealMatrix(d, false); + } + } + + /** + * Copies a column from m1 to m2. + * + * @param m1 Source matrix. + * @param col1 Source column. + * @param m2 Target matrix. + * @param col2 Target column. + */ + private static void copyColumn(final RealMatrix m1, int col1, + RealMatrix m2, int col2) { + for (int i = 0; i < m1.getRowDimension(); i++) { + m2.setEntry(i, col2, m1.getEntry(i, col1)); + } + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix filled with 1. + */ + private static RealMatrix ones(int n, int m) { + final double[][] d = new double[n][m]; + for (int r = 0; r < n; r++) { + Arrays.fill(d[r], 1); + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix of 0 values out of diagonal, and 1 values on + * the diagonal. + */ + private static RealMatrix eye(int n, int m) { + final double[][] d = new double[n][m]; + for (int r = 0; r < n; r++) { + if (r < m) { + d[r][r] = 1; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix of zero values. + */ + private static RealMatrix zeros(int n, int m) { + return new Array2DRowRealMatrix(n, m); + } + + /** + * @param mat Input matrix. + * @param n Number of row replicates. + * @param m Number of column replicates. + * @return a matrix which replicates the input matrix in both directions. + */ + private static RealMatrix repmat(final RealMatrix mat, int n, int m) { + final int rd = mat.getRowDimension(); + final int cd = mat.getColumnDimension(); + final double[][] d = new double[n * rd][m * cd]; + for (int r = 0; r < n * rd; r++) { + for (int c = 0; c < m * cd; c++) { + d[r][c] = mat.getEntry(r % rd, c % cd); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param start Start value. + * @param end End value. + * @param step Step size. + * @return a sequence as column matrix. + */ + private static RealMatrix sequence(double start, double end, double step) { + final int size = (int) ((end - start) / step + 1); + final double[][] d = new double[size][1]; + double value = start; + for (int r = 0; r < size; r++) { + d[r][0] = value; + value += step; + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return the maximum of the matrix element values. + */ + private static double max(final RealMatrix m) { + double max = -Double.MAX_VALUE; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + if (max < e) { + max = e; + } + } + } + return max; + } + + /** + * @param m Input matrix. + * @return the minimum of the matrix element values. + */ + private static double min(final RealMatrix m) { + double min = Double.MAX_VALUE; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + if (min > e) { + min = e; + } + } + } + return min; + } + + /** + * @param m Input array. + * @return the maximum of the array values. + */ + private static double max(final double[] m) { + double max = -Double.MAX_VALUE; + for (int r = 0; r < m.length; r++) { + if (max < m[r]) { + max = m[r]; + } + } + return max; + } + + /** + * @param m Input array. + * @return the minimum of the array values. + */ + private static double min(final double[] m) { + double min = Double.MAX_VALUE; + for (int r = 0; r < m.length; r++) { + if (min > m[r]) { + min = m[r]; + } + } + return min; + } + + /** + * @param indices Input index array. + * @return the inverse of the mapping defined by indices. + */ + private static int[] inverse(final int[] indices) { + final int[] inverse = new int[indices.length]; + for (int i = 0; i < indices.length; i++) { + inverse[indices[i]] = i; + } + return inverse; + } + + /** + * @param indices Input index array. + * @return the indices in inverse order (last is first). + */ + private static int[] reverse(final int[] indices) { + final int[] reverse = new int[indices.length]; + for (int i = 0; i < indices.length; i++) { + reverse[i] = indices[indices.length - i - 1]; + } + return reverse; + } + + /** + * @param size Length of random array. + * @return an array of Gaussian random numbers. + */ + private double[] randn(int size) { + final double[] randn = new double[size]; + for (int i = 0; i < size; i++) { + randn[i] = random.nextGaussian(); + } + return randn; + } + + /** + * @param size Number of rows. + * @param popSize Population size. + * @return a 2-dimensional matrix of Gaussian random numbers. + */ + private RealMatrix randn1(int size, int popSize) { + final double[][] d = new double[size][popSize]; + for (int r = 0; r < size; r++) { + for (int c = 0; c < popSize; c++) { + d[r][c] = random.nextGaussian(); + } + } + return new Array2DRowRealMatrix(d, false); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java new file mode 100644 index 000000000..c9529343f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * This class implements the multi-directional direct search method. + * + * @since 3.0 + */ +public class MultiDirectionalSimplex extends AbstractSimplex { + /** Default value for {@link #khi}: {@value}. */ + private static final double DEFAULT_KHI = 2; + /** Default value for {@link #gamma}: {@value}. */ + private static final double DEFAULT_GAMMA = 0.5; + /** Expansion coefficient. */ + private final double khi; + /** Contraction coefficient. */ + private final double gamma; + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param n Dimension of the simplex. + */ + public MultiDirectionalSimplex(final int n) { + this(n, 1d); + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + */ + public MultiDirectionalSimplex(final int n, double sideLength) { + this(n, sideLength, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final int n, + final double khi, final double gamma) { + this(n, 1d, khi, gamma); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final int n, double sideLength, + final double khi, final double gamma) { + super(n, sideLength); + + this.khi = khi; + this.gamma = gamma; + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + */ + public MultiDirectionalSimplex(final double[] steps) { + this(steps, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + * {@link AbstractSimplex#AbstractSimplex(double[])}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final double[] steps, + final double khi, final double gamma) { + super(steps); + + this.khi = khi; + this.gamma = gamma; + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + */ + public MultiDirectionalSimplex(final double[][] referenceSimplex) { + this(referenceSimplex, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @throws NotStrictlyPositiveException + * if the reference simplex does not contain at least one point. + * @throws DimensionMismatchException + * if there is a dimension mismatch in the reference simplex. + */ + public MultiDirectionalSimplex(final double[][] referenceSimplex, + final double khi, final double gamma) { + super(referenceSimplex); + + this.khi = khi; + this.gamma = gamma; + } + + /** {@inheritDoc} */ + @Override + public void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // Save the original simplex. + final PointValuePair[] original = getPoints(); + final PointValuePair best = original[0]; + + // Perform a reflection step. + final PointValuePair reflected = evaluateNewSimplex(evaluationFunction, + original, 1, comparator); + if (comparator.compare(reflected, best) < 0) { + // Compute the expanded simplex. + final PointValuePair[] reflectedSimplex = getPoints(); + final PointValuePair expanded = evaluateNewSimplex(evaluationFunction, + original, khi, comparator); + if (comparator.compare(reflected, expanded) <= 0) { + // Keep the reflected simplex. + setPoints(reflectedSimplex); + } + // Keep the expanded simplex. + return; + } + + // Compute the contracted simplex. + evaluateNewSimplex(evaluationFunction, original, gamma, comparator); + + } + + /** + * Compute and evaluate a new simplex. + * + * @param evaluationFunction Evaluation function. + * @param original Original simplex (to be preserved). + * @param coeff Linear coefficient. + * @param comparator Comparator to use to sort simplex vertices from best + * to poorest. + * @return the best point in the transformed simplex. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + */ + private PointValuePair evaluateNewSimplex(final MultivariateFunction evaluationFunction, + final PointValuePair[] original, + final double coeff, + final Comparator comparator) { + final double[] xSmallest = original[0].getPointRef(); + // Perform a linear transformation on all the simplex points, + // except the first one. + setPoint(0, original[0]); + final int dim = getDimension(); + for (int i = 1; i < getSize(); i++) { + final double[] xOriginal = original[i].getPointRef(); + final double[] xTransformed = new double[dim]; + for (int j = 0; j < dim; j++) { + xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]); + } + setPoint(i, new PointValuePair(xTransformed, Double.NaN, false)); + } + + // Evaluate the simplex. + evaluate(evaluationFunction, comparator); + + return getPoint(0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java new file mode 100644 index 000000000..0c14a8ff0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; + +/** + * This class implements the Nelder-Mead simplex algorithm. + * + * @since 3.0 + */ +public class NelderMeadSimplex extends AbstractSimplex { + /** Default value for {@link #rho}: {@value}. */ + private static final double DEFAULT_RHO = 1; + /** Default value for {@link #khi}: {@value}. */ + private static final double DEFAULT_KHI = 2; + /** Default value for {@link #gamma}: {@value}. */ + private static final double DEFAULT_GAMMA = 0.5; + /** Default value for {@link #sigma}: {@value}. */ + private static final double DEFAULT_SIGMA = 0.5; + /** Reflection coefficient. */ + private final double rho; + /** Expansion coefficient. */ + private final double khi; + /** Contraction coefficient. */ + private final double gamma; + /** Shrinkage coefficient. */ + private final double sigma; + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param n Dimension of the simplex. + */ + public NelderMeadSimplex(final int n) { + this(n, 1d); + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + */ + public NelderMeadSimplex(final int n, double sideLength) { + this(n, sideLength, + DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + */ + public NelderMeadSimplex(final int n, double sideLength, + final double rho, final double khi, + final double gamma, final double sigma) { + super(n, sideLength); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int)}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + */ + public NelderMeadSimplex(final int n, + final double rho, final double khi, + final double gamma, final double sigma) { + this(n, 1d, rho, khi, gamma, sigma); + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + */ + public NelderMeadSimplex(final double[] steps) { + this(steps, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + * {@link AbstractSimplex#AbstractSimplex(double[])}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + * @throws IllegalArgumentException if one of the steps is zero. + */ + public NelderMeadSimplex(final double[] steps, + final double rho, final double khi, + final double gamma, final double sigma) { + super(steps); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + */ + public NelderMeadSimplex(final double[][] referenceSimplex) { + this(referenceSimplex, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + * @throws NotStrictlyPositiveException + * if the reference simplex does not contain at least one point. + * @throws DimensionMismatchException + * if there is a dimension mismatch in the reference simplex. + */ + public NelderMeadSimplex(final double[][] referenceSimplex, + final double rho, final double khi, + final double gamma, final double sigma) { + super(referenceSimplex); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** {@inheritDoc} */ + @Override + public void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // The simplex has n + 1 points if dimension is n. + final int n = getDimension(); + + // Interesting values. + final PointValuePair best = getPoint(0); + final PointValuePair secondBest = getPoint(n - 1); + final PointValuePair worst = getPoint(n); + final double[] xWorst = worst.getPointRef(); + + // Compute the centroid of the best vertices (dismissing the worst + // point at index n). + final double[] centroid = new double[n]; + for (int i = 0; i < n; i++) { + final double[] x = getPoint(i).getPointRef(); + for (int j = 0; j < n; j++) { + centroid[j] += x[j]; + } + } + final double scaling = 1.0 / n; + for (int j = 0; j < n; j++) { + centroid[j] *= scaling; + } + + // compute the reflection point + final double[] xR = new double[n]; + for (int j = 0; j < n; j++) { + xR[j] = centroid[j] + rho * (centroid[j] - xWorst[j]); + } + final PointValuePair reflected + = new PointValuePair(xR, evaluationFunction.value(xR), false); + + if (comparator.compare(best, reflected) <= 0 && + comparator.compare(reflected, secondBest) < 0) { + // Accept the reflected point. + replaceWorstPoint(reflected, comparator); + } else if (comparator.compare(reflected, best) < 0) { + // Compute the expansion point. + final double[] xE = new double[n]; + for (int j = 0; j < n; j++) { + xE[j] = centroid[j] + khi * (xR[j] - centroid[j]); + } + final PointValuePair expanded + = new PointValuePair(xE, evaluationFunction.value(xE), false); + + if (comparator.compare(expanded, reflected) < 0) { + // Accept the expansion point. + replaceWorstPoint(expanded, comparator); + } else { + // Accept the reflected point. + replaceWorstPoint(reflected, comparator); + } + } else { + if (comparator.compare(reflected, worst) < 0) { + // Perform an outside contraction. + final double[] xC = new double[n]; + for (int j = 0; j < n; j++) { + xC[j] = centroid[j] + gamma * (xR[j] - centroid[j]); + } + final PointValuePair outContracted + = new PointValuePair(xC, evaluationFunction.value(xC), false); + if (comparator.compare(outContracted, reflected) <= 0) { + // Accept the contraction point. + replaceWorstPoint(outContracted, comparator); + return; + } + } else { + // Perform an inside contraction. + final double[] xC = new double[n]; + for (int j = 0; j < n; j++) { + xC[j] = centroid[j] - gamma * (centroid[j] - xWorst[j]); + } + final PointValuePair inContracted + = new PointValuePair(xC, evaluationFunction.value(xC), false); + + if (comparator.compare(inContracted, worst) < 0) { + // Accept the contraction point. + replaceWorstPoint(inContracted, comparator); + return; + } + } + + // Perform a shrink. + final double[] xSmallest = getPoint(0).getPointRef(); + for (int i = 1; i <= n; i++) { + final double[] x = getPoint(i).getPoint(); + for (int j = 0; j < n; j++) { + x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]); + } + setPoint(i, new PointValuePair(x, Double.NaN, false)); + } + evaluate(evaluationFunction, comparator); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java new file mode 100644 index 000000000..748243cbc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.LineSearch; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.univariate.UnivariatePointValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; + +/** + * Powell's algorithm. + * This code is translated and adapted from the Python version of this + * algorithm (as implemented in module {@code optimize.py} v0.5 of + * SciPy). + *
    + * The default stopping criterion is based on the differences of the + * function value between two successive iterations. It is however possible + * to define a custom convergence checker that might terminate the algorithm + * earlier. + *
    + * Line search is performed by the {@link LineSearch} class. + *
    + * Constraints are not supported: the call to + * {@link #optimize(OptimizationData[]) optimize} will throw + * {@link MathUnsupportedOperationException} if bounds are passed to it. + * In order to impose simple constraints, the objective function must be + * wrapped in an adapter like + * {@link MultivariateFunctionMappingAdapter + * MultivariateFunctionMappingAdapter} or + * {@link MultivariateFunctionPenaltyAdapter + * MultivariateFunctionPenaltyAdapter}. + * + * @since 2.2 + */ +public class PowellOptimizer + extends MultivariateOptimizer { + /** + * Minimum relative tolerance. + */ + private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d); + /** + * Relative threshold. + */ + private final double relativeThreshold; + /** + * Absolute threshold. + */ + private final double absoluteThreshold; + /** + * Line search. + */ + private final LineSearch line; + + /** + * This constructor allows to specify a user-defined convergence checker, + * in addition to the parameters that control the default convergence + * checking procedure. + *
    + * The internal line search tolerances are set to the square-root of their + * corresponding value in the multivariate optimizer. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param checker Convergence checker. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs, + ConvergenceChecker checker) { + this(rel, abs, FastMath.sqrt(rel), FastMath.sqrt(abs), checker); + } + + /** + * This constructor allows to specify a user-defined convergence checker, + * in addition to the parameters that control the default convergence + * checking procedure and the line search tolerances. + * + * @param rel Relative threshold for this optimizer. + * @param abs Absolute threshold for this optimizer. + * @param lineRel Relative threshold for the internal line search optimizer. + * @param lineAbs Absolute threshold for the internal line search optimizer. + * @param checker Convergence checker. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs, + double lineRel, + double lineAbs, + ConvergenceChecker checker) { + super(checker); + + if (rel < MIN_RELATIVE_TOLERANCE) { + throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true); + } + if (abs <= 0) { + throw new NotStrictlyPositiveException(abs); + } + relativeThreshold = rel; + absoluteThreshold = abs; + + // Create the line search optimizer. + line = new LineSearch(this, + lineRel, + lineAbs, + 1d); + } + + /** + * The parameters control the default convergence checking procedure. + *
    + * The internal line search tolerances are set to the square-root of their + * corresponding value in the multivariate optimizer. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs) { + this(rel, abs, null); + } + + /** + * Builds an instance with the default convergence checking procedure. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param lineRel Relative threshold for the internal line search optimizer. + * @param lineAbs Absolute threshold for the internal line search optimizer. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs, + double lineRel, + double lineAbs) { + this(rel, abs, lineRel, lineAbs, null); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + checkParameters(); + + final GoalType goal = getGoalType(); + final double[] guess = getStartPoint(); + final int n = guess.length; + + final double[][] direc = new double[n][n]; + for (int i = 0; i < n; i++) { + direc[i][i] = 1; + } + + final ConvergenceChecker checker + = getConvergenceChecker(); + + double[] x = guess; + double fVal = computeObjectiveValue(x); + double[] x1 = x.clone(); + while (true) { + incrementIterationCount(); + + double fX = fVal; + double fX2 = 0; + double delta = 0; + int bigInd = 0; + double alphaMin = 0; + + for (int i = 0; i < n; i++) { + final double[] d = MathArrays.copyOf(direc[i]); + + fX2 = fVal; + + final UnivariatePointValuePair optimum = line.search(x, d); + fVal = optimum.getValue(); + alphaMin = optimum.getPoint(); + final double[][] result = newPointAndDirection(x, d, alphaMin); + x = result[0]; + + if ((fX2 - fVal) > delta) { + delta = fX2 - fVal; + bigInd = i; + } + } + + // Default convergence check. + boolean stop = 2 * (fX - fVal) <= + (relativeThreshold * (FastMath.abs(fX) + FastMath.abs(fVal)) + + absoluteThreshold); + + final PointValuePair previous = new PointValuePair(x1, fX); + final PointValuePair current = new PointValuePair(x, fVal); + if (!stop && checker != null) { // User-defined stopping criteria. + stop = checker.converged(getIterations(), previous, current); + } + if (stop) { + if (goal == GoalType.MINIMIZE) { + return (fVal < fX) ? current : previous; + } else { + return (fVal > fX) ? current : previous; + } + } + + final double[] d = new double[n]; + final double[] x2 = new double[n]; + for (int i = 0; i < n; i++) { + d[i] = x[i] - x1[i]; + x2[i] = 2 * x[i] - x1[i]; + } + + x1 = x.clone(); + fX2 = computeObjectiveValue(x2); + + if (fX > fX2) { + double t = 2 * (fX + fX2 - 2 * fVal); + double temp = fX - fVal - delta; + t *= temp * temp; + temp = fX - fX2; + t -= delta * temp * temp; + + if (t < 0.0) { + final UnivariatePointValuePair optimum = line.search(x, d); + fVal = optimum.getValue(); + alphaMin = optimum.getPoint(); + final double[][] result = newPointAndDirection(x, d, alphaMin); + x = result[0]; + + final int lastInd = n - 1; + direc[bigInd] = direc[lastInd]; + direc[lastInd] = result[1]; + } + } + } + } + + /** + * Compute a new point (in the original space) and a new direction + * vector, resulting from the line search. + * + * @param p Point used in the line search. + * @param d Direction used in the line search. + * @param optimum Optimum found by the line search. + * @return a 2-element array containing the new point (at index 0) and + * the new direction (at index 1). + */ + private double[][] newPointAndDirection(double[] p, + double[] d, + double optimum) { + final int n = p.length; + final double[] nP = new double[n]; + final double[] nD = new double[n]; + for (int i = 0; i < n; i++) { + nD[i] = d[i] * optimum; + nP[i] = p[i] + nD[i]; + } + + final double[][] result = new double[2][]; + result[0] = nP; + result[1] = nD; + + return result; + } + + /** + * @throws MathUnsupportedOperationException if bounds were passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + */ + private void checkParameters() { + if (getLowerBound() != null || + getUpperBound() != null) { + throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java new file mode 100644 index 000000000..2d4c5e40f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointValuePair; +import com.fr.third.org.apache.commons.math3.optim.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * This class implements simplex-based direct search optimization. + * + *

    + * Direct search methods only use objective function values, they do + * not need derivatives and don't either try to compute approximation + * of the derivatives. According to a 1996 paper by Margaret H. Wright + * (Direct + * Search Methods: Once Scorned, Now Respectable), 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 + * not too bad 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. + *

    + *

    + * 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. + *

    + *

    + * The simplex update procedure ({@link NelderMeadSimplex} or + * {@link MultiDirectionalSimplex}) must be passed to the + * {@code optimize} method. + *

    + *

    + * Each call to {@code optimize} will re-use the start configuration of + * the current simplex and move it such that its first vertex is at the + * provided start point of the optimization. + * If the {@code optimize} method is called to solve a different problem + * and the number of parameters change, the simplex must be re-initialized + * to one with the appropriate dimensions. + *

    + *

    + * Convergence is checked by providing the worst points of + * previous and current simplex to the convergence checker, not the best + * ones. + *

    + *

    + * This simplex optimizer implementation does not directly support constrained + * optimization with simple bounds; so, for such optimizations, either a more + * dedicated algorithm must be used like + * {@link CMAESOptimizer} or {@link BOBYQAOptimizer}, or the objective + * function must be wrapped in an adapter like + * {@link MultivariateFunctionMappingAdapter + * MultivariateFunctionMappingAdapter} or + * {@link MultivariateFunctionPenaltyAdapter + * MultivariateFunctionPenaltyAdapter}. + *
    + * The call to {@link #optimize(OptimizationData[]) optimize} will throw + * {@link MathUnsupportedOperationException} if bounds are passed to it. + *

    + * + * @since 3.0 + */ +public class SimplexOptimizer extends MultivariateOptimizer { + /** Simplex update rule. */ + private AbstractSimplex simplex; + + /** + * @param checker Convergence checker. + */ + public SimplexOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * @param rel Relative threshold. + * @param abs Absolute threshold. + */ + public SimplexOptimizer(double rel, double abs) { + this(new SimpleValueChecker(rel, abs)); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * MultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link AbstractSimplex}
    • + *
    + * @return {@inheritDoc} + */ + @Override + public PointValuePair optimize(OptimizationData... optData) { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + checkParameters(); + + // Indirect call to "computeObjectiveValue" in order to update the + // evaluations counter. + final MultivariateFunction evalFunc + = new MultivariateFunction() { + /** {@inheritDoc} */ + public double value(double[] point) { + return computeObjectiveValue(point); + } + }; + + final boolean isMinim = getGoalType() == GoalType.MINIMIZE; + final Comparator comparator + = new Comparator() { + /** {@inheritDoc} */ + public int compare(final PointValuePair o1, + final PointValuePair o2) { + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return isMinim ? Double.compare(v1, v2) : Double.compare(v2, v1); + } + }; + + // Initialize search. + simplex.build(getStartPoint()); + simplex.evaluate(evalFunc, comparator); + + PointValuePair[] previous = null; + int iteration = 0; + final ConvergenceChecker checker = getConvergenceChecker(); + while (true) { + if (getIterations() > 0) { + boolean converged = true; + for (int i = 0; i < simplex.getSize(); i++) { + PointValuePair prev = previous[i]; + converged = converged && + checker.converged(iteration, prev, simplex.getPoint(i)); + } + if (converged) { + // We have found an optimum. + return simplex.getPoint(0); + } + } + + // We still need to search. + previous = simplex.getPoints(); + simplex.iterate(evalFunc, comparator); + + incrementIterationCount(); + } + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link AbstractSimplex}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof AbstractSimplex) { + simplex = (AbstractSimplex) data; + // If more data must be parsed, this statement _must_ be + // changed to "continue". + break; + } + } + } + + /** + * @throws MathUnsupportedOperationException if bounds were passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + * @throws NullArgumentException if no initial simplex was passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + */ + private void checkParameters() { + if (simplex == null) { + throw new NullArgumentException(); + } + if (getLowerBound() != null || + getUpperBound() != null) { + throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java new file mode 100644 index 000000000..dee6779c4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package provides optimization algorithms that do not require derivatives. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.noderiv; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java new file mode 100644 index 000000000..5333da04f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Algorithms for optimizing a scalar function. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.java new file mode 100644 index 000000000..39288d287 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.InitialGuess; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * Base class for implementing optimizers for multivariate vector + * differentiable functions. + * It contains boiler-plate code for dealing with Jacobian evaluation. + * It assumes that the rows of the Jacobian matrix iterate on the model + * functions while the columns iterate on the parameters; thus, the numbers + * of rows is equal to the dimension of the {@link Target} while the + * number of columns is equal to the dimension of the + * {@link InitialGuess InitialGuess}. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public abstract class JacobianMultivariateVectorOptimizer + extends MultivariateVectorOptimizer { + /** + * Jacobian of the model function. + */ + private MultivariateMatrixFunction jacobian; + + /** + * @param checker Convergence checker. + */ + protected JacobianMultivariateVectorOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Computes the Jacobian matrix. + * + * @param params Point at which the Jacobian must be evaluated. + * @return the Jacobian at the specified point. + */ + protected double[][] computeJacobian(final double[] params) { + return jacobian.value(params); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link MultivariateVectorOptimizer#optimize(OptimizationData...)} + * MultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link ModelFunctionJacobian}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + */ + @Override + public PointVectorValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException, + DimensionMismatchException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
      + *
    • {@link ModelFunctionJacobian}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof ModelFunctionJacobian) { + jacobian = ((ModelFunctionJacobian) data).getModelFunctionJacobian(); + // If more data must be parsed, this statement _must_ be + // changed to "continue". + break; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java new file mode 100644 index 000000000..65ee89e3b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Model (vector) function to be optimized. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class ModelFunction implements OptimizationData { + /** Function to be optimized. */ + private final MultivariateVectorFunction model; + + /** + * @param m Model function to be optimized. + */ + public ModelFunction(MultivariateVectorFunction m) { + model = m; + } + + /** + * Gets the model function to be optimized. + * + * @return the model function. + */ + public MultivariateVectorFunction getModelFunction() { + return model; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java new file mode 100644 index 000000000..614156b01 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Jacobian of the model (vector) function to be optimized. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class ModelFunctionJacobian implements OptimizationData { + /** Function to be optimized. */ + private final MultivariateMatrixFunction jacobian; + + /** + * @param j Jacobian of the model function to be optimized. + */ + public ModelFunctionJacobian(MultivariateMatrixFunction j) { + jacobian = j; + } + + /** + * Gets the Jacobian of the model function to be optimized. + * + * @return the model function Jacobian. + */ + public MultivariateMatrixFunction getModelFunctionJacobian() { + return jacobian; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.java new file mode 100644 index 000000000..ed8bd74a8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.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 com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.optim.BaseMultiStartMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * Multi-start optimizer for a (vector) model function. + * + * This class wraps an optimizer in order to use it several times in + * turn with different starting points (trying to avoid being trapped + * in a local extremum when looking for a global one). + * + * @since 3.0 + */ +@Deprecated +public class MultiStartMultivariateVectorOptimizer + extends BaseMultiStartMultivariateOptimizer { + /** Underlying optimizer. */ + private final MultivariateVectorOptimizer optimizer; + /** Found optima. */ + private final List optima = new ArrayList(); + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. + * If {@code starts == 1}, the result will be same as if {@code optimizer} + * is called directly. + * @param generator Random vector generator to use for restarts. + * @throws NullArgumentException if {@code optimizer} or {@code generator} + * is {@code null}. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + public MultiStartMultivariateVectorOptimizer(final MultivariateVectorOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) + throws NullArgumentException, + NotStrictlyPositiveException { + super(optimizer, starts, generator); + this.optimizer = optimizer; + } + + /** + * {@inheritDoc} + */ + @Override + public PointVectorValuePair[] getOptima() { + Collections.sort(optima, getPairComparator()); + return optima.toArray(new PointVectorValuePair[0]); + } + + /** + * {@inheritDoc} + */ + @Override + protected void store(PointVectorValuePair optimum) { + optima.add(optimum); + } + + /** + * {@inheritDoc} + */ + @Override + protected void clear() { + optima.clear(); + } + + /** + * @return a comparator for sorting the optima. + */ + private Comparator getPairComparator() { + return new Comparator() { + /** Observed value to be matched. */ + private final RealVector target = new ArrayRealVector(optimizer.getTarget(), false); + /** Observations weights. */ + private final RealMatrix weight = optimizer.getWeight(); + + /** {@inheritDoc} */ + public int compare(final PointVectorValuePair o1, + final PointVectorValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + return Double.compare(weightedResidual(o1), + weightedResidual(o2)); + } + + private double weightedResidual(final PointVectorValuePair pv) { + final RealVector v = new ArrayRealVector(pv.getValueRef(), false); + final RealVector r = target.subtract(v); + return r.dotProduct(weight.operate(r)); + } + }; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java new file mode 100644 index 000000000..6015ad0a8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * Base class for a multivariate vector function optimizer. + * + * @since 3.1 + */ +@Deprecated +public abstract class MultivariateVectorOptimizer + extends BaseMultivariateOptimizer { + /** Target values for the model function at optimum. */ + private double[] target; + /** Weight matrix. */ + private RealMatrix weightMatrix; + /** Model function. */ + private MultivariateVectorFunction model; + + /** + * @param checker Convergence checker. + */ + protected MultivariateVectorOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Computes the objective function value. + * This method must be called by subclasses to enforce the + * evaluation counter limit. + * + * @param params Point at which the objective function must be evaluated. + * @return the objective function value at the specified point. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * (of the model vector function) is exceeded. + */ + protected double[] computeObjectiveValue(double[] params) { + super.incrementEvaluationCount(); + return model.value(params); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link BaseMultivariateOptimizer#parseOptimizationData(OptimizationData[]) + * BaseMultivariateOptimizer}, this method will register the following data: + *
      + *
    • {@link Target}
    • + *
    • {@link Weight}
    • + *
    • {@link ModelFunction}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + */ + @Override + public PointVectorValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException, + DimensionMismatchException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Gets the weight matrix of the observations. + * + * @return the weight matrix. + */ + public RealMatrix getWeight() { + return weightMatrix.copy(); + } + /** + * Gets the observed values to be matched by the objective vector + * function. + * + * @return the target values. + */ + public double[] getTarget() { + return target.clone(); + } + + /** + * Gets the number of observed values. + * + * @return the length of the target vector. + */ + public int getTargetSize() { + return target.length; + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
      + *
    • {@link Target}
    • + *
    • {@link Weight}
    • + *
    • {@link ModelFunction}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof ModelFunction) { + model = ((ModelFunction) data).getModelFunction(); + continue; + } + if (data instanceof Target) { + target = ((Target) data).getTarget(); + continue; + } + if (data instanceof Weight) { + weightMatrix = ((Weight) data).getWeight(); + continue; + } + } + + // Check input consistency. + checkParameters(); + } + + /** + * Check parameters consistency. + * + * @throws DimensionMismatchException if {@link #target} and + * {@link #weightMatrix} have inconsistent dimensions. + */ + private void checkParameters() { + if (target.length != weightMatrix.getColumnDimension()) { + throw new DimensionMismatchException(target.length, + weightMatrix.getColumnDimension()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Target.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Target.java new file mode 100644 index 000000000..ab35eabe4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Target.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Target of the optimization procedure. + * They are the values which the objective vector function must reproduce + * When the parameters of the model have been optimized. + *
    + * Immutable class. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class Target implements OptimizationData { + /** Target values (of the objective vector function). */ + private final double[] target; + + /** + * @param observations Target values. + */ + public Target(double[] observations) { + target = observations.clone(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public double[] getTarget() { + return target.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Weight.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Weight.java new file mode 100644 index 000000000..ab3f91fac --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/Weight.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; + +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.linear.NonSquareMatrixException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Weight matrix of the residuals between model and observations. + *
    + * Immutable class. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class Weight implements OptimizationData { + /** Weight matrix. */ + private final RealMatrix weightMatrix; + + /** + * Creates a diagonal weight matrix. + * + * @param weight List of the values of the diagonal. + */ + public Weight(double[] weight) { + weightMatrix = new DiagonalMatrix(weight); + } + + /** + * @param weight Weight matrix. + * @throws NonSquareMatrixException if the argument is not + * a square matrix. + */ + public Weight(RealMatrix weight) { + if (weight.getColumnDimension() != weight.getRowDimension()) { + throw new NonSquareMatrixException(weight.getColumnDimension(), + weight.getRowDimension()); + } + + weightMatrix = weight.copy(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public RealMatrix getWeight() { + return weightMatrix.copy(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java new file mode 100644 index 000000000..0ef3303e2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.DecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.Weight; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.JacobianMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Base class for implementing least-squares optimizers. + * It provides methods for error estimation. + * + * @since 3.1 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public abstract class AbstractLeastSquaresOptimizer + extends JacobianMultivariateVectorOptimizer { + /** Square-root of the weight matrix. */ + private RealMatrix weightMatrixSqrt; + /** Cost value (square root of the sum of the residuals). */ + private double cost; + + /** + * @param checker Convergence checker. + */ + protected AbstractLeastSquaresOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Computes the weighted Jacobian matrix. + * + * @param params Model parameters at which to compute the Jacobian. + * @return the weighted Jacobian: W1/2 J. + * @throws DimensionMismatchException if the Jacobian dimension does not + * match problem dimension. + */ + protected RealMatrix computeWeightedJacobian(double[] params) { + return weightMatrixSqrt.multiply(MatrixUtils.createRealMatrix(computeJacobian(params))); + } + + /** + * Computes the cost. + * + * @param residuals Residuals. + * @return the cost. + * @see #computeResiduals(double[]) + */ + protected double computeCost(double[] residuals) { + final ArrayRealVector r = new ArrayRealVector(residuals); + return FastMath.sqrt(r.dotProduct(getWeight().operate(r))); + } + + /** + * Gets the root-mean-square (RMS) value. + * + * The RMS the root of the arithmetic mean of the square of all weighted + * residuals. + * This is related to the criterion that is minimized by the optimizer + * as follows: If c if the criterion, and n is the + * number of measurements, then the RMS is sqrt (c/n). + * + * @return the RMS value. + */ + public double getRMS() { + return FastMath.sqrt(getChiSquare() / getTargetSize()); + } + + /** + * Get a Chi-Square-like value assuming the N residuals follow N + * distinct normal distributions centered on 0 and whose variances are + * the reciprocal of the weights. + * @return chi-square value + */ + public double getChiSquare() { + return cost * cost; + } + + /** + * Gets the square-root of the weight matrix. + * + * @return the square-root of the weight matrix. + */ + public RealMatrix getWeightSquareRoot() { + return weightMatrixSqrt.copy(); + } + + /** + * Sets the cost. + * + * @param cost Cost value. + */ + protected void setCost(double cost) { + this.cost = cost; + } + + /** + * Get the covariance matrix of the optimized parameters. + *
    + * Note that this operation involves the inversion of the + * JTJ matrix, where {@code J} is the + * Jacobian matrix. + * The {@code threshold} parameter is a way for the caller to specify + * that the result of this computation should be considered meaningless, + * and thus trigger an exception. + * + * @param params Model parameters. + * @param threshold Singularity threshold. + * @return the covariance matrix. + * @throws SingularMatrixException + * if the covariance matrix cannot be computed (singular problem). + */ + public double[][] computeCovariances(double[] params, + double threshold) { + // Set up the Jacobian. + final RealMatrix j = computeWeightedJacobian(params); + + // Compute transpose(J)J. + final RealMatrix jTj = j.transpose().multiply(j); + + // Compute the covariances matrix. + final DecompositionSolver solver + = new QRDecomposition(jTj, threshold).getSolver(); + return solver.getInverse().getData(); + } + + /** + * Computes an estimate of the standard deviation of the parameters. The + * returned values are the square root of the diagonal coefficients of the + * covariance matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]} + * is the optimized value of the {@code i}-th parameter, and {@code C} is + * the covariance matrix. + * + * @param params Model parameters. + * @param covarianceSingularityThreshold Singularity threshold (see + * {@link #computeCovariances(double[],double) computeCovariances}). + * @return an estimate of the standard deviation of the optimized parameters + * @throws SingularMatrixException + * if the covariance matrix cannot be computed. + */ + public double[] computeSigma(double[] params, + double covarianceSingularityThreshold) { + final int nC = params.length; + final double[] sig = new double[nC]; + final double[][] cov = computeCovariances(params, covarianceSingularityThreshold); + for (int i = 0; i < nC; ++i) { + sig[i] = FastMath.sqrt(cov[i][i]); + } + return sig; + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link JacobianMultivariateVectorOptimizer#parseOptimizationData(OptimizationData[]) + * JacobianMultivariateVectorOptimizer}, this method will register the following data: + *
      + *
    • {@link Weight}
    • + *
    + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + */ + @Override + public PointVectorValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException { + // Set up base class and perform computation. + return super.optimize(optData); + } + + /** + * Computes the residuals. + * The residual is the difference between the observed (target) + * values and the model (objective function) value. + * There is one residual for each element of the vector-valued + * function. + * + * @param objectiveValue Value of the the objective function. This is + * the value returned from a call to + * {@link #computeObjectiveValue(double[]) computeObjectiveValue} + * (whose array argument contains the model parameters). + * @return the residuals. + * @throws DimensionMismatchException if {@code params} has a wrong + * length. + */ + protected double[] computeResiduals(double[] objectiveValue) { + final double[] target = getTarget(); + if (objectiveValue.length != target.length) { + throw new DimensionMismatchException(target.length, + objectiveValue.length); + } + + final double[] residuals = new double[target.length]; + for (int i = 0; i < target.length; i++) { + residuals[i] = target[i] - objectiveValue[i]; + } + + return residuals; + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * If the weight matrix is specified, the {@link #weightMatrixSqrt} + * field is recomputed. + * + * @param optData Optimization data. The following data will be looked for: + *
      + *
    • {@link Weight}
    • + *
    + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof Weight) { + weightMatrixSqrt = squareRoot(((Weight) data).getWeight()); + // If more data must be parsed, this statement _must_ be + // changed to "continue". + break; + } + } + } + + /** + * Computes the square-root of the weight matrix. + * + * @param m Symmetric, positive-definite (weight) matrix. + * @return the square-root of the weight matrix. + */ + private RealMatrix squareRoot(RealMatrix m) { + if (m instanceof DiagonalMatrix) { + final int dim = m.getRowDimension(); + final RealMatrix sqrtM = new DiagonalMatrix(dim); + for (int i = 0; i < dim; i++) { + sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i))); + } + return sqrtM; + } else { + final EigenDecomposition dec = new EigenDecomposition(m); + return dec.getSquareRoot(); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java new file mode 100644 index 000000000..347dd879a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.DecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.LUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * Gauss-Newton least-squares solver. + *
    + * Constraints are not supported: the call to + * {@link #optimize(OptimizationData[]) optimize} will throw + * {@link MathUnsupportedOperationException} if bounds are passed to it. + * + *

    + * 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. + *

    + * + * @since 2.0 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class GaussNewtonOptimizer extends AbstractLeastSquaresOptimizer { + /** Indicator for using LU decomposition. */ + private final boolean useLU; + + /** + * Simple constructor with default settings. + * The normal equations will be solved using LU decomposition. + * + * @param checker Convergence checker. + */ + public GaussNewtonOptimizer(ConvergenceChecker checker) { + this(true, checker); + } + + /** + * @param useLU If {@code true}, the normal equations will be solved + * using LU decomposition, otherwise they will be solved using QR + * decomposition. + * @param checker Convergence checker. + */ + public GaussNewtonOptimizer(final boolean useLU, + ConvergenceChecker checker) { + super(checker); + this.useLU = useLU; + } + + /** {@inheritDoc} */ + @Override + public PointVectorValuePair doOptimize() { + checkParameters(); + + final ConvergenceChecker checker + = getConvergenceChecker(); + + // Computation will be useless without a checker (see "for-loop"). + if (checker == null) { + throw new NullArgumentException(); + } + + final double[] targetValues = getTarget(); + final int nR = targetValues.length; // Number of observed data. + + final RealMatrix weightMatrix = getWeight(); + // Diagonal of the weight matrix. + final double[] residualsWeights = new double[nR]; + for (int i = 0; i < nR; i++) { + residualsWeights[i] = weightMatrix.getEntry(i, i); + } + + final double[] currentPoint = getStartPoint(); + final int nC = currentPoint.length; + + // iterate until convergence is reached + PointVectorValuePair current = null; + for (boolean converged = false; !converged;) { + incrementIterationCount(); + + // evaluate the objective function and its jacobian + PointVectorValuePair previous = current; + // Value of the objective function at "currentPoint". + final double[] currentObjective = computeObjectiveValue(currentPoint); + final double[] currentResiduals = computeResiduals(currentObjective); + final RealMatrix weightedJacobian = computeWeightedJacobian(currentPoint); + current = new PointVectorValuePair(currentPoint, currentObjective); + + // build the linear problem + final double[] b = new double[nC]; + final double[][] a = new double[nC][nC]; + for (int i = 0; i < nR; ++i) { + + final double[] grad = weightedJacobian.getRow(i); + final double weight = residualsWeights[i]; + final double residual = currentResiduals[i]; + + // compute the normal equation + final double wr = weight * residual; + for (int j = 0; j < nC; ++j) { + b[j] += wr * grad[j]; + } + + // build the contribution matrix for measurement i + for (int k = 0; k < nC; ++k) { + double[] ak = a[k]; + double wgk = weight * grad[k]; + for (int l = 0; l < nC; ++l) { + ak[l] += wgk * grad[l]; + } + } + } + + // Check convergence. + if (previous != null) { + converged = checker.converged(getIterations(), previous, current); + if (converged) { + setCost(computeCost(currentResiduals)); + return current; + } + } + + try { + // solve the linearized least squares problem + RealMatrix mA = new BlockRealMatrix(a); + DecompositionSolver solver = useLU ? + new LUDecomposition(mA).getSolver() : + new QRDecomposition(mA).getSolver(); + final double[] dX = solver.solve(new ArrayRealVector(b, false)).toArray(); + // update the estimated parameters + for (int i = 0; i < nC; ++i) { + currentPoint[i] += dX[i]; + } + } catch (SingularMatrixException e) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM); + } + } + // Must never happen. + throw new MathInternalError(); + } + + /** + * @throws MathUnsupportedOperationException if bounds were passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + */ + private void checkParameters() { + if (getLowerBound() != null || + getUpperBound() != null) { + throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java new file mode 100644 index 000000000..33e2f6243 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java @@ -0,0 +1,962 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optim.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class solves a least-squares problem using the Levenberg-Marquardt + * algorithm. + *
    + * Constraints are not supported: the call to + * {@link #optimize(OptimizationData[]) optimize} will throw + * {@link MathUnsupportedOperationException} if bounds are passed to it. + * + *

    This implementation should 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.

    + * + *

    The resolution engine is a simple translation of the MINPACK lmder 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 Analyse numérique matricielle + * appliquée à l'art de l'ingénieur, Masson 1986.

    + *

    The authors of the original fortran version are: + *

      + *
    • Argonne National Laboratory. MINPACK project. March 1980
    • + *
    • Burton S. Garbow
    • + *
    • Kenneth E. Hillstrom
    • + *
    • Jorge J. More
    • + *
    + * The redistribution policy for MINPACK is available here, for convenience, it + * is reproduced below.

    + * + * + * + * + *
    + * 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. + *
    3. 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.
    4. + *
    5. 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.
    6. + *
    7. 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.
    8. + *
    9. 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.
    10. + *
      + * + * @since 2.0 + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +@Deprecated +public class LevenbergMarquardtOptimizer + extends AbstractLeastSquaresOptimizer { + /** Twice the "epsilon machine". */ + private static final double TWO_EPS = 2 * Precision.EPSILON; + /** Number of solved point. */ + private int solvedCols; + /** Diagonal elements of the R matrix in the Q.R. decomposition. */ + private double[] diagR; + /** Norms of the columns of the jacobian matrix. */ + private double[] jacNorm; + /** Coefficients of the Householder transforms vectors. */ + private double[] beta; + /** Columns permutation array. */ + private int[] permutation; + /** Rank of the jacobian matrix. */ + private int rank; + /** Levenberg-Marquardt parameter. */ + private double lmPar; + /** Parameters evolution direction associated with lmPar. */ + private double[] lmDir; + /** Positive input variable used in determining the initial step bound. */ + private final double initialStepBoundFactor; + /** Desired relative error in the sum of squares. */ + private final double costRelativeTolerance; + /** Desired relative error in the approximate solution parameters. */ + private final double parRelativeTolerance; + /** Desired max cosine on the orthogonality between the function vector + * and the columns of the jacobian. */ + private final double orthoTolerance; + /** Threshold for QR ranking. */ + private final double qrRankingThreshold; + /** Weighted residuals. */ + private double[] weightedResidual; + /** Weighted Jacobian. */ + private double[][] weightedJacobian; + + /** + * Build an optimizer for least squares problems with default values + * for all the tuning parameters (see the {@link + * #LevenbergMarquardtOptimizer(double,double,double,double,double) + * other contructor}. + * The default values for the algorithm settings are: + *
        + *
      • Initial step bound factor: 100
      • + *
      • Cost relative tolerance: 1e-10
      • + *
      • Parameters relative tolerance: 1e-10
      • + *
      • Orthogonality tolerance: 1e-10
      • + *
      • QR ranking threshold: {@link Precision#SAFE_MIN}
      • + *
      + */ + public LevenbergMarquardtOptimizer() { + this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN); + } + + /** + * Constructor that allows the specification of a custom convergence + * checker. + * Note that all the usual convergence checks will be disabled. + * The default values for the algorithm settings are: + *
        + *
      • Initial step bound factor: 100
      • + *
      • Cost relative tolerance: 1e-10
      • + *
      • Parameters relative tolerance: 1e-10
      • + *
      • Orthogonality tolerance: 1e-10
      • + *
      • QR ranking threshold: {@link Precision#SAFE_MIN}
      • + *
      + * + * @param checker Convergence checker. + */ + public LevenbergMarquardtOptimizer(ConvergenceChecker checker) { + this(100, checker, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN); + } + + /** + * Constructor that allows the specification of a custom convergence + * checker, in addition to the standard ones. + * + * @param initialStepBoundFactor Positive input variable used in + * determining the initial step bound. This bound is set to the + * product of initialStepBoundFactor and the euclidean norm of + * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor} + * itself. In most cases factor should lie in the interval + * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value. + * @param checker Convergence checker. + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + * @param threshold Desired threshold for QR ranking. If the squared norm + * of a column vector is smaller or equal to this threshold during QR + * decomposition, it is considered to be a zero vector and hence the rank + * of the matrix is reduced. + */ + public LevenbergMarquardtOptimizer(double initialStepBoundFactor, + ConvergenceChecker checker, + double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance, + double threshold) { + super(checker); + this.initialStepBoundFactor = initialStepBoundFactor; + this.costRelativeTolerance = costRelativeTolerance; + this.parRelativeTolerance = parRelativeTolerance; + this.orthoTolerance = orthoTolerance; + this.qrRankingThreshold = threshold; + } + + /** + * Build an optimizer for least squares problems with default values + * for some of the tuning parameters (see the {@link + * #LevenbergMarquardtOptimizer(double,double,double,double,double) + * other contructor}. + * The default values for the algorithm settings are: + *
        + *
      • Initial step bound factor}: 100
      • + *
      • QR ranking threshold}: {@link Precision#SAFE_MIN}
      • + *
      + * + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + */ + public LevenbergMarquardtOptimizer(double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance) { + this(100, + costRelativeTolerance, parRelativeTolerance, orthoTolerance, + Precision.SAFE_MIN); + } + + /** + * The arguments control the behaviour of the default convergence checking + * procedure. + * Additional criteria can defined through the setting of a {@link + * ConvergenceChecker}. + * + * @param initialStepBoundFactor Positive input variable used in + * determining the initial step bound. This bound is set to the + * product of initialStepBoundFactor and the euclidean norm of + * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor} + * itself. In most cases factor should lie in the interval + * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value. + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + * @param threshold Desired threshold for QR ranking. If the squared norm + * of a column vector is smaller or equal to this threshold during QR + * decomposition, it is considered to be a zero vector and hence the rank + * of the matrix is reduced. + */ + public LevenbergMarquardtOptimizer(double initialStepBoundFactor, + double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance, + double threshold) { + super(null); // No custom convergence criterion. + this.initialStepBoundFactor = initialStepBoundFactor; + this.costRelativeTolerance = costRelativeTolerance; + this.parRelativeTolerance = parRelativeTolerance; + this.orthoTolerance = orthoTolerance; + this.qrRankingThreshold = threshold; + } + + /** {@inheritDoc} */ + @Override + protected PointVectorValuePair doOptimize() { + checkParameters(); + + final int nR = getTarget().length; // Number of observed data. + final double[] currentPoint = getStartPoint(); + final int nC = currentPoint.length; // Number of parameters. + + // arrays shared with the other private methods + solvedCols = FastMath.min(nR, nC); + diagR = new double[nC]; + jacNorm = new double[nC]; + beta = new double[nC]; + permutation = new int[nC]; + lmDir = new double[nC]; + + // local point + double delta = 0; + double xNorm = 0; + double[] diag = new double[nC]; + double[] oldX = new double[nC]; + double[] oldRes = new double[nR]; + double[] oldObj = new double[nR]; + double[] qtf = new double[nR]; + double[] work1 = new double[nC]; + double[] work2 = new double[nC]; + double[] work3 = new double[nC]; + + final RealMatrix weightMatrixSqrt = getWeightSquareRoot(); + + // Evaluate the function at the starting point and calculate its norm. + double[] currentObjective = computeObjectiveValue(currentPoint); + double[] currentResiduals = computeResiduals(currentObjective); + PointVectorValuePair current = new PointVectorValuePair(currentPoint, currentObjective); + double currentCost = computeCost(currentResiduals); + + // Outer loop. + lmPar = 0; + boolean firstIteration = true; + final ConvergenceChecker checker = getConvergenceChecker(); + while (true) { + incrementIterationCount(); + + final PointVectorValuePair previous = current; + + // QR decomposition of the jacobian matrix + qrDecomposition(computeWeightedJacobian(currentPoint)); + + weightedResidual = weightMatrixSqrt.operate(currentResiduals); + for (int i = 0; i < nR; i++) { + qtf[i] = weightedResidual[i]; + } + + // compute Qt.res + qTy(qtf); + + // now we don't need Q anymore, + // so let jacobian contain the R matrix with its diagonal elements + for (int k = 0; k < solvedCols; ++k) { + int pk = permutation[k]; + weightedJacobian[k][pk] = diagR[pk]; + } + + if (firstIteration) { + // scale the point according to the norms of the columns + // of the initial jacobian + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double dk = jacNorm[k]; + if (dk == 0) { + dk = 1.0; + } + double xk = dk * currentPoint[k]; + xNorm += xk * xk; + diag[k] = dk; + } + xNorm = FastMath.sqrt(xNorm); + + // initialize the step bound delta + delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm); + } + + // check orthogonality between function vector and jacobian columns + double maxCosine = 0; + if (currentCost != 0) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = jacNorm[pj]; + if (s != 0) { + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qtf[i]; + } + maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost)); + } + } + } + if (maxCosine <= orthoTolerance) { + // Convergence has been reached. + setCost(currentCost); + return current; + } + + // rescale if necessary + for (int j = 0; j < nC; ++j) { + diag[j] = FastMath.max(diag[j], jacNorm[j]); + } + + // Inner loop. + for (double ratio = 0; ratio < 1.0e-4;) { + + // save the state + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + oldX[pj] = currentPoint[pj]; + } + final double previousCost = currentCost; + double[] tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + tmpVec = currentObjective; + currentObjective = oldObj; + oldObj = tmpVec; + + // determine the Levenberg-Marquardt parameter + determineLMParameter(qtf, delta, diag, work1, work2, work3); + + // compute the new point and the norm of the evolution direction + double lmNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + lmDir[pj] = -lmDir[pj]; + currentPoint[pj] = oldX[pj] + lmDir[pj]; + double s = diag[pj] * lmDir[pj]; + lmNorm += s * s; + } + lmNorm = FastMath.sqrt(lmNorm); + // on the first iteration, adjust the initial step bound. + if (firstIteration) { + delta = FastMath.min(delta, lmNorm); + } + + // Evaluate the function at x + p and calculate its norm. + currentObjective = computeObjectiveValue(currentPoint); + currentResiduals = computeResiduals(currentObjective); + current = new PointVectorValuePair(currentPoint, currentObjective); + currentCost = computeCost(currentResiduals); + + // compute the scaled actual reduction + double actRed = -1.0; + if (0.1 * currentCost < previousCost) { + double r = currentCost / previousCost; + actRed = 1.0 - r * r; + } + + // compute the scaled predicted reduction + // and the scaled directional derivative + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double dirJ = lmDir[pj]; + work1[j] = 0; + for (int i = 0; i <= j; ++i) { + work1[i] += weightedJacobian[i][pj] * dirJ; + } + } + double coeff1 = 0; + for (int j = 0; j < solvedCols; ++j) { + coeff1 += work1[j] * work1[j]; + } + double pc2 = previousCost * previousCost; + coeff1 /= pc2; + double coeff2 = lmPar * lmNorm * lmNorm / pc2; + double preRed = coeff1 + 2 * coeff2; + double dirDer = -(coeff1 + coeff2); + + // ratio of the actual to the predicted reduction + ratio = (preRed == 0) ? 0 : (actRed / preRed); + + // update the step bound + if (ratio <= 0.25) { + double tmp = + (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5; + if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) { + tmp = 0.1; + } + delta = tmp * FastMath.min(delta, 10.0 * lmNorm); + lmPar /= tmp; + } else if ((lmPar == 0) || (ratio >= 0.75)) { + delta = 2 * lmNorm; + lmPar *= 0.5; + } + + // test for successful iteration. + if (ratio >= 1.0e-4) { + // successful iteration, update the norm + firstIteration = false; + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double xK = diag[k] * currentPoint[k]; + xNorm += xK * xK; + } + xNorm = FastMath.sqrt(xNorm); + + // tests for convergence. + if (checker != null && checker.converged(getIterations(), previous, current)) { + setCost(currentCost); + return current; + } + } else { + // failed iteration, reset the previous values + currentCost = previousCost; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + currentPoint[pj] = oldX[pj]; + } + tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + tmpVec = currentObjective; + currentObjective = oldObj; + oldObj = tmpVec; + // Reset "current" to previous values. + current = new PointVectorValuePair(currentPoint, currentObjective); + } + + // Default convergence criteria. + if ((FastMath.abs(actRed) <= costRelativeTolerance && + preRed <= costRelativeTolerance && + ratio <= 2.0) || + delta <= parRelativeTolerance * xNorm) { + setCost(currentCost); + return current; + } + + // tests for termination and stringent tolerances + if (FastMath.abs(actRed) <= TWO_EPS && + preRed <= TWO_EPS && + ratio <= 2.0) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE, + costRelativeTolerance); + } else if (delta <= TWO_EPS * xNorm) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE, + parRelativeTolerance); + } else if (maxCosine <= TWO_EPS) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE, + orthoTolerance); + } + } + } + } + + /** + * Determine the Levenberg-Marquardt parameter. + *

      This implementation is a translation in Java of the MINPACK + * lmpar + * routine.

      + *

      This method sets the lmPar and lmDir attributes.

      + *

      The authors of the original fortran function are:

      + *
        + *
      • Argonne National Laboratory. MINPACK project. March 1980
      • + *
      • Burton S. Garbow
      • + *
      • Kenneth E. Hillstrom
      • + *
      • Jorge J. More
      • + *
      + *

      Luc Maisonobe did the Java translation.

      + * + * @param qy array containing qTy + * @param delta upper bound on the euclidean norm of diagR * lmDir + * @param diag diagonal matrix + * @param work1 work array + * @param work2 work array + * @param work3 work array + */ + private void determineLMParameter(double[] qy, double delta, double[] diag, + double[] work1, double[] work2, double[] work3) { + final int nC = weightedJacobian[0].length; + + // compute and store in x the gauss-newton direction, if the + // jacobian is rank-deficient, obtain a least squares solution + for (int j = 0; j < rank; ++j) { + lmDir[permutation[j]] = qy[j]; + } + for (int j = rank; j < nC; ++j) { + lmDir[permutation[j]] = 0; + } + for (int k = rank - 1; k >= 0; --k) { + int pk = permutation[k]; + double ypk = lmDir[pk] / diagR[pk]; + for (int i = 0; i < k; ++i) { + lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk]; + } + lmDir[pk] = ypk; + } + + // evaluate the function at the origin, and test + // for acceptance of the Gauss-Newton direction + double dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work1[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double fp = dxNorm - delta; + if (fp <= 0.1 * delta) { + lmPar = 0; + return; + } + + // if the jacobian is not rank deficient, the Newton step provides + // a lower bound, parl, for the zero of the function, + // otherwise set this bound to zero + double sum2; + double parl = 0; + if (rank == solvedCols) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] *= diag[pj] / dxNorm; + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i < j; ++i) { + sum += weightedJacobian[i][pj] * work1[permutation[i]]; + } + double s = (work1[pj] - sum) / diagR[pj]; + work1[pj] = s; + sum2 += s * s; + } + parl = fp / (delta * sum2); + } + + // calculate an upper bound, paru, for the zero of the function + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qy[i]; + } + sum /= diag[pj]; + sum2 += sum * sum; + } + double gNorm = FastMath.sqrt(sum2); + double paru = gNorm / delta; + if (paru == 0) { + paru = Precision.SAFE_MIN / FastMath.min(delta, 0.1); + } + + // if the input par lies outside of the interval (parl,paru), + // set par to the closer endpoint + lmPar = FastMath.min(paru, FastMath.max(lmPar, parl)); + if (lmPar == 0) { + lmPar = gNorm / dxNorm; + } + + for (int countdown = 10; countdown >= 0; --countdown) { + + // evaluate the function at the current value of lmPar + if (lmPar == 0) { + lmPar = FastMath.max(Precision.SAFE_MIN, 0.001 * paru); + } + double sPar = FastMath.sqrt(lmPar); + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = sPar * diag[pj]; + } + determineLMDirection(qy, work1, work2, work3); + + dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work3[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double previousFP = fp; + fp = dxNorm - delta; + + // if the function is small enough, accept the current value + // of lmPar, also test for the exceptional cases where parl is zero + if ((FastMath.abs(fp) <= 0.1 * delta) || + ((parl == 0) && (fp <= previousFP) && (previousFP < 0))) { + return; + } + + // compute the Newton correction + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = work3[pj] * diag[pj] / dxNorm; + } + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] /= work2[j]; + double tmp = work1[pj]; + for (int i = j + 1; i < solvedCols; ++i) { + work1[permutation[i]] -= weightedJacobian[i][pj] * tmp; + } + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + double s = work1[permutation[j]]; + sum2 += s * s; + } + double correction = fp / (delta * sum2); + + // depending on the sign of the function, update parl or paru. + if (fp > 0) { + parl = FastMath.max(parl, lmPar); + } else if (fp < 0) { + paru = FastMath.min(paru, lmPar); + } + + // compute an improved estimate for lmPar + lmPar = FastMath.max(parl, lmPar + correction); + + } + } + + /** + * Solve a*x = b and d*x = 0 in the least squares sense. + *

      This implementation is a translation in Java of the MINPACK + * qrsolv + * routine.

      + *

      This method sets the lmDir and lmDiag attributes.

      + *

      The authors of the original fortran function are:

      + *
        + *
      • Argonne National Laboratory. MINPACK project. March 1980
      • + *
      • Burton S. Garbow
      • + *
      • Kenneth E. Hillstrom
      • + *
      • Jorge J. More
      • + *
      + *

      Luc Maisonobe did the Java translation.

      + * + * @param qy array containing qTy + * @param diag diagonal matrix + * @param lmDiag diagonal elements associated with lmDir + * @param work work array + */ + private void determineLMDirection(double[] qy, double[] diag, + double[] lmDiag, double[] work) { + + // copy R and Qty to preserve input and initialize s + // in particular, save the diagonal elements of R in lmDir + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + for (int i = j + 1; i < solvedCols; ++i) { + weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]]; + } + lmDir[j] = diagR[pj]; + work[j] = qy[j]; + } + + // eliminate the diagonal matrix d using a Givens rotation + for (int j = 0; j < solvedCols; ++j) { + + // prepare the row of d to be eliminated, locating the + // diagonal element using p from the Q.R. factorization + int pj = permutation[j]; + double dpj = diag[pj]; + if (dpj != 0) { + Arrays.fill(lmDiag, j + 1, lmDiag.length, 0); + } + lmDiag[j] = dpj; + + // the transformations to eliminate the row of d + // modify only a single element of Qty + // beyond the first n, which is initially zero. + double qtbpj = 0; + for (int k = j; k < solvedCols; ++k) { + int pk = permutation[k]; + + // determine a Givens rotation which eliminates the + // appropriate element in the current row of d + if (lmDiag[k] != 0) { + + final double sin; + final double cos; + double rkk = weightedJacobian[k][pk]; + if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) { + final double cotan = rkk / lmDiag[k]; + sin = 1.0 / FastMath.sqrt(1.0 + cotan * cotan); + cos = sin * cotan; + } else { + final double tan = lmDiag[k] / rkk; + cos = 1.0 / FastMath.sqrt(1.0 + tan * tan); + sin = cos * tan; + } + + // compute the modified diagonal element of R and + // the modified element of (Qty,0) + weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k]; + final double temp = cos * work[k] + sin * qtbpj; + qtbpj = -sin * work[k] + cos * qtbpj; + work[k] = temp; + + // accumulate the tranformation in the row of s + for (int i = k + 1; i < solvedCols; ++i) { + double rik = weightedJacobian[i][pk]; + final double temp2 = cos * rik + sin * lmDiag[i]; + lmDiag[i] = -sin * rik + cos * lmDiag[i]; + weightedJacobian[i][pk] = temp2; + } + } + } + + // store the diagonal element of s and restore + // the corresponding diagonal element of R + lmDiag[j] = weightedJacobian[j][permutation[j]]; + weightedJacobian[j][permutation[j]] = lmDir[j]; + } + + // solve the triangular system for z, if the system is + // singular, then obtain a least squares solution + int nSing = solvedCols; + for (int j = 0; j < solvedCols; ++j) { + if ((lmDiag[j] == 0) && (nSing == solvedCols)) { + nSing = j; + } + if (nSing < solvedCols) { + work[j] = 0; + } + } + if (nSing > 0) { + for (int j = nSing - 1; j >= 0; --j) { + int pj = permutation[j]; + double sum = 0; + for (int i = j + 1; i < nSing; ++i) { + sum += weightedJacobian[i][pj] * work[i]; + } + work[j] = (work[j] - sum) / lmDiag[j]; + } + } + + // permute the components of z back to components of lmDir + for (int j = 0; j < lmDir.length; ++j) { + lmDir[permutation[j]] = work[j]; + } + } + + /** + * Decompose a matrix A as A.P = Q.R using Householder transforms. + *

      As suggested in the P. Lascaux and R. Theodor book + * Analyse numérique matricielle appliquée à + * l'art de l'ingénieur (Masson, 1986), instead of representing + * the Householder transforms with uk unit vectors such that: + *

      +     * Hk = I - 2uk.ukt
      +     * 
      + * we use k non-unit vectors such that: + *
      +     * Hk = I - betakvk.vkt
      +     * 
      + * where vk = ak - alphak ek. + * The betak coefficients are provided upon exit as recomputing + * them from the vk vectors would be costly.

      + *

      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.

      + * + * @param jacobian Weighted Jacobian matrix at the current point. + * @exception ConvergenceException if the decomposition cannot be performed + */ + private void qrDecomposition(RealMatrix jacobian) throws ConvergenceException { + // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J), + // hence the multiplication by -1. + weightedJacobian = jacobian.scalarMultiply(-1).getData(); + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + // initializations + for (int k = 0; k < nC; ++k) { + permutation[k] = k; + double norm2 = 0; + for (int i = 0; i < nR; ++i) { + double akk = weightedJacobian[i][k]; + norm2 += akk * akk; + } + jacNorm[k] = FastMath.sqrt(norm2); + } + + // transform the matrix column after column + for (int k = 0; k < nC; ++k) { + + // select the column with the greatest norm on active components + int nextColumn = -1; + double ak2 = Double.NEGATIVE_INFINITY; + for (int i = k; i < nC; ++i) { + double norm2 = 0; + for (int j = k; j < nR; ++j) { + double aki = weightedJacobian[j][permutation[i]]; + norm2 += aki * aki; + } + if (Double.isInfinite(norm2) || Double.isNaN(norm2)) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN, + nR, nC); + } + if (norm2 > ak2) { + nextColumn = i; + ak2 = norm2; + } + } + if (ak2 <= qrRankingThreshold) { + rank = k; + return; + } + int pk = permutation[nextColumn]; + permutation[nextColumn] = permutation[k]; + permutation[k] = pk; + + // choose alpha such that Hk.u = alpha ek + double akk = weightedJacobian[k][pk]; + double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2); + double betak = 1.0 / (ak2 - akk * alpha); + beta[pk] = betak; + + // transform the current column + diagR[pk] = alpha; + weightedJacobian[k][pk] -= alpha; + + // transform the remaining columns + for (int dk = nC - 1 - k; dk > 0; --dk) { + double gamma = 0; + for (int j = k; j < nR; ++j) { + gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]]; + } + gamma *= betak; + for (int j = k; j < nR; ++j) { + weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk]; + } + } + } + rank = solvedCols; + } + + /** + * Compute the product Qt.y for some Q.R. decomposition. + * + * @param y vector to multiply (will be overwritten with the result) + */ + private void qTy(double[] y) { + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + for (int k = 0; k < nC; ++k) { + int pk = permutation[k]; + double gamma = 0; + for (int i = k; i < nR; ++i) { + gamma += weightedJacobian[i][pk] * y[i]; + } + gamma *= beta[pk]; + for (int i = k; i < nR; ++i) { + y[i] -= gamma * weightedJacobian[i][pk]; + } + } + } + + /** + * @throws MathUnsupportedOperationException if bounds were passed to the + * {@link #optimize(OptimizationData[]) optimize} method. + */ + private void checkParameters() { + if (getLowerBound() != null || + getUpperBound() != null) { + throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java new file mode 100644 index 000000000..bd7bcb014 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package provides optimization algorithms that require derivatives. + * + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/package-info.java new file mode 100644 index 000000000..e02562447 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/nonlinear/vector/package-info.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Algorithms for optimizing a vector function. + * + * @deprecated All classes and interfaces in this package are deprecated. + * The optimizers that were provided here were moved to the + * {@link org.apache.commons.math3.fitting.leastsquares} package + * (cf. MATH-1008). + */ +package com.fr.third.org.apache.commons.math3.optim.nonlinear.vector; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/package-info.java new file mode 100644 index 000000000..12fafea0f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/package-info.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. + */ + +/** + *

      + * Generally, optimizers are algorithms that will either + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType#MINIMIZE minimize} or + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType#MAXIMIZE maximize} + * a scalar function, called the + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunction objective + * function}. + *
      + * For some scalar objective functions the gradient can be computed (analytically + * or numerically). Algorithms that use this knowledge are defined in the + * {@link org.apache.commons.math3.optim.nonlinear.scalar.gradient} package. + * The algorithms that do not need this additional information are located in + * the {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv} package. + *

      + * + *

      + * Some problems are solved more efficiently by algorithms that, instead of an + * objective function, need access to a + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.ModelFunction + * model function}: such a model predicts a set of values which the + * algorithm tries to match with a set of given + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.Target target values}. + * Those algorithms are located in the + * {@link org.apache.commons.math3.optim.nonlinear.vector} package. + *
      + * Algorithms that also require the + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.ModelFunctionJacobian + * Jacobian matrix of the model} are located in the + * {@link org.apache.commons.math3.optim.nonlinear.vector.jacobian} package. + *
      + * The {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.vector.jacobian.AbstractLeastSquaresOptimizer + * non-linear least-squares optimizers} are a specialization of the the latter, + * that minimize the distance (called cost or χ2) + * between model and observations. + *
      + * For cases where the Jacobian cannot be provided, a utility class will + * {@link com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.LeastSquaresConverter + * convert} a (vector) model into a (scalar) objective function. + *

      + * + *

      + * This package provides common functionality for the optimization algorithms. + * Abstract classes ({@link com.fr.third.org.apache.commons.math3.optim.BaseOptimizer} and + * {@link com.fr.third.org.apache.commons.math3.optim.BaseMultivariateOptimizer}) contain + * boiler-plate code for storing {@link com.fr.third.org.apache.commons.math3.optim.MaxEval + * evaluations} and {@link com.fr.third.org.apache.commons.math3.optim.MaxIter iterations} + * counters and a user-defined + * {@link com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker convergence checker}. + *

      + * + *

      + * For each of the optimizer types, there is a special implementation that + * wraps an optimizer instance and provides a "multi-start" feature: it calls + * the underlying optimizer several times with different starting points and + * returns the best optimum found, or all optima if so desired. + * This could be useful to avoid being trapped in a local extremum. + *

      + */ +package com.fr.third.org.apache.commons.math3.optim; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BracketFinder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BracketFinder.java new file mode 100644 index 000000000..9f683e7e4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BracketFinder.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.IntegerSequence; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; + +/** + * Provide an interval that brackets a local optimum of a function. + * This code is based on a Python implementation (from SciPy, + * module {@code optimize.py} v0.5). + * + * @since 2.2 + */ +public class BracketFinder { + /** Tolerance to avoid division by zero. */ + private static final double EPS_MIN = 1e-21; + /** + * Golden section. + */ + private static final double GOLD = 1.618034; + /** + * Factor for expanding the interval. + */ + private final double growLimit; + /** + * Counter for function evaluations. + */ + private IntegerSequence.Incrementor evaluations; + /** + * Lower bound of the bracket. + */ + private double lo; + /** + * Higher bound of the bracket. + */ + private double hi; + /** + * Point inside the bracket. + */ + private double mid; + /** + * Function value at {@link #lo}. + */ + private double fLo; + /** + * Function value at {@link #hi}. + */ + private double fHi; + /** + * Function value at {@link #mid}. + */ + private double fMid; + + /** + * Constructor with default values {@code 100, 500} (see the + * {@link #BracketFinder(double,int) other constructor}). + */ + public BracketFinder() { + this(100, 500); + } + + /** + * Create a bracketing interval finder. + * + * @param growLimit Expanding factor. + * @param maxEvaluations Maximum number of evaluations allowed for finding + * a bracketing interval. + */ + public BracketFinder(double growLimit, + int maxEvaluations) { + if (growLimit <= 0) { + throw new NotStrictlyPositiveException(growLimit); + } + if (maxEvaluations <= 0) { + throw new NotStrictlyPositiveException(maxEvaluations); + } + + this.growLimit = growLimit; + evaluations = IntegerSequence.Incrementor.create().withMaximalCount(maxEvaluations); + } + + /** + * Search new points that bracket a local optimum of the function. + * + * @param func Function whose optimum should be bracketed. + * @param goal {@link GoalType Goal type}. + * @param xA Initial point. + * @param xB Initial point. + * @throws TooManyEvaluationsException if the maximum number of evaluations + * is exceeded. + */ + public void search(UnivariateFunction func, + GoalType goal, + double xA, + double xB) { + evaluations = evaluations.withStart(0); + final boolean isMinim = goal == GoalType.MINIMIZE; + + double fA = eval(func, xA); + double fB = eval(func, xB); + if (isMinim ? + fA < fB : + fA > fB) { + + double tmp = xA; + xA = xB; + xB = tmp; + + tmp = fA; + fA = fB; + fB = tmp; + } + + double xC = xB + GOLD * (xB - xA); + double fC = eval(func, xC); + + while (isMinim ? fC < fB : fC > fB) { + double tmp1 = (xB - xA) * (fB - fC); + double tmp2 = (xB - xC) * (fB - fA); + + double val = tmp2 - tmp1; + double denom = FastMath.abs(val) < EPS_MIN ? 2 * EPS_MIN : 2 * val; + + double w = xB - ((xB - xC) * tmp2 - (xB - xA) * tmp1) / denom; + double wLim = xB + growLimit * (xC - xB); + + double fW; + if ((w - xC) * (xB - w) > 0) { + fW = eval(func, w); + if (isMinim ? + fW < fC : + fW > fC) { + xA = xB; + xB = w; + fA = fB; + fB = fW; + break; + } else if (isMinim ? + fW > fB : + fW < fB) { + xC = w; + fC = fW; + break; + } + w = xC + GOLD * (xC - xB); + fW = eval(func, w); + } else if ((w - wLim) * (wLim - xC) >= 0) { + w = wLim; + fW = eval(func, w); + } else if ((w - wLim) * (xC - w) > 0) { + fW = eval(func, w); + if (isMinim ? + fW < fC : + fW > fC) { + xB = xC; + xC = w; + w = xC + GOLD * (xC - xB); + fB = fC; + fC =fW; + fW = eval(func, w); + } + } else { + w = xC + GOLD * (xC - xB); + fW = eval(func, w); + } + + xA = xB; + fA = fB; + xB = xC; + fB = fC; + xC = w; + fC = fW; + } + + lo = xA; + fLo = fA; + mid = xB; + fMid = fB; + hi = xC; + fHi = fC; + + if (lo > hi) { + double tmp = lo; + lo = hi; + hi = tmp; + + tmp = fLo; + fLo = fHi; + fHi = tmp; + } + } + + /** + * @return the number of evalutations. + */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** + * @return the number of evalutations. + */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** + * @return the lower bound of the bracket. + * @see #getFLo() + */ + public double getLo() { + return lo; + } + + /** + * Get function value at {@link #getLo()}. + * @return function value at {@link #getLo()} + */ + public double getFLo() { + return fLo; + } + + /** + * @return the higher bound of the bracket. + * @see #getFHi() + */ + public double getHi() { + return hi; + } + + /** + * Get function value at {@link #getHi()}. + * @return function value at {@link #getHi()} + */ + public double getFHi() { + return fHi; + } + + /** + * @return a point in the middle of the bracket. + * @see #getFMid() + */ + public double getMid() { + return mid; + } + + /** + * Get function value at {@link #getMid()}. + * @return function value at {@link #getMid()} + */ + public double getFMid() { + return fMid; + } + + /** + * @param f Function. + * @param x Argument. + * @return {@code f(x)} + * @throws TooManyEvaluationsException if the maximal number of evaluations is + * exceeded. + */ + private double eval(UnivariateFunction f, double x) { + try { + evaluations.increment(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return f.value(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BrentOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BrentOptimizer.java new file mode 100644 index 000000000..e6e83a345 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/BrentOptimizer.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; + +/** + * For a function defined on some interval {@code (lo, hi)}, this class + * finds an approximation {@code x} to the point at which the function + * attains its minimum. + * It implements Richard Brent's algorithm (from his book "Algorithms for + * Minimization without Derivatives", p. 79) for finding minima of real + * univariate functions. + *
      + * This code is an adaptation, partly based on the Python code from SciPy + * (module "optimize.py" v0.5); the original algorithm is also modified + *
        + *
      • to use an initial guess provided by the user,
      • + *
      • to ensure that the best point encountered is the one returned.
      • + *
      + * + * @since 2.0 + */ +public class BrentOptimizer extends UnivariateOptimizer { + /** + * Golden section. + */ + private static final double GOLDEN_SECTION = 0.5 * (3 - FastMath.sqrt(5)); + /** + * Minimum relative tolerance. + */ + private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d); + /** + * Relative threshold. + */ + private final double relativeThreshold; + /** + * Absolute threshold. + */ + private final double absoluteThreshold; + + /** + * The arguments are used implement the original stopping criterion + * of Brent's algorithm. + * {@code abs} and {@code rel} define a tolerance + * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than + * 2 macheps and preferably not much less than sqrt(macheps), + * where macheps is the relative machine precision. {@code abs} must + * be positive. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param checker Additional, user-defined, convergence checking + * procedure. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public BrentOptimizer(double rel, + double abs, + ConvergenceChecker checker) { + super(checker); + + if (rel < MIN_RELATIVE_TOLERANCE) { + throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true); + } + if (abs <= 0) { + throw new NotStrictlyPositiveException(abs); + } + + relativeThreshold = rel; + absoluteThreshold = abs; + } + + /** + * The arguments are used for implementing the original stopping criterion + * of Brent's algorithm. + * {@code abs} and {@code rel} define a tolerance + * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than + * 2 macheps and preferably not much less than sqrt(macheps), + * where macheps is the relative machine precision. {@code abs} must + * be positive. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public BrentOptimizer(double rel, + double abs) { + this(rel, abs, null); + } + + /** {@inheritDoc} */ + @Override + protected UnivariatePointValuePair doOptimize() { + final boolean isMinim = getGoalType() == GoalType.MINIMIZE; + final double lo = getMin(); + final double mid = getStartValue(); + final double hi = getMax(); + + // Optional additional convergence criteria. + final ConvergenceChecker checker + = getConvergenceChecker(); + + double a; + double b; + if (lo < hi) { + a = lo; + b = hi; + } else { + a = hi; + b = lo; + } + + double x = mid; + double v = x; + double w = x; + double d = 0; + double e = 0; + double fx = computeObjectiveValue(x); + if (!isMinim) { + fx = -fx; + } + double fv = fx; + double fw = fx; + + UnivariatePointValuePair previous = null; + UnivariatePointValuePair current + = new UnivariatePointValuePair(x, isMinim ? fx : -fx); + // Best point encountered so far (which is the initial guess). + UnivariatePointValuePair best = current; + + while (true) { + final double m = 0.5 * (a + b); + final double tol1 = relativeThreshold * FastMath.abs(x) + absoluteThreshold; + final double tol2 = 2 * tol1; + + // Default stopping criterion. + final boolean stop = FastMath.abs(x - m) <= tol2 - 0.5 * (b - a); + if (!stop) { + double p = 0; + double q = 0; + double r = 0; + double u = 0; + + if (FastMath.abs(e) > tol1) { // Fit parabola. + r = (x - w) * (fx - fv); + q = (x - v) * (fx - fw); + p = (x - v) * q - (x - w) * r; + q = 2 * (q - r); + + if (q > 0) { + p = -p; + } else { + q = -q; + } + + r = e; + e = d; + + if (p > q * (a - x) && + p < q * (b - x) && + FastMath.abs(p) < FastMath.abs(0.5 * q * r)) { + // Parabolic interpolation step. + d = p / q; + u = x + d; + + // f must not be evaluated too close to a or b. + if (u - a < tol2 || b - u < tol2) { + if (x <= m) { + d = tol1; + } else { + d = -tol1; + } + } + } else { + // Golden section step. + if (x < m) { + e = b - x; + } else { + e = a - x; + } + d = GOLDEN_SECTION * e; + } + } else { + // Golden section step. + if (x < m) { + e = b - x; + } else { + e = a - x; + } + d = GOLDEN_SECTION * e; + } + + // Update by at least "tol1". + if (FastMath.abs(d) < tol1) { + if (d >= 0) { + u = x + tol1; + } else { + u = x - tol1; + } + } else { + u = x + d; + } + + double fu = computeObjectiveValue(u); + if (!isMinim) { + fu = -fu; + } + + // User-defined convergence checker. + previous = current; + current = new UnivariatePointValuePair(u, isMinim ? fu : -fu); + best = best(best, + best(previous, + current, + isMinim), + isMinim); + + if (checker != null && checker.converged(getIterations(), previous, current)) { + return best; + } + + // Update a, b, v, w and x. + if (fu <= fx) { + if (u < x) { + b = x; + } else { + a = x; + } + v = w; + fv = fw; + w = x; + fw = fx; + x = u; + fx = fu; + } else { + if (u < x) { + a = u; + } else { + b = u; + } + if (fu <= fw || + Precision.equals(w, x)) { + v = w; + fv = fw; + w = u; + fw = fu; + } else if (fu <= fv || + Precision.equals(v, x) || + Precision.equals(v, w)) { + v = u; + fv = fu; + } + } + } else { // Default termination (Brent's criterion). + return best(best, + best(previous, + current, + isMinim), + isMinim); + } + + incrementIterationCount(); + } + } + + /** + * Selects the best of two points. + * + * @param a Point and value. + * @param b Point and value. + * @param isMinim {@code true} if the selected point must be the one with + * the lowest value. + * @return the best point, or {@code null} if {@code a} and {@code b} are + * both {@code null}. When {@code a} and {@code b} have the same function + * value, {@code a} is returned. + */ + private UnivariatePointValuePair best(UnivariatePointValuePair a, + UnivariatePointValuePair b, + boolean isMinim) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + + if (isMinim) { + return a.getValue() <= b.getValue() ? a : b; + } else { + return a.getValue() >= b.getValue() ? a : b; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.java new file mode 100644 index 000000000..f7c955fae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optim.univariate; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.optim.MaxEval; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Special implementation of the {@link UnivariateOptimizer} interface + * adding multi-start features to an existing optimizer. + *
      + * This class wraps an optimizer in order to use it several times in + * turn with different starting points (trying to avoid being trapped + * in a local extremum when looking for a global one). + * + * @since 3.0 + */ +public class MultiStartUnivariateOptimizer + extends UnivariateOptimizer { + /** Underlying classical optimizer. */ + private final UnivariateOptimizer optimizer; + /** Number of evaluations already performed for all starts. */ + private int totalEvaluations; + /** Number of starts to go. */ + private int starts; + /** Random generator for multi-start. */ + private RandomGenerator generator; + /** Found optima. */ + private UnivariatePointValuePair[] optima; + /** Optimization data. */ + private OptimizationData[] optimData; + /** + * Location in {@link #optimData} where the updated maximum + * number of evaluations will be stored. + */ + private int maxEvalIndex = -1; + /** + * Location in {@link #optimData} where the updated start value + * will be stored. + */ + private int searchIntervalIndex = -1; + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. If {@code starts == 1}, + * the {@code optimize} methods will return the same solution as + * {@code optimizer} would. + * @param generator Random generator to use for restarts. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + public MultiStartUnivariateOptimizer(final UnivariateOptimizer optimizer, + final int starts, + final RandomGenerator generator) { + super(optimizer.getConvergenceChecker()); + + if (starts < 1) { + throw new NotStrictlyPositiveException(starts); + } + + this.optimizer = optimizer; + this.starts = starts; + this.generator = generator; + } + + /** {@inheritDoc} */ + @Override + public int getEvaluations() { + return totalEvaluations; + } + + /** + * Gets all the optima found during the last call to {@code optimize}. + * The optimizer stores all the optima found during a set of + * restarts. The {@code optimize} method returns the best point only. + * This method returns all the points found at the end of each starts, + * including the best one already returned by the {@code optimize} method. + *
      + * The returned array as one element for each start as specified + * in the constructor. It is ordered with the results from the + * runs that did converge first, sorted from best to worst + * objective value (i.e in ascending order if minimizing and in + * descending order if maximizing), followed by {@code null} elements + * corresponding to the runs that did not converge. This means all + * elements will be {@code null} if the {@code optimize} method did throw + * an exception. + * This also means that if the first element is not {@code null}, it is + * the best point found across all starts. + * + * @return an array containing the optima. + * @throws MathIllegalStateException if {@link #optimize(OptimizationData[]) + * optimize} has not been called. + */ + public UnivariatePointValuePair[] getOptima() { + if (optima == null) { + throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET); + } + return optima.clone(); + } + + /** + * {@inheritDoc} + * + * @throws MathIllegalStateException if {@code optData} does not contain an + * instance of {@link MaxEval} or {@link SearchInterval}. + */ + @Override + public UnivariatePointValuePair optimize(OptimizationData... optData) { + // Store arguments in order to pass them to the internal optimizer. + optimData = optData; + // Set up base class and perform computations. + return super.optimize(optData); + } + + /** {@inheritDoc} */ + @Override + protected UnivariatePointValuePair doOptimize() { + // Remove all instances of "MaxEval" and "SearchInterval" from the + // array that will be passed to the internal optimizer. + // The former is to enforce smaller numbers of allowed evaluations + // (according to how many have been used up already), and the latter + // to impose a different start value for each start. + for (int i = 0; i < optimData.length; i++) { + if (optimData[i] instanceof MaxEval) { + optimData[i] = null; + maxEvalIndex = i; + continue; + } + if (optimData[i] instanceof SearchInterval) { + optimData[i] = null; + searchIntervalIndex = i; + continue; + } + } + if (maxEvalIndex == -1) { + throw new MathIllegalStateException(); + } + if (searchIntervalIndex == -1) { + throw new MathIllegalStateException(); + } + + RuntimeException lastException = null; + optima = new UnivariatePointValuePair[starts]; + totalEvaluations = 0; + + final int maxEval = getMaxEvaluations(); + final double min = getMin(); + final double max = getMax(); + final double startValue = getStartValue(); + + // Multi-start loop. + for (int i = 0; i < starts; i++) { + // CHECKSTYLE: stop IllegalCatch + try { + // Decrease number of allowed evaluations. + optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations); + // New start value. + final double s = (i == 0) ? + startValue : + min + generator.nextDouble() * (max - min); + optimData[searchIntervalIndex] = new SearchInterval(min, max, s); + // Optimize. + optima[i] = optimizer.optimize(optimData); + } catch (RuntimeException mue) { + lastException = mue; + optima[i] = null; + } + // CHECKSTYLE: resume IllegalCatch + + totalEvaluations += optimizer.getEvaluations(); + } + + sortPairs(getGoalType()); + + if (optima[0] == null) { + throw lastException; // Cannot be null if starts >= 1. + } + + // Return the point with the best objective function value. + return optima[0]; + } + + /** + * Sort the optima from best to worst, followed by {@code null} elements. + * + * @param goal Goal type. + */ + private void sortPairs(final GoalType goal) { + Arrays.sort(optima, new Comparator() { + /** {@inheritDoc} */ + public int compare(final UnivariatePointValuePair o1, + final UnivariatePointValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return (goal == GoalType.MINIMIZE) ? + Double.compare(v1, v2) : Double.compare(v2, v1); + } + }); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SearchInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SearchInterval.java new file mode 100644 index 000000000..630a1f9c6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SearchInterval.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Search interval and (optional) start value. + *
      + * Immutable class. + * + * @since 3.1 + */ +public class SearchInterval implements OptimizationData { + /** Lower bound. */ + private final double lower; + /** Upper bound. */ + private final double upper; + /** Start value. */ + private final double start; + + /** + * @param lo Lower bound. + * @param hi Upper bound. + * @param init Start value. + * @throws NumberIsTooLargeException if {@code lo >= hi}. + * @throws OutOfRangeException if {@code init < lo} or {@code init > hi}. + */ + public SearchInterval(double lo, + double hi, + double init) { + if (lo >= hi) { + throw new NumberIsTooLargeException(lo, hi, false); + } + if (init < lo || + init > hi) { + throw new OutOfRangeException(init, lo, hi); + } + + lower = lo; + upper = hi; + start = init; + } + + /** + * @param lo Lower bound. + * @param hi Upper bound. + * @throws NumberIsTooLargeException if {@code lo >= hi}. + */ + public SearchInterval(double lo, + double hi) { + this(lo, hi, 0.5 * (lo + hi)); + } + + /** + * Gets the lower bound. + * + * @return the lower bound. + */ + public double getMin() { + return lower; + } + /** + * Gets the upper bound. + * + * @return the upper bound. + */ + public double getMax() { + return upper; + } + /** + * Gets the start value. + * + * @return the start value. + */ + public double getStartValue() { + return start; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.java new file mode 100644 index 000000000..371edda01 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.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 com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.optim.AbstractConvergenceChecker; + +/** + * Simple implementation of the + * {@link ConvergenceChecker} interface + * that uses only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold. + *
      + * The {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair) + * converged} method will also return {@code true} if the number of iterations + * has been set (see {@link #SimpleUnivariateValueChecker(double,double,int) + * this constructor}). + * + * @since 3.1 + */ +public class SimpleUnivariateValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)} + * method will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleUnivariateValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleUnivariateValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several time from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm has converged. + */ + @Override + public boolean converged(final int iteration, + final UnivariatePointValuePair previous, + final UnivariatePointValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double p = previous.getValue(); + final double c = current.getValue(); + final double difference = FastMath.abs(p - c); + final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c)); + return difference <= size * getRelativeThreshold() || + difference <= getAbsoluteThreshold(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java new file mode 100644 index 000000000..7aba2b479 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; + +/** + * Scalar function to be optimized. + * + * @since 3.1 + */ +public class UnivariateObjectiveFunction implements OptimizationData { + /** Function to be optimized. */ + private final UnivariateFunction function; + + /** + * @param f Function to be optimized. + */ + public UnivariateObjectiveFunction(UnivariateFunction f) { + function = f; + } + + /** + * Gets the function to be optimized. + * + * @return the objective function. + */ + public UnivariateFunction getObjectiveFunction() { + return function; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java new file mode 100644 index 000000000..ba98ff81f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optim.BaseOptimizer; +import com.fr.third.org.apache.commons.math3.optim.OptimizationData; +import com.fr.third.org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import com.fr.third.org.apache.commons.math3.optim.ConvergenceChecker; + +/** + * Base class for a univariate scalar function optimizer. + * + * @since 3.1 + */ +public abstract class UnivariateOptimizer + extends BaseOptimizer { + /** Objective function. */ + private UnivariateFunction function; + /** Type of optimization. */ + private GoalType goal; + /** Initial guess. */ + private double start; + /** Lower bound. */ + private double min; + /** Upper bound. */ + private double max; + + /** + * @param checker Convergence checker. + */ + protected UnivariateOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * {@inheritDoc} + * + * @param optData Optimization data. In addition to those documented in + * {@link BaseOptimizer#parseOptimizationData(OptimizationData[]) + * BaseOptimizer}, this method will register the following data: + *
        + *
      • {@link GoalType}
      • + *
      • {@link SearchInterval}
      • + *
      • {@link UnivariateObjectiveFunction}
      • + *
      + * @return {@inheritDoc} + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + */ + @Override + public UnivariatePointValuePair optimize(OptimizationData... optData) + throws TooManyEvaluationsException { + // Perform computation. + return super.optimize(optData); + } + + /** + * @return the optimization type. + */ + public GoalType getGoalType() { + return goal; + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. + * The following data will be looked for: + *
        + *
      • {@link GoalType}
      • + *
      • {@link SearchInterval}
      • + *
      • {@link UnivariateObjectiveFunction}
      • + *
      + */ + @Override + protected void parseOptimizationData(OptimizationData... optData) { + // Allow base class to register its own data. + super.parseOptimizationData(optData); + + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof SearchInterval) { + final SearchInterval interval = (SearchInterval) data; + min = interval.getMin(); + max = interval.getMax(); + start = interval.getStartValue(); + continue; + } + if (data instanceof UnivariateObjectiveFunction) { + function = ((UnivariateObjectiveFunction) data).getObjectiveFunction(); + continue; + } + if (data instanceof GoalType) { + goal = (GoalType) data; + continue; + } + } + } + + /** + * @return the initial guess. + */ + public double getStartValue() { + return start; + } + /** + * @return the lower bounds. + */ + public double getMin() { + return min; + } + /** + * @return the upper bounds. + */ + public double getMax() { + return max; + } + + /** + * Computes the objective function value. + * This method must be called by subclasses to enforce the + * evaluation counter limit. + * + * @param x Point at which the objective function must be evaluated. + * @return the objective function value at the specified point. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + */ + protected double computeObjectiveValue(double x) { + super.incrementEvaluationCount(); + return function.value(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java new file mode 100644 index 000000000..e64bcb6c7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optim.univariate; + +import java.io.Serializable; + +/** + * This class holds a point and the value of an objective function at this + * point. + * This is a simple immutable container. + * + * @since 3.0 + */ +public class UnivariatePointValuePair implements Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = 1003888396256744753L; + /** Point. */ + private final double point; + /** Value of the objective function at the point. */ + private final double value; + + /** + * Build a point/objective function value pair. + * + * @param point Point. + * @param value Value of an objective function at the point + */ + public UnivariatePointValuePair(final double point, + final double value) { + this.point = point; + this.value = value; + } + + /** + * Get the point. + * + * @return the point. + */ + public double getPoint() { + return point; + } + + /** + * Get the value of the objective function. + * + * @return the stored value of the objective function. + */ + public double getValue() { + return value; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/package-info.java new file mode 100644 index 000000000..479edb656 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optim/univariate/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * One-dimensional optimization algorithms. + */ +package com.fr.third.org.apache.commons.math3.optim.univariate; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/AbstractConvergenceChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/AbstractConvergenceChecker.java new file mode 100644 index 000000000..ddab396de --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/AbstractConvergenceChecker.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Base class for all convergence checker implementations. + * + * @param Type of (point, value) pair. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public abstract class AbstractConvergenceChecker + implements ConvergenceChecker { + /** + * Default relative threshold. + * @deprecated in 3.1 (to be removed in 4.0) because this value is too small + * to be useful as a default (cf. MATH-798). + */ + @Deprecated + private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * Precision.EPSILON; + /** + * Default absolute threshold. + * @deprecated in 3.1 (to be removed in 4.0) because this value is too small + * to be useful as a default (cf. MATH-798). + */ + @Deprecated + private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * Precision.SAFE_MIN; + /** + * Relative tolerance threshold. + */ + private final double relativeThreshold; + /** + * Absolute tolerance threshold. + */ + private final double absoluteThreshold; + + /** + * Build an instance with default thresholds. + * @deprecated in 3.1 (to be removed in 4.0). Convergence thresholds are + * problem-dependent. As this class is intended for users who want to set + * their own convergence criterion instead of relying on an algorithm's + * default procedure, they should also set the thresholds appropriately + * (cf. MATH-798). + */ + @Deprecated + public AbstractConvergenceChecker() { + this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD; + this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD; + } + + /** + * Build an instance with a specified thresholds. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public AbstractConvergenceChecker(final double relativeThreshold, + final double absoluteThreshold) { + this.relativeThreshold = relativeThreshold; + this.absoluteThreshold = absoluteThreshold; + } + + /** + * @return the relative threshold. + */ + public double getRelativeThreshold() { + return relativeThreshold; + } + + /** + * @return the absolute threshold. + */ + public double getAbsoluteThreshold() { + return absoluteThreshold; + } + + /** + * {@inheritDoc} + */ + public abstract boolean converged(int iteration, + PAIR previous, + PAIR current); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.java new file mode 100644 index 000000000..57e429e8d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Base class for all implementations of a multi-start optimizer. + * + * This interface is mainly intended to enforce the internal coherence of + * Commons-Math. Users of the API are advised to base their code on + * {@link MultivariateMultiStartOptimizer} or on + * {@link DifferentiableMultivariateMultiStartOptimizer}. + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class BaseMultivariateMultiStartOptimizer + implements BaseMultivariateOptimizer { + /** Underlying classical optimizer. */ + private final BaseMultivariateOptimizer optimizer; + /** Maximal number of evaluations allowed. */ + private int maxEvaluations; + /** Number of evaluations already performed for all starts. */ + private int totalEvaluations; + /** Number of starts to go. */ + private int starts; + /** Random generator for multi-start. */ + private RandomVectorGenerator generator; + /** Found optima. */ + private PointValuePair[] optima; + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. If {@code starts == 1}, + * the {@link #optimize(int,MultivariateFunction,GoalType,double[]) + * optimize} will return the same solution as {@code optimizer} would. + * @param generator Random vector generator to use for restarts. + * @throws NullArgumentException if {@code optimizer} or {@code generator} + * is {@code null}. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + protected BaseMultivariateMultiStartOptimizer(final BaseMultivariateOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + if (optimizer == null || + generator == null) { + throw new NullArgumentException(); + } + if (starts < 1) { + throw new NotStrictlyPositiveException(starts); + } + + this.optimizer = optimizer; + this.starts = starts; + this.generator = generator; + } + + /** + * Get all the optima found during the last call to {@link + * #optimize(int,MultivariateFunction,GoalType,double[]) optimize}. + * The optimizer stores all the optima found during a set of + * restarts. The {@link #optimize(int,MultivariateFunction,GoalType,double[]) + * optimize} method returns the best point only. This method + * returns all the points found at the end of each starts, + * including the best one already returned by the {@link + * #optimize(int,MultivariateFunction,GoalType,double[]) optimize} method. + *
      + * The returned array as one element for each start as specified + * in the constructor. It is ordered with the results from the + * runs that did converge first, sorted from best to worst + * objective value (i.e in ascending order if minimizing and in + * descending order if maximizing), followed by and null elements + * corresponding to the runs that did not converge. This means all + * elements will be null if the {@link #optimize(int,MultivariateFunction,GoalType,double[]) + * optimize} method did throw an exception. + * This also means that if the first element is not {@code null}, it + * is the best point found across all starts. + * + * @return an array containing the optima. + * @throws MathIllegalStateException if {@link + * #optimize(int,MultivariateFunction,GoalType,double[]) optimize} + * has not been called. + */ + public PointValuePair[] getOptima() { + if (optima == null) { + throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET); + } + return optima.clone(); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return maxEvaluations; + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return totalEvaluations; + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return optimizer.getConvergenceChecker(); + } + + /** + * {@inheritDoc} + */ + public PointValuePair optimize(int maxEval, final FUNC f, + final GoalType goal, + double[] startPoint) { + maxEvaluations = maxEval; + RuntimeException lastException = null; + optima = new PointValuePair[starts]; + totalEvaluations = 0; + + // Multi-start loop. + for (int i = 0; i < starts; ++i) { + // CHECKSTYLE: stop IllegalCatch + try { + optima[i] = optimizer.optimize(maxEval - totalEvaluations, f, goal, + i == 0 ? startPoint : generator.nextVector()); + } catch (RuntimeException mue) { + lastException = mue; + optima[i] = null; + } + // CHECKSTYLE: resume IllegalCatch + + totalEvaluations += optimizer.getEvaluations(); + } + + sortPairs(goal); + + if (optima[0] == null) { + throw lastException; // cannot be null if starts >=1 + } + + // Return the found point given the best objective function value. + return optima[0]; + } + + /** + * Sort the optima from best to worst, followed by {@code null} elements. + * + * @param goal Goal type. + */ + private void sortPairs(final GoalType goal) { + Arrays.sort(optima, new Comparator() { + /** {@inheritDoc} */ + public int compare(final PointValuePair o1, + final PointValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return (goal == GoalType.MINIMIZE) ? + Double.compare(v1, v2) : Double.compare(v2, v1); + } + }); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.java new file mode 100644 index 000000000..75bd4a042 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * This interface is mainly intended to enforce the internal coherence of + * Commons-FastMath. Users of the API are advised to base their code on + * the following interfaces: + *
        + *
      • {@link MultivariateOptimizer}
      • + *
      • {@link MultivariateDifferentiableOptimizer}
      • + *
      + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface BaseMultivariateOptimizer + extends BaseOptimizer { + /** + * Optimize an objective function. + * + * @param f Objective function. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param startPoint Start point for optimization. + * @param maxEval Maximum number of function evaluations. + * @return the point/value pair giving the optimal value for objective + * function. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + * @deprecated As of 3.1. In 4.0, it will be replaced by the declaration + * corresponding to this {@link BaseAbstractMultivariateOptimizer#optimize(int,MultivariateFunction,GoalType,OptimizationData[]) method}. + */ + @Deprecated + PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double[] startPoint); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.java new file mode 100644 index 000000000..1c44ccc39 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * This interface is mainly intended to enforce the internal coherence of + * Commons-FastMath. Users of the API are advised to base their code on + * the following interfaces: + *
        + *
      • {@link MultivariateOptimizer}
      • + *
      • {@link MultivariateDifferentiableOptimizer}
      • + *
      + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface BaseMultivariateSimpleBoundsOptimizer + extends BaseMultivariateOptimizer { + /** + * Optimize an objective function. + * + * @param f Objective function. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param startPoint Start point for optimization. + * @param maxEval Maximum number of function evaluations. + * @param lowerBound Lower bound for each of the parameters. + * @param upperBound Upper bound for each of the parameters. + * @return the point/value pair giving the optimal value for objective + * function. + * @throws DimensionMismatchException + * if the array sizes are wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * {@code f}, {@code goalType} or {@code startPoint} is {@code null}. + * @throws NumberIsTooSmallException if any + * of the initial values is less than its lower bound. + * @throws NumberIsTooLargeException if any + * of the initial values is greater than its upper bound. + */ + PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double[] startPoint, + double[] lowerBound, double[] upperBound); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.java new file mode 100644 index 000000000..5911e00cf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Base class for all implementations of a multi-start optimizer. + * + * This interface is mainly intended to enforce the internal coherence of + * Commons-Math. Users of the API are advised to base their code on + * {@link DifferentiableMultivariateVectorMultiStartOptimizer}. + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class BaseMultivariateVectorMultiStartOptimizer + implements BaseMultivariateVectorOptimizer { + /** Underlying classical optimizer. */ + private final BaseMultivariateVectorOptimizer optimizer; + /** Maximal number of evaluations allowed. */ + private int maxEvaluations; + /** Number of evaluations already performed for all starts. */ + private int totalEvaluations; + /** Number of starts to go. */ + private int starts; + /** Random generator for multi-start. */ + private RandomVectorGenerator generator; + /** Found optima. */ + private PointVectorValuePair[] optima; + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. If {@code starts == 1}, + * the {@link #optimize(int,MultivariateVectorFunction,double[],double[],double[]) + * optimize} will return the same solution as {@code optimizer} would. + * @param generator Random vector generator to use for restarts. + * @throws NullArgumentException if {@code optimizer} or {@code generator} + * is {@code null}. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + protected BaseMultivariateVectorMultiStartOptimizer(final BaseMultivariateVectorOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + if (optimizer == null || + generator == null) { + throw new NullArgumentException(); + } + if (starts < 1) { + throw new NotStrictlyPositiveException(starts); + } + + this.optimizer = optimizer; + this.starts = starts; + this.generator = generator; + } + + /** + * Get all the optima found during the last call to {@link + * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize}. + * The optimizer stores all the optima found during a set of + * restarts. The {@link #optimize(int,MultivariateVectorFunction,double[],double[],double[]) + * optimize} method returns the best point only. This method + * returns all the points found at the end of each starts, including + * the best one already returned by the {@link + * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} method. + *
      + * The returned array as one element for each start as specified + * in the constructor. It is ordered with the results from the + * runs that did converge first, sorted from best to worst + * objective value (i.e. in ascending order if minimizing and in + * descending order if maximizing), followed by and null elements + * corresponding to the runs that did not converge. This means all + * elements will be null if the {@link + * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} method did + * throw a {@link ConvergenceException}). This also means that if + * the first element is not {@code null}, it is the best point found + * across all starts. + * + * @return array containing the optima + * @throws MathIllegalStateException if {@link + * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} has not been + * called. + */ + public PointVectorValuePair[] getOptima() { + if (optima == null) { + throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET); + } + return optima.clone(); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return maxEvaluations; + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return totalEvaluations; + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return optimizer.getConvergenceChecker(); + } + + /** + * {@inheritDoc} + */ + public PointVectorValuePair optimize(int maxEval, final FUNC f, + double[] target, double[] weights, + double[] startPoint) { + maxEvaluations = maxEval; + RuntimeException lastException = null; + optima = new PointVectorValuePair[starts]; + totalEvaluations = 0; + + // Multi-start loop. + for (int i = 0; i < starts; ++i) { + + // CHECKSTYLE: stop IllegalCatch + try { + optima[i] = optimizer.optimize(maxEval - totalEvaluations, f, target, weights, + i == 0 ? startPoint : generator.nextVector()); + } catch (ConvergenceException oe) { + optima[i] = null; + } catch (RuntimeException mue) { + lastException = mue; + optima[i] = null; + } + // CHECKSTYLE: resume IllegalCatch + + totalEvaluations += optimizer.getEvaluations(); + } + + sortPairs(target, weights); + + if (optima[0] == null) { + throw lastException; // cannot be null if starts >=1 + } + + // Return the found point given the best objective function value. + return optima[0]; + } + + /** + * Sort the optima from best to worst, followed by {@code null} elements. + * + * @param target Target value for the objective functions at optimum. + * @param weights Weights for the least-squares cost computation. + */ + private void sortPairs(final double[] target, + final double[] weights) { + Arrays.sort(optima, new Comparator() { + /** {@inheritDoc} */ + public int compare(final PointVectorValuePair o1, + final PointVectorValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + return Double.compare(weightedResidual(o1), weightedResidual(o2)); + } + private double weightedResidual(final PointVectorValuePair pv) { + final double[] value = pv.getValueRef(); + double sum = 0; + for (int i = 0; i < value.length; ++i) { + final double ri = value[i] - target[i]; + sum += weights[i] * ri * ri; + } + return sum; + } + }); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.java new file mode 100644 index 000000000..5b62fb7e5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; + +/** + * This interface is mainly intended to enforce the internal coherence of + * Commons-Math. Users of the API are advised to base their code on + * the following interfaces: + *
        + *
      • {@link DifferentiableMultivariateVectorOptimizer}
      • + *
      + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface BaseMultivariateVectorOptimizer + extends BaseOptimizer { + /** + * Optimize an objective function. + * Optimization is considered to be a weighted least-squares minimization. + * The cost function to be minimized is + * ∑weighti(objectivei - targeti)2 + * + * @param f Objective function. + * @param target Target value for the objective functions at optimum. + * @param weight Weights for the least squares cost computation. + * @param startPoint Start point for optimization. + * @return the point/value pair giving the optimal value for objective + * function. + * @param maxEval Maximum number of function evaluations. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + * @deprecated As of 3.1. In 4.0, this will be replaced by the declaration + * corresponding to this {@link BaseAbstractMultivariateVectorOptimizer#optimize(int,MultivariateVectorFunction,OptimizationData[]) method}. + */ + @Deprecated + PointVectorValuePair optimize(int maxEval, FUNC f, double[] target, + double[] weight, double[] startPoint); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseOptimizer.java new file mode 100644 index 000000000..4fcac5f23 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/BaseOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.optimization.univariate.UnivariateOptimizer; + +/** + * This interface is mainly intended to enforce the internal coherence of + * Commons-Math. Users of the API are advised to base their code on + * the following interfaces: + *
        + *
      • {@link MultivariateOptimizer}
      • + *
      • {@link MultivariateDifferentiableOptimizer}
      • + *
      • {@link MultivariateDifferentiableVectorOptimizer}
      • + *
      • {@link UnivariateOptimizer}
      • + *
      + * + * @param Type of the point/objective pair. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface BaseOptimizer { + /** + * Get the maximal number of function evaluations. + * + * @return the maximal number of function evaluations. + */ + int getMaxEvaluations(); + + /** + * Get the number of evaluations of the objective function. + * The number of evaluations corresponds to the last call to the + * {@code optimize} method. It is 0 if the method has not been + * called yet. + * + * @return the number of evaluations of the objective function. + */ + int getEvaluations(); + + /** + * Get the convergence checker. + * + * @return the object used to check for convergence. + */ + ConvergenceChecker getConvergenceChecker(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/ConvergenceChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/ConvergenceChecker.java new file mode 100644 index 000000000..00ff29c46 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/ConvergenceChecker.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 com.fr.third.org.apache.commons.math3.optimization; + +/** + * This interface specifies how to check if an optimization algorithm has + * converged. + *
      + * 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. + *
      + * For convenience, three implementations that fit simple needs are already + * provided: {@link SimpleValueChecker}, {@link SimpleVectorValueChecker} and + * {@link SimplePointChecker}. The first two consider that convergence is + * reached when the objective function value does not change much anymore, it + * does not use the point set at all. + * The third one considers that convergence is reached when the input point + * set does not change much anymore, it does not use objective function value + * at all. + * + * @param Type of the (point, objective value) pair. + * + * @see SimplePointChecker + * @see SimpleValueChecker + * @see SimpleVectorValueChecker + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface ConvergenceChecker { + /** + * Check if the optimization algorithm has converged. + * + * @param iteration Current iteration. + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm is considered to have converged. + */ + boolean converged(int iteration, PAIR previous, PAIR current); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.java new file mode 100644 index 000000000..31b7822e6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateFunction; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Special implementation of the {@link DifferentiableMultivariateOptimizer} + * interface adding multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class DifferentiableMultivariateMultiStartOptimizer + extends BaseMultivariateMultiStartOptimizer + implements DifferentiableMultivariateOptimizer { + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1. + * @param generator Random vector generator to use for restarts. + */ + public DifferentiableMultivariateMultiStartOptimizer(final DifferentiableMultivariateOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer, starts, generator); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.java new file mode 100644 index 000000000..b928ffcb2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateFunction; + +/** + * This interface represents an optimization algorithm for + * {@link DifferentiableMultivariateFunction scalar differentiable objective + * functions}. + * Optimization algorithms find the input point set that either {@link GoalType + * maximize or minimize} an objective function. + * + * @see MultivariateOptimizer + * @see DifferentiableMultivariateVectorOptimizer + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public interface DifferentiableMultivariateOptimizer + extends BaseMultivariateOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.java new file mode 100644 index 000000000..5aee79079 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Special implementation of the {@link DifferentiableMultivariateVectorOptimizer} + * interface addind multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class DifferentiableMultivariateVectorMultiStartOptimizer + extends BaseMultivariateVectorMultiStartOptimizer + implements DifferentiableMultivariateVectorOptimizer { + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1. + * @param generator Random vector generator to use for restarts. + */ + public DifferentiableMultivariateVectorMultiStartOptimizer( + final DifferentiableMultivariateVectorOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer, starts, generator); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.java new file mode 100644 index 000000000..5f522a4b8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction; + +/** + * This interface represents an optimization algorithm for + * {@link DifferentiableMultivariateVectorFunction vectorial differentiable + * objective functions}. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface DifferentiableMultivariateVectorOptimizer + extends BaseMultivariateVectorOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/GoalType.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/GoalType.java new file mode 100644 index 000000000..bef8ef1a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/GoalType.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 com.fr.third.org.apache.commons.math3.optimization; + +import java.io.Serializable; + +/** + * Goal type for an optimization problem. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public enum GoalType implements Serializable { + + /** Maximization goal. */ + MAXIMIZE, + + /** Minimization goal. */ + MINIMIZE + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/InitialGuess.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/InitialGuess.java new file mode 100644 index 000000000..42a14193e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/InitialGuess.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 com.fr.third.org.apache.commons.math3.optimization; + +/** + * Starting point (first guess) of the optimization procedure. + *
      + * Immutable class. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class InitialGuess implements OptimizationData { + /** Initial guess. */ + private final double[] init; + + /** + * @param startPoint Initial guess. + */ + public InitialGuess(double[] startPoint) { + init = startPoint.clone(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public double[] getInitialGuess() { + return init.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/LeastSquaresConverter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/LeastSquaresConverter.java new file mode 100644 index 000000000..dd5c147bc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/LeastSquaresConverter.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** This class converts {@link MultivariateVectorFunction vectorial + * objective functions} to {@link MultivariateFunction scalar objective functions} + * when the goal is to minimize them. + *

      + * 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. + *

      + *

      + * This class computes a possibly weighted squared sum of the residuals, which is + * a scalar value. The residuals are the difference between the theoretical model + * (i.e. the output of the vectorial objective function) and the observations. The + * class implements the {@link MultivariateFunction} interface and can therefore be + * minimized by any optimizer supporting scalar objectives functions.This is one way + * to perform a least square estimation. There are other ways to do this without using + * this converter, as some optimization algorithms directly support vectorial objective + * functions. + *

      + *

      + * This class support combination of residuals with or without weights and correlations. + *

      + * + * @see MultivariateFunction + * @see MultivariateVectorFunction + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ + +@Deprecated +public class LeastSquaresConverter implements MultivariateFunction { + + /** Underlying vectorial function. */ + private final MultivariateVectorFunction function; + + /** Observations to be compared to objective function to compute residuals. */ + private final double[] observations; + + /** Optional weights for the residuals. */ + private final double[] weights; + + /** Optional scaling matrix (weight and correlations) for the residuals. */ + private final RealMatrix scale; + + /** Build a simple converter for uncorrelated residuals with the same weight. + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations) { + this.function = function; + this.observations = observations.clone(); + this.weights = null; + this.scale = null; + } + + /** Build a simple converter for uncorrelated residuals with the specific weights. + *

      + * The scalar objective function value is computed as: + *

      +     * objective = ∑weighti(observationi-objectivei)2
      +     * 
      + *

      + *

      + * 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.012) in the even elements and 1.0/(15.02) in the + * odd elements (i.e. reciprocals of variances). + *

      + *

      + * The array computed by the objective function, the observations array and the + * weights array must have consistent sizes or a {@link DimensionMismatchException} + * will be triggered while computing the scalar objective. + *

      + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + * @param weights weights to apply to the residuals + * @exception DimensionMismatchException if the observations vector and the weights + * vector dimensions do not match (objective function dimension is checked only when + * the {@link #value(double[])} method is called) + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations, final double[] weights) { + if (observations.length != weights.length) { + throw new DimensionMismatchException(observations.length, weights.length); + } + this.function = function; + this.observations = observations.clone(); + this.weights = weights.clone(); + this.scale = null; + } + + /** Build a simple converter for correlated residuals with the specific weights. + *

      + * The scalar objective function value is computed as: + *

      +     * objective = yTy with y = scale×(observation-objective)
      +     * 
      + *

      + *

      + * The array computed by the objective function, the observations array and the + * the scaling matrix must have consistent sizes or a {@link DimensionMismatchException} + * will be triggered while computing the scalar objective. + *

      + * @param function vectorial residuals function to wrap + * @param observations observations to be compared to objective function to compute residuals + * @param scale scaling matrix + * @throws DimensionMismatchException if the observations vector and the scale + * matrix dimensions do not match (objective function dimension is checked only when + * the {@link #value(double[])} method is called) + */ + public LeastSquaresConverter(final MultivariateVectorFunction function, + final double[] observations, final RealMatrix scale) { + if (observations.length != scale.getColumnDimension()) { + throw new DimensionMismatchException(observations.length, scale.getColumnDimension()); + } + this.function = function; + this.observations = observations.clone(); + this.weights = null; + this.scale = scale.copy(); + } + + /** {@inheritDoc} */ + public double value(final double[] point) { + // compute residuals + final double[] residuals = function.value(point); + if (residuals.length != observations.length) { + throw new DimensionMismatchException(residuals.length, observations.length); + } + for (int i = 0; i < residuals.length; ++i) { + residuals[i] -= observations[i]; + } + + // compute sum of squares + double sumSquares = 0; + if (weights != null) { + for (int i = 0; i < residuals.length; ++i) { + final double ri = residuals[i]; + sumSquares += weights[i] * ri * ri; + } + } else if (scale != null) { + for (final double yi : scale.operate(residuals)) { + sumSquares += yi * yi; + } + } else { + for (final double ri : residuals) { + sumSquares += ri * ri; + } + } + + return sumSquares; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.java new file mode 100644 index 000000000..7584a7c08 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Special implementation of the {@link MultivariateDifferentiableOptimizer} + * interface adding multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class MultivariateDifferentiableMultiStartOptimizer + extends BaseMultivariateMultiStartOptimizer + implements MultivariateDifferentiableOptimizer { + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1. + * @param generator Random vector generator to use for restarts. + */ + public MultivariateDifferentiableMultiStartOptimizer(final MultivariateDifferentiableOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer, starts, generator); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.java new file mode 100644 index 000000000..eef567255 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; + +/** + * This interface represents an optimization algorithm for + * {@link MultivariateDifferentiableFunction scalar differentiable objective + * functions}. + * Optimization algorithms find the input point set that either {@link GoalType + * maximize or minimize} an objective function. + * + * @see MultivariateOptimizer + * @see MultivariateDifferentiableVectorOptimizer + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public interface MultivariateDifferentiableOptimizer + extends BaseMultivariateOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.java new file mode 100644 index 000000000..0fa293d9c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Special implementation of the {@link MultivariateDifferentiableVectorOptimizer} + * interface adding multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class MultivariateDifferentiableVectorMultiStartOptimizer + extends BaseMultivariateVectorMultiStartOptimizer + implements MultivariateDifferentiableVectorOptimizer { + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1. + * @param generator Random vector generator to use for restarts. + */ + public MultivariateDifferentiableVectorMultiStartOptimizer( + final MultivariateDifferentiableVectorOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer, starts, generator); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.java new file mode 100644 index 000000000..d8170fe3b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; + +/** + * This interface represents an optimization algorithm for + * {@link MultivariateDifferentiableVectorFunction differentiable vectorial + * objective functions}. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public interface MultivariateDifferentiableVectorOptimizer + extends BaseMultivariateVectorOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.java new file mode 100644 index 000000000..c266b053a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.random.RandomVectorGenerator; + +/** + * Special implementation of the {@link MultivariateOptimizer} interface adding + * multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class MultivariateMultiStartOptimizer + extends BaseMultivariateMultiStartOptimizer + implements MultivariateOptimizer { + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1. + * @param generator Random vector generator to use for restarts. + */ + public MultivariateMultiStartOptimizer(final MultivariateOptimizer optimizer, + final int starts, + final RandomVectorGenerator generator) { + super(optimizer, starts, generator); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateOptimizer.java new file mode 100644 index 000000000..2653bdecd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/MultivariateOptimizer.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * This interface represents an optimization algorithm for {@link MultivariateFunction + * scalar objective functions}. + *

      Optimization algorithms find the input point set that either {@link GoalType + * maximize or minimize} an objective function.

      + * + * @see MultivariateDifferentiableOptimizer + * @see MultivariateDifferentiableVectorOptimizer + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public interface MultivariateOptimizer + extends BaseMultivariateOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/OptimizationData.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/OptimizationData.java new file mode 100644 index 000000000..6b5092b35 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/OptimizationData.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optimization; + +/** + * Marker interface. + * Implementations will provide functionality (optional or required) needed + * by the optimizers, and those will need to check the actual type of the + * arguments and perform the appropriate cast in order to access the data + * they need. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public interface OptimizationData {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointValuePair.java new file mode 100644 index 000000000..e18df9e50 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointValuePair.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 com.fr.third.org.apache.commons.math3.optimization; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * This class holds a point and the value of an objective function at + * that point. + * + * @see PointVectorValuePair + * @see MultivariateFunction + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class PointValuePair extends Pair implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. This instance will store + * a copy of the array, not the array passed as argument. + * @param value Value of the objective function at the point. + */ + public PointValuePair(final double[] point, + final double value) { + this(point, value, true); + } + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. + * @param value Value of the objective function at the point. + * @param copyArray if {@code true}, the input array will be copied, + * otherwise it will be referenced. + */ + public PointValuePair(final double[] point, + final double value, + final boolean copyArray) { + super(copyArray ? ((point == null) ? null : + point.clone()) : + point, + value); + } + + /** + * Gets the point. + * + * @return a copy of the stored point. + */ + public double[] getPoint() { + final double[] p = getKey(); + return p == null ? null : p.clone(); + } + + /** + * Gets a reference to the point. + * + * @return a reference to the internal array storing the point. + */ + public double[] getPointRef() { + return getKey(); + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(getKey(), getValue()); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + /** + * Point coordinates. + * @Serial + */ + private final double[] point; + /** + * Value of the objective function at the point. + * @Serial + */ + private final double value; + + /** Simple constructor. + * @param point Point coordinates. + * @param value Value of the objective function at the point. + */ + DataTransferObject(final double[] point, final double value) { + this.point = point.clone(); + this.value = value; + } + + /** Replace the deserialized data transfer object with a {@link PointValuePair}. + * @return replacement {@link PointValuePair} + */ + private Object readResolve() { + return new PointValuePair(point, value, false); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointVectorValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointVectorValuePair.java new file mode 100644 index 000000000..e1a3216c4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/PointVectorValuePair.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 com.fr.third.org.apache.commons.math3.optimization; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.util.Pair; + +/** + * This class holds a point and the vectorial value of an objective function at + * that point. + * + * @see PointValuePair + * @see MultivariateVectorFunction + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class PointVectorValuePair extends Pair implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + + /** + * Builds a point/objective function value pair. + * + * @param point Point coordinates. This instance will store + * a copy of the array, not the array passed as argument. + * @param value Value of the objective function at the point. + */ + public PointVectorValuePair(final double[] point, + final double[] value) { + this(point, value, true); + } + + /** + * Build a point/objective function value pair. + * + * @param point Point coordinates. + * @param value Value of the objective function at the point. + * @param copyArray if {@code true}, the input arrays will be copied, + * otherwise they will be referenced. + */ + public PointVectorValuePair(final double[] point, + final double[] value, + final boolean copyArray) { + super(copyArray ? + ((point == null) ? null : + point.clone()) : + point, + copyArray ? + ((value == null) ? null : + value.clone()) : + value); + } + + /** + * Gets the point. + * + * @return a copy of the stored point. + */ + public double[] getPoint() { + final double[] p = getKey(); + return p == null ? null : p.clone(); + } + + /** + * Gets a reference to the point. + * + * @return a reference to the internal array storing the point. + */ + public double[] getPointRef() { + return getKey(); + } + + /** + * Gets the value of the objective function. + * + * @return a copy of the stored value of the objective function. + */ + @Override + public double[] getValue() { + final double[] v = super.getValue(); + return v == null ? null : v.clone(); + } + + /** + * Gets a reference to the value of the objective function. + * + * @return a reference to the internal array storing the value of + * the objective function. + */ + public double[] getValueRef() { + return super.getValue(); + } + + /** + * Replace the instance with a data transfer object for serialization. + * @return data transfer object that will be serialized + */ + private Object writeReplace() { + return new DataTransferObject(getKey(), getValue()); + } + + /** Internal class used only for serialization. */ + private static class DataTransferObject implements Serializable { + /** Serializable UID. */ + private static final long serialVersionUID = 20120513L; + /** + * Point coordinates. + * @Serial + */ + private final double[] point; + /** + * Value of the objective function at the point. + * @Serial + */ + private final double[] value; + + /** Simple constructor. + * @param point Point coordinates. + * @param value Value of the objective function at the point. + */ + DataTransferObject(final double[] point, final double[] value) { + this.point = point.clone(); + this.value = value.clone(); + } + + /** Replace the deserialized data transfer object with a {@link PointValuePair}. + * @return replacement {@link PointValuePair} + */ + private Object readResolve() { + return new PointVectorValuePair(point, value, false); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleBounds.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleBounds.java new file mode 100644 index 000000000..1c1c6e835 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleBounds.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 com.fr.third.org.apache.commons.math3.optimization; + +/** + * Simple optimization constraints: lower and upper bounds. + * The valid range of the parameters is an interval that can be infinite + * (in one or both directions). + *
      + * Immutable class. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class SimpleBounds implements OptimizationData { + /** Lower bounds. */ + private final double[] lower; + /** Upper bounds. */ + private final double[] upper; + + /** + * @param lB Lower bounds. + * @param uB Upper bounds. + */ + public SimpleBounds(double[] lB, + double[] uB) { + lower = lB.clone(); + upper = uB.clone(); + } + + /** + * Gets the lower bounds. + * + * @return the initial guess. + */ + public double[] getLower() { + return lower.clone(); + } + /** + * Gets the lower bounds. + * + * @return the initial guess. + */ + public double[] getUpper() { + return upper.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimplePointChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimplePointChecker.java new file mode 100644 index 000000000..52e581af7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimplePointChecker.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Pair; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only point coordinates. + * + * 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. + *
      + * The {@link #converged(int,Pair,Pair) converged} method will also return + * {@code true} if the number of iterations has been set (see + * {@link #SimplePointChecker(double,double,int) this constructor}). + * + * @param Type of the (point, value) pair. + * The type of the "value" part of the pair (not used by this class). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class SimplePointChecker> + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause {@link #converged(int, Pair, Pair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int, Pair, Pair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with default threshold. + * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()} + */ + @Deprecated + public SimplePointChecker() { + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Build an instance with specified thresholds. + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimplePointChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold Relative tolerance threshold. + * @param absoluteThreshold Absolute tolerance threshold. + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimplePointChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several times from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the arguments satify the convergence criterion. + */ + @Override + public boolean converged(final int iteration, + final PAIR previous, + final PAIR current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double[] p = previous.getKey(); + final double[] c = current.getKey(); + for (int i = 0; i < p.length; ++i) { + final double pi = p[i]; + final double ci = c[i]; + final double difference = FastMath.abs(pi - ci); + final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci)); + if (difference > size * getRelativeThreshold() && + difference > getAbsoluteThreshold()) { + return false; + } + } + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleValueChecker.java new file mode 100644 index 000000000..edbf431ec --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleValueChecker.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold. + *
      + * The {@link #converged(int,PointValuePair,PointValuePair) converged} + * method will also return {@code true} if the number of iterations has been set + * (see {@link #SimpleValueChecker(double,double,int) this constructor}). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class SimpleValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,PointValuePair,PointValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,PointValuePair,PointValuePair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with default thresholds. + * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()} + */ + @Deprecated + public SimpleValueChecker() { + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several time from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm has converged. + */ + @Override + public boolean converged(final int iteration, + final PointValuePair previous, + final PointValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double p = previous.getValue(); + final double c = current.getValue(); + final double difference = FastMath.abs(p - c); + final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c)); + return difference <= size * getRelativeThreshold() || + difference <= getAbsoluteThreshold(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleVectorValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleVectorValueChecker.java new file mode 100644 index 000000000..35abebb88 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/SimpleVectorValueChecker.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Simple implementation of the {@link ConvergenceChecker} interface using + * only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold for all vectors elements. + *
      + * The {@link #converged(int,PointVectorValuePair,PointVectorValuePair) converged} + * method will also return {@code true} if the number of iterations has been set + * (see {@link #SimpleVectorValueChecker(double,double,int) this constructor}). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class SimpleVectorValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} method + * will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with default thresholds. + * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()} + */ + @Deprecated + public SimpleVectorValueChecker() { + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleVectorValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified tolerance thresholds and + * iteration count. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold Relative tolerance threshold. + * @param absoluteThreshold Absolute tolerance threshold. + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleVectorValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several times from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the arguments satify the convergence criterion. + */ + @Override + public boolean converged(final int iteration, + final PointVectorValuePair previous, + final PointVectorValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double[] p = previous.getValueRef(); + final double[] c = current.getValueRef(); + for (int i = 0; i < p.length; ++i) { + final double pi = p[i]; + final double ci = c[i]; + final double difference = FastMath.abs(pi - ci); + final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci)); + if (difference > size * getRelativeThreshold() && + difference > getAbsoluteThreshold()) { + return false; + } + } + return true; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Target.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Target.java new file mode 100644 index 000000000..90481a4a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Target.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 com.fr.third.org.apache.commons.math3.optimization; + +/** + * Target of the optimization procedure. + * They are the values which the objective vector function must reproduce + * When the parameters of the model have been optimized. + *
      + * Immutable class. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class Target implements OptimizationData { + /** Target values (of the objective vector function). */ + private final double[] target; + + /** + * @param observations Target values. + */ + public Target(double[] observations) { + target = observations.clone(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public double[] getTarget() { + return target.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Weight.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Weight.java new file mode 100644 index 000000000..ab1737b3b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/Weight.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 com.fr.third.org.apache.commons.math3.optimization; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.linear.NonSquareMatrixException; + +/** + * Weight matrix of the residuals between model and observations. + *
      + * Immutable class. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class Weight implements OptimizationData { + /** Weight matrix. */ + private final RealMatrix weightMatrix; + + /** + * Creates a diagonal weight matrix. + * + * @param weight List of the values of the diagonal. + */ + public Weight(double[] weight) { + weightMatrix = new DiagonalMatrix(weight); + } + + /** + * @param weight Weight matrix. + * @throws NonSquareMatrixException if the argument is not + * a square matrix. + */ + public Weight(RealMatrix weight) { + if (weight.getColumnDimension() != weight.getRowDimension()) { + throw new NonSquareMatrixException(weight.getColumnDimension(), + weight.getRowDimension()); + } + + weightMatrix = weight.copy(); + } + + /** + * Gets the initial guess. + * + * @return the initial guess. + */ + public RealMatrix getWeight() { + return weightMatrix.copy(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/AbstractSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/AbstractSimplex.java new file mode 100644 index 000000000..89db14772 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/AbstractSimplex.java @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; + +/** + * This class implements the simplex concept. + * It is intended to be used in conjunction with {@link SimplexOptimizer}. + *
      + * The initial configuration of the simplex is set by the constructors + * {@link #AbstractSimplex(double[])} or {@link #AbstractSimplex(double[][])}. + * The other {@link #AbstractSimplex(int) constructor} will set all steps + * to 1, thus building a default configuration from a unit hypercube. + *
      + * Users must call the {@link #build(double[]) build} method in order + * to create the data structure that will be acted on by the other methods of + * this class. + * + * @see SimplexOptimizer + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public abstract class AbstractSimplex implements OptimizationData { + /** Simplex. */ + private PointValuePair[] simplex; + /** Start simplex configuration. */ + private double[][] startConfiguration; + /** Simplex dimension (must be equal to {@code simplex.length - 1}). */ + private final int dimension; + + /** + * Build a unit hypercube simplex. + * + * @param n Dimension of the simplex. + */ + protected AbstractSimplex(int n) { + this(n, 1d); + } + + /** + * Build a hypercube simplex with the given side length. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the hypercube. + */ + protected AbstractSimplex(int n, + double sideLength) { + this(createHypercubeSteps(n, sideLength)); + } + + /** + * The start configuration for simplex is built from a box parallel to + * the canonical axes of the space. The simplex is the subset of vertices + * of a box parallel to the canonical axes. It is built as the path followed + * while traveling from one vertex of the box to the diagonally opposite + * vertex moving only along the box edges. The first vertex of the box will + * be located at the start point of the optimization. + * As an example, in dimension 3 a simplex has 4 vertices. Setting the + * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the + * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }. + * The first vertex would be set to the start point at (1, 1, 1) and the + * last vertex would be set to the diagonally opposite vertex at (2, 11, 3). + * + * @param steps Steps along the canonical axes representing box edges. They + * may be negative but not zero. + * @throws NullArgumentException if {@code steps} is {@code null}. + * @throws ZeroException if one of the steps is zero. + */ + protected AbstractSimplex(final double[] steps) { + if (steps == null) { + throw new NullArgumentException(); + } + if (steps.length == 0) { + throw new ZeroException(); + } + dimension = steps.length; + + // Only the relative position of the n final vertices with respect + // to the first one are stored. + startConfiguration = new double[dimension][dimension]; + for (int i = 0; i < dimension; i++) { + final double[] vertexI = startConfiguration[i]; + for (int j = 0; j < i + 1; j++) { + if (steps[j] == 0) { + throw new ZeroException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX); + } + System.arraycopy(steps, 0, vertexI, 0, j + 1); + } + } + } + + /** + * The real initial simplex will be set up by moving the reference + * simplex such that its first point is located at the start point of the + * optimization. + * + * @param referenceSimplex Reference simplex. + * @throws NotStrictlyPositiveException if the reference simplex does not + * contain at least one point. + * @throws DimensionMismatchException if there is a dimension mismatch + * in the reference simplex. + * @throws IllegalArgumentException if one of its vertices is duplicated. + */ + protected AbstractSimplex(final double[][] referenceSimplex) { + if (referenceSimplex.length <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.SIMPLEX_NEED_ONE_POINT, + referenceSimplex.length); + } + dimension = referenceSimplex.length - 1; + + // Only the relative position of the n final vertices with respect + // to the first one are stored. + startConfiguration = new double[dimension][dimension]; + final double[] ref0 = referenceSimplex[0]; + + // Loop over vertices. + for (int i = 0; i < referenceSimplex.length; i++) { + final double[] refI = referenceSimplex[i]; + + // Safety checks. + if (refI.length != dimension) { + throw new DimensionMismatchException(refI.length, dimension); + } + for (int j = 0; j < i; j++) { + final double[] refJ = referenceSimplex[j]; + boolean allEquals = true; + for (int k = 0; k < dimension; k++) { + if (refI[k] != refJ[k]) { + allEquals = false; + break; + } + } + if (allEquals) { + throw new MathIllegalArgumentException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, + i, j); + } + } + + // Store vertex i position relative to vertex 0 position. + if (i > 0) { + final double[] confI = startConfiguration[i - 1]; + for (int k = 0; k < dimension; k++) { + confI[k] = refI[k] - ref0[k]; + } + } + } + } + + /** + * Get simplex dimension. + * + * @return the dimension of the simplex. + */ + public int getDimension() { + return dimension; + } + + /** + * Get simplex size. + * After calling the {@link #build(double[]) build} method, this method will + * will be equivalent to {@code getDimension() + 1}. + * + * @return the size of the simplex. + */ + public int getSize() { + return simplex.length; + } + + /** + * Compute the next simplex of the algorithm. + * + * @param evaluationFunction Evaluation function. + * @param comparator Comparator to use to sort simplex vertices from best + * to worst. + * @throws TooManyEvaluationsException + * if the algorithm fails to converge. + */ + public abstract void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator); + + /** + * Build an initial simplex. + * + * @param startPoint First point of the simplex. + * @throws DimensionMismatchException if the start point does not match + * simplex dimension. + */ + public void build(final double[] startPoint) { + if (dimension != startPoint.length) { + throw new DimensionMismatchException(dimension, startPoint.length); + } + + // Set first vertex. + simplex = new PointValuePair[dimension + 1]; + simplex[0] = new PointValuePair(startPoint, Double.NaN); + + // Set remaining vertices. + for (int i = 0; i < dimension; i++) { + final double[] confI = startConfiguration[i]; + final double[] vertexI = new double[dimension]; + for (int k = 0; k < dimension; k++) { + vertexI[k] = startPoint[k] + confI[k]; + } + simplex[i + 1] = new PointValuePair(vertexI, Double.NaN); + } + } + + /** + * Evaluate all the non-evaluated points of the simplex. + * + * @param evaluationFunction Evaluation function. + * @param comparator Comparator to use to sort simplex vertices from best to worst. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + */ + public void evaluate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // Evaluate the objective function at all non-evaluated simplex points. + for (int i = 0; i < simplex.length; i++) { + final PointValuePair vertex = simplex[i]; + final double[] point = vertex.getPointRef(); + if (Double.isNaN(vertex.getValue())) { + simplex[i] = new PointValuePair(point, evaluationFunction.value(point), false); + } + } + + // Sort the simplex from best to worst. + Arrays.sort(simplex, comparator); + } + + /** + * Replace the worst point of the simplex by a new point. + * + * @param pointValuePair Point to insert. + * @param comparator Comparator to use for sorting the simplex vertices + * from best to worst. + */ + protected void replaceWorstPoint(PointValuePair pointValuePair, + final Comparator comparator) { + for (int i = 0; i < dimension; i++) { + if (comparator.compare(simplex[i], pointValuePair) > 0) { + PointValuePair tmp = simplex[i]; + simplex[i] = pointValuePair; + pointValuePair = tmp; + } + } + simplex[dimension] = pointValuePair; + } + + /** + * Get the points of the simplex. + * + * @return all the simplex points. + */ + public PointValuePair[] getPoints() { + final PointValuePair[] copy = new PointValuePair[simplex.length]; + System.arraycopy(simplex, 0, copy, 0, simplex.length); + return copy; + } + + /** + * Get the simplex point stored at the requested {@code index}. + * + * @param index Location. + * @return the point at location {@code index}. + */ + public PointValuePair getPoint(int index) { + if (index < 0 || + index >= simplex.length) { + throw new OutOfRangeException(index, 0, simplex.length - 1); + } + return simplex[index]; + } + + /** + * Store a new point at location {@code index}. + * Note that no deep-copy of {@code point} is performed. + * + * @param index Location. + * @param point New value. + */ + protected void setPoint(int index, PointValuePair point) { + if (index < 0 || + index >= simplex.length) { + throw new OutOfRangeException(index, 0, simplex.length - 1); + } + simplex[index] = point; + } + + /** + * Replace all points. + * Note that no deep-copy of {@code points} is performed. + * + * @param points New Points. + */ + protected void setPoints(PointValuePair[] points) { + if (points.length != simplex.length) { + throw new DimensionMismatchException(points.length, simplex.length); + } + simplex = points; + } + + /** + * Create steps for a unit hypercube. + * + * @param n Dimension of the hypercube. + * @param sideLength Length of the sides of the hypercube. + * @return the steps. + */ + private static double[] createHypercubeSteps(int n, + double sideLength) { + final double[] steps = new double[n]; + for (int i = 0; i < n; i++) { + steps[i] = sideLength; + } + return steps; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java new file mode 100644 index 000000000..106f8096c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java @@ -0,0 +1,2480 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// CHECKSTYLE: stop all +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Powell's BOBYQA algorithm. This implementation is translated and + * adapted from the Fortran version available + * here. + * See + * this paper for an introduction. + *
      + * BOBYQA is particularly well suited for high dimensional problems + * where derivatives are not available. In most cases it outperforms the + * {@link PowellOptimizer} significantly. Stochastic algorithms like + * {@link CMAESOptimizer} succeed more often than BOBYQA, but are more + * expensive. BOBYQA could also be considered as a replacement of any + * derivative-based optimizer when the derivatives are approximated by + * finite differences. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class BOBYQAOptimizer + extends BaseAbstractMultivariateSimpleBoundsOptimizer + implements MultivariateOptimizer { + /** Minimum dimension of the problem: {@value} */ + public static final int MINIMUM_PROBLEM_DIMENSION = 2; + /** Default value for {@link #initialTrustRegionRadius}: {@value} . */ + public static final double DEFAULT_INITIAL_RADIUS = 10.0; + /** Default value for {@link #stoppingTrustRegionRadius}: {@value} . */ + public static final double DEFAULT_STOPPING_RADIUS = 1E-8; + /** Constant 0. */ + private static final double ZERO = 0d; + /** Constant 1. */ + private static final double ONE = 1d; + /** Constant 2. */ + private static final double TWO = 2d; + /** Constant 10. */ + private static final double TEN = 10d; + /** Constant 16. */ + private static final double SIXTEEN = 16d; + /** Constant 250. */ + private static final double TWO_HUNDRED_FIFTY = 250d; + /** Constant -1. */ + private static final double MINUS_ONE = -ONE; + /** Constant 1/2. */ + private static final double HALF = ONE / 2; + /** Constant 1/4. */ + private static final double ONE_OVER_FOUR = ONE / 4; + /** Constant 1/8. */ + private static final double ONE_OVER_EIGHT = ONE / 8; + /** Constant 1/10. */ + private static final double ONE_OVER_TEN = ONE / 10; + /** Constant 1/1000. */ + private static final double ONE_OVER_A_THOUSAND = ONE / 1000; + + /** + * numberOfInterpolationPoints XXX + */ + private final int numberOfInterpolationPoints; + /** + * initialTrustRegionRadius XXX + */ + private double initialTrustRegionRadius; + /** + * stoppingTrustRegionRadius XXX + */ + private final double stoppingTrustRegionRadius; + /** Goal type (minimize or maximize). */ + private boolean isMinimize; + /** + * Current best values for the variables to be optimized. + * The vector will be changed in-place to contain the values of the least + * calculated objective function values. + */ + private ArrayRealVector currentBest; + /** Differences between the upper and lower bounds. */ + private double[] boundDifference; + /** + * Index of the interpolation point at the trust region center. + */ + private int trustRegionCenterInterpolationPointIndex; + /** + * Last n columns of matrix H (where n is the dimension + * of the problem). + * XXX "bmat" in the original code. + */ + private Array2DRowRealMatrix bMatrix; + /** + * Factorization of the leading npt square submatrix of H, this + * factorization being Z ZT, which provides both the correct + * rank and positive semi-definiteness. + * XXX "zmat" in the original code. + */ + private Array2DRowRealMatrix zMatrix; + /** + * Coordinates of the interpolation points relative to {@link #originShift}. + * XXX "xpt" in the original code. + */ + private Array2DRowRealMatrix interpolationPoints; + /** + * Shift of origin that should reduce the contributions from rounding + * errors to values of the model and Lagrange functions. + * XXX "xbase" in the original code. + */ + private ArrayRealVector originShift; + /** + * Values of the objective function at the interpolation points. + * XXX "fval" in the original code. + */ + private ArrayRealVector fAtInterpolationPoints; + /** + * Displacement from {@link #originShift} of the trust region center. + * XXX "xopt" in the original code. + */ + private ArrayRealVector trustRegionCenterOffset; + /** + * Gradient of the quadratic model at {@link #originShift} + + * {@link #trustRegionCenterOffset}. + * XXX "gopt" in the original code. + */ + private ArrayRealVector gradientAtTrustRegionCenter; + /** + * Differences {@link #getLowerBound()} - {@link #originShift}. + * All the components of every {@link #trustRegionCenterOffset} are going + * to satisfy the bounds
      + * {@link #getLowerBound() lowerBound}i ≤ + * {@link #trustRegionCenterOffset}i,
      + * with appropriate equalities when {@link #trustRegionCenterOffset} is + * on a constraint boundary. + * XXX "sl" in the original code. + */ + private ArrayRealVector lowerDifference; + /** + * Differences {@link #getUpperBound()} - {@link #originShift} + * All the components of every {@link #trustRegionCenterOffset} are going + * to satisfy the bounds
      + * {@link #trustRegionCenterOffset}i ≤ + * {@link #getUpperBound() upperBound}i,
      + * with appropriate equalities when {@link #trustRegionCenterOffset} is + * on a constraint boundary. + * XXX "su" in the original code. + */ + private ArrayRealVector upperDifference; + /** + * Parameters of the implicit second derivatives of the quadratic model. + * XXX "pq" in the original code. + */ + private ArrayRealVector modelSecondDerivativesParameters; + /** + * Point chosen by function {@link #trsbox(double,ArrayRealVector, + * ArrayRealVector, ArrayRealVector,ArrayRealVector,ArrayRealVector) trsbox} + * or {@link #altmov(int,double) altmov}. + * Usually {@link #originShift} + {@link #newPoint} is the vector of + * variables for the next evaluation of the objective function. + * It also satisfies the constraints indicated in {@link #lowerDifference} + * and {@link #upperDifference}. + * XXX "xnew" in the original code. + */ + private ArrayRealVector newPoint; + /** + * Alternative to {@link #newPoint}, chosen by + * {@link #altmov(int,double) altmov}. + * It may replace {@link #newPoint} in order to increase the denominator + * in the {@link #update(double, double, int) updating procedure}. + * XXX "xalt" in the original code. + */ + private ArrayRealVector alternativeNewPoint; + /** + * Trial step from {@link #trustRegionCenterOffset} which is usually + * {@link #newPoint} - {@link #trustRegionCenterOffset}. + * XXX "d__" in the original code. + */ + private ArrayRealVector trialStepPoint; + /** + * Values of the Lagrange functions at a new point. + * XXX "vlag" in the original code. + */ + private ArrayRealVector lagrangeValuesAtNewPoint; + /** + * Explicit second derivatives of the quadratic model. + * XXX "hq" in the original code. + */ + private ArrayRealVector modelSecondDerivativesValues; + + /** + * @param numberOfInterpolationPoints Number of interpolation conditions. + * For a problem of dimension {@code n}, its value must be in the interval + * {@code [n+2, (n+1)(n+2)/2]}. + * Choices that exceed {@code 2n+1} are not recommended. + */ + public BOBYQAOptimizer(int numberOfInterpolationPoints) { + this(numberOfInterpolationPoints, + DEFAULT_INITIAL_RADIUS, + DEFAULT_STOPPING_RADIUS); + } + + /** + * @param numberOfInterpolationPoints Number of interpolation conditions. + * For a problem of dimension {@code n}, its value must be in the interval + * {@code [n+2, (n+1)(n+2)/2]}. + * Choices that exceed {@code 2n+1} are not recommended. + * @param initialTrustRegionRadius Initial trust region radius. + * @param stoppingTrustRegionRadius Stopping trust region radius. + */ + public BOBYQAOptimizer(int numberOfInterpolationPoints, + double initialTrustRegionRadius, + double stoppingTrustRegionRadius) { + super(null); // No custom convergence criterion. + this.numberOfInterpolationPoints = numberOfInterpolationPoints; + this.initialTrustRegionRadius = initialTrustRegionRadius; + this.stoppingTrustRegionRadius = stoppingTrustRegionRadius; + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + final double[] lowerBound = getLowerBound(); + final double[] upperBound = getUpperBound(); + + // Validity checks. + setup(lowerBound, upperBound); + + isMinimize = (getGoalType() == GoalType.MINIMIZE); + currentBest = new ArrayRealVector(getStartPoint()); + + final double value = bobyqa(lowerBound, upperBound); + + return new PointValuePair(currentBest.getDataRef(), + isMinimize ? value : -value); + } + + /** + * This subroutine seeks the least value of a function of many variables, + * by applying a trust region method that forms quadratic models by + * interpolation. There is usually some freedom in the interpolation + * conditions, which is taken up by minimizing the Frobenius norm of + * the change to the second derivative of the model, beginning with the + * zero matrix. The values of the variables are constrained by upper and + * lower bounds. The arguments of the subroutine are as follows. + * + * N must be set to the number of variables and must be at least two. + * NPT is the number of interpolation conditions. Its value must be in + * the interval [N+2,(N+1)(N+2)/2]. Choices that exceed 2*N+1 are not + * recommended. + * Initial values of the variables must be set in X(1),X(2),...,X(N). They + * will be changed to the values that give the least calculated F. + * For I=1,2,...,N, XL(I) and XU(I) must provide the lower and upper + * bounds, respectively, on X(I). The construction of quadratic models + * requires XL(I) to be strictly less than XU(I) for each I. Further, + * the contribution to a model from changes to the I-th variable is + * damaged severely by rounding errors if XU(I)-XL(I) is too small. + * RHOBEG and RHOEND must be set to the initial and final values of a trust + * region radius, so both must be positive with RHOEND no greater than + * RHOBEG. Typically, RHOBEG should be about one tenth of the greatest + * expected change to a variable, while RHOEND should indicate the + * accuracy that is required in the final values of the variables. An + * error return occurs if any of the differences XU(I)-XL(I), I=1,...,N, + * is less than 2*RHOBEG. + * MAXFUN must be set to an upper bound on the number of calls of CALFUN. + * The array W will be used for working space. Its length must be at least + * (NPT+5)*(NPT+N)+3*N*(N+5)/2. + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + * @return the value of the objective at the optimum. + */ + private double bobyqa(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + + // Return if there is insufficient space between the bounds. Modify the + // initial X if necessary in order to avoid conflicts between the bounds + // and the construction of the first quadratic model. The lower and upper + // bounds on moves from the updated X are set now, in the ISL and ISU + // partitions of W, in order to provide useful and exact information about + // components of X that become within distance RHOBEG from their bounds. + + for (int j = 0; j < n; j++) { + final double boundDiff = boundDifference[j]; + lowerDifference.setEntry(j, lowerBound[j] - currentBest.getEntry(j)); + upperDifference.setEntry(j, upperBound[j] - currentBest.getEntry(j)); + if (lowerDifference.getEntry(j) >= -initialTrustRegionRadius) { + if (lowerDifference.getEntry(j) >= ZERO) { + currentBest.setEntry(j, lowerBound[j]); + lowerDifference.setEntry(j, ZERO); + upperDifference.setEntry(j, boundDiff); + } else { + currentBest.setEntry(j, lowerBound[j] + initialTrustRegionRadius); + lowerDifference.setEntry(j, -initialTrustRegionRadius); + // Computing MAX + final double deltaOne = upperBound[j] - currentBest.getEntry(j); + upperDifference.setEntry(j, FastMath.max(deltaOne, initialTrustRegionRadius)); + } + } else if (upperDifference.getEntry(j) <= initialTrustRegionRadius) { + if (upperDifference.getEntry(j) <= ZERO) { + currentBest.setEntry(j, upperBound[j]); + lowerDifference.setEntry(j, -boundDiff); + upperDifference.setEntry(j, ZERO); + } else { + currentBest.setEntry(j, upperBound[j] - initialTrustRegionRadius); + // Computing MIN + final double deltaOne = lowerBound[j] - currentBest.getEntry(j); + final double deltaTwo = -initialTrustRegionRadius; + lowerDifference.setEntry(j, FastMath.min(deltaOne, deltaTwo)); + upperDifference.setEntry(j, initialTrustRegionRadius); + } + } + } + + // Make the call of BOBYQB. + + return bobyqb(lowerBound, upperBound); + } // bobyqa + + // ---------------------------------------------------------------------------------------- + + /** + * The arguments N, NPT, X, XL, XU, RHOBEG, RHOEND, IPRINT and MAXFUN + * are identical to the corresponding arguments in SUBROUTINE BOBYQA. + * XBASE holds a shift of origin that should reduce the contributions + * from rounding errors to values of the model and Lagrange functions. + * XPT is a two-dimensional array that holds the coordinates of the + * interpolation points relative to XBASE. + * FVAL holds the values of F at the interpolation points. + * XOPT is set to the displacement from XBASE of the trust region centre. + * GOPT holds the gradient of the quadratic model at XBASE+XOPT. + * HQ holds the explicit second derivatives of the quadratic model. + * PQ contains the parameters of the implicit second derivatives of the + * quadratic model. + * BMAT holds the last N columns of H. + * ZMAT holds the factorization of the leading NPT by NPT submatrix of H, + * this factorization being ZMAT times ZMAT^T, which provides both the + * correct rank and positive semi-definiteness. + * NDIM is the first dimension of BMAT and has the value NPT+N. + * SL and SU hold the differences XL-XBASE and XU-XBASE, respectively. + * All the components of every XOPT are going to satisfy the bounds + * SL(I) .LEQ. XOPT(I) .LEQ. SU(I), with appropriate equalities when + * XOPT is on a constraint boundary. + * XNEW is chosen by SUBROUTINE TRSBOX or ALTMOV. Usually XBASE+XNEW is the + * vector of variables for the next call of CALFUN. XNEW also satisfies + * the SL and SU constraints in the way that has just been mentioned. + * XALT is an alternative to XNEW, chosen by ALTMOV, that may replace XNEW + * in order to increase the denominator in the updating of UPDATE. + * D is reserved for a trial step from XOPT, which is usually XNEW-XOPT. + * VLAG contains the values of the Lagrange functions at a new point X. + * They are part of a product that requires VLAG to be of length NDIM. + * W is a one-dimensional array that is used for working space. Its length + * must be at least 3*NDIM = 3*(NPT+N). + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + * @return the value of the objective at the optimum. + */ + private double bobyqb(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int np = n + 1; + final int nptm = npt - np; + final int nh = n * np / 2; + + final ArrayRealVector work1 = new ArrayRealVector(n); + final ArrayRealVector work2 = new ArrayRealVector(npt); + final ArrayRealVector work3 = new ArrayRealVector(npt); + + double cauchy = Double.NaN; + double alpha = Double.NaN; + double dsq = Double.NaN; + double crvmin = Double.NaN; + + // Set some constants. + // Parameter adjustments + + // Function Body + + // The call of PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ, + // BMAT and ZMAT for the first iteration, with the corresponding values of + // of NF and KOPT, which are the number of calls of CALFUN so far and the + // index of the interpolation point at the trust region centre. Then the + // initial XOPT is set too. The branch to label 720 occurs if MAXFUN is + // less than NPT. GOPT will be updated if KOPT is different from KBASE. + + trustRegionCenterInterpolationPointIndex = 0; + + prelim(lowerBound, upperBound); + double xoptsq = ZERO; + for (int i = 0; i < n; i++) { + trustRegionCenterOffset.setEntry(i, interpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex, i)); + // Computing 2nd power + final double deltaOne = trustRegionCenterOffset.getEntry(i); + xoptsq += deltaOne * deltaOne; + } + double fsave = fAtInterpolationPoints.getEntry(0); + final int kbase = 0; + + // Complete the settings that are required for the iterative procedure. + + int ntrits = 0; + int itest = 0; + int knew = 0; + int nfsav = getEvaluations(); + double rho = initialTrustRegionRadius; + double delta = rho; + double diffa = ZERO; + double diffb = ZERO; + double diffc = ZERO; + double f = ZERO; + double beta = ZERO; + double adelt = ZERO; + double denom = ZERO; + double ratio = ZERO; + double dnorm = ZERO; + double scaden = ZERO; + double biglsq = ZERO; + double distsq = ZERO; + + // Update GOPT if necessary before the first iteration and after each + // call of RESCUE that makes a call of CALFUN. + + int state = 20; + for(;;) { + switch (state) { + case 20: { + printState(20); // XXX + if (trustRegionCenterInterpolationPointIndex != kbase) { + int ih = 0; + for (int j = 0; j < n; j++) { + for (int i = 0; i <= j; i++) { + if (i < j) { + gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(i)); + } + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(j)); + ih++; + } + } + if (getEvaluations() > npt) { + for (int k = 0; k < npt; k++) { + double temp = ZERO; + for (int j = 0; j < n; j++) { + temp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + temp *= modelSecondDerivativesParameters.getEntry(k); + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + // throw new PathIsExploredException(); // XXX + } + } + + // Generate the next point in the trust region that provides a small value + // of the quadratic model subject to the constraints on the variables. + // The int NTRITS is set to the number "trust region" iterations that + // have occurred since the last "alternative" iteration. If the length + // of XNEW-XOPT is less than HALF*RHO, however, then there is a branch to + // label 650 or 680 with NTRITS=-1, instead of calculating F at XNEW. + + } + case 60: { + printState(60); // XXX + final ArrayRealVector gnew = new ArrayRealVector(n); + final ArrayRealVector xbdi = new ArrayRealVector(n); + final ArrayRealVector s = new ArrayRealVector(n); + final ArrayRealVector hs = new ArrayRealVector(n); + final ArrayRealVector hred = new ArrayRealVector(n); + + final double[] dsqCrvmin = trsbox(delta, gnew, xbdi, s, + hs, hred); + dsq = dsqCrvmin[0]; + crvmin = dsqCrvmin[1]; + + // Computing MIN + double deltaOne = delta; + double deltaTwo = FastMath.sqrt(dsq); + dnorm = FastMath.min(deltaOne, deltaTwo); + if (dnorm < HALF * rho) { + ntrits = -1; + // Computing 2nd power + deltaOne = TEN * rho; + distsq = deltaOne * deltaOne; + if (getEvaluations() <= nfsav + 2) { + state = 650; break; + } + + // The following choice between labels 650 and 680 depends on whether or + // not our work with the current RHO seems to be complete. Either RHO is + // decreased or termination occurs if the errors in the quadratic model at + // the last three interpolation points compare favourably with predictions + // of likely improvements to the model within distance HALF*RHO of XOPT. + + // Computing MAX + deltaOne = FastMath.max(diffa, diffb); + final double errbig = FastMath.max(deltaOne, diffc); + final double frhosq = rho * ONE_OVER_EIGHT * rho; + if (crvmin > ZERO && + errbig > frhosq * crvmin) { + state = 650; break; + } + final double bdtol = errbig / rho; + for (int j = 0; j < n; j++) { + double bdtest = bdtol; + if (newPoint.getEntry(j) == lowerDifference.getEntry(j)) { + bdtest = work1.getEntry(j); + } + if (newPoint.getEntry(j) == upperDifference.getEntry(j)) { + bdtest = -work1.getEntry(j); + } + if (bdtest < bdtol) { + double curv = modelSecondDerivativesValues.getEntry((j + j * j) / 2); + for (int k = 0; k < npt; k++) { + // Computing 2nd power + final double d1 = interpolationPoints.getEntry(k, j); + curv += modelSecondDerivativesParameters.getEntry(k) * (d1 * d1); + } + bdtest += HALF * curv * rho; + if (bdtest < bdtol) { + state = 650; break; + } + // throw new PathIsExploredException(); // XXX + } + } + state = 680; break; + } + ++ntrits; + + // Severe cancellation is likely to occur if XOPT is too far from XBASE. + // If the following test holds, then XBASE is shifted so that XOPT becomes + // zero. The appropriate changes are made to BMAT and to the second + // derivatives of the current model, beginning with the changes to BMAT + // that do not depend on ZMAT. VLAG is used temporarily for working space. + + } + case 90: { + printState(90); // XXX + if (dsq <= xoptsq * ONE_OVER_A_THOUSAND) { + final double fracsq = xoptsq * ONE_OVER_FOUR; + double sumpq = ZERO; + // final RealVector sumVector + // = new ArrayRealVector(npt, -HALF * xoptsq).add(interpolationPoints.operate(trustRegionCenter)); + for (int k = 0; k < npt; k++) { + sumpq += modelSecondDerivativesParameters.getEntry(k); + double sum = -HALF * xoptsq; + for (int i = 0; i < n; i++) { + sum += interpolationPoints.getEntry(k, i) * trustRegionCenterOffset.getEntry(i); + } + // sum = sumVector.getEntry(k); // XXX "testAckley" and "testDiffPow" fail. + work2.setEntry(k, sum); + final double temp = fracsq - HALF * sum; + for (int i = 0; i < n; i++) { + work1.setEntry(i, bMatrix.getEntry(k, i)); + lagrangeValuesAtNewPoint.setEntry(i, sum * interpolationPoints.getEntry(k, i) + temp * trustRegionCenterOffset.getEntry(i)); + final int ip = npt + i; + for (int j = 0; j <= i; j++) { + bMatrix.setEntry(ip, j, + bMatrix.getEntry(ip, j) + + work1.getEntry(i) * lagrangeValuesAtNewPoint.getEntry(j) + + lagrangeValuesAtNewPoint.getEntry(i) * work1.getEntry(j)); + } + } + } + + // Then the revisions of BMAT that depend on ZMAT are calculated. + + for (int m = 0; m < nptm; m++) { + double sumz = ZERO; + double sumw = ZERO; + for (int k = 0; k < npt; k++) { + sumz += zMatrix.getEntry(k, m); + lagrangeValuesAtNewPoint.setEntry(k, work2.getEntry(k) * zMatrix.getEntry(k, m)); + sumw += lagrangeValuesAtNewPoint.getEntry(k); + } + for (int j = 0; j < n; j++) { + double sum = (fracsq * sumz - HALF * sumw) * trustRegionCenterOffset.getEntry(j); + for (int k = 0; k < npt; k++) { + sum += lagrangeValuesAtNewPoint.getEntry(k) * interpolationPoints.getEntry(k, j); + } + work1.setEntry(j, sum); + for (int k = 0; k < npt; k++) { + bMatrix.setEntry(k, j, + bMatrix.getEntry(k, j) + + sum * zMatrix.getEntry(k, m)); + } + } + for (int i = 0; i < n; i++) { + final int ip = i + npt; + final double temp = work1.getEntry(i); + for (int j = 0; j <= i; j++) { + bMatrix.setEntry(ip, j, + bMatrix.getEntry(ip, j) + + temp * work1.getEntry(j)); + } + } + } + + // The following instructions complete the shift, including the changes + // to the second derivative parameters of the quadratic model. + + int ih = 0; + for (int j = 0; j < n; j++) { + work1.setEntry(j, -HALF * sumpq * trustRegionCenterOffset.getEntry(j)); + for (int k = 0; k < npt; k++) { + work1.setEntry(j, work1.getEntry(j) + modelSecondDerivativesParameters.getEntry(k) * interpolationPoints.getEntry(k, j)); + interpolationPoints.setEntry(k, j, interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j)); + } + for (int i = 0; i <= j; i++) { + modelSecondDerivativesValues.setEntry(ih, + modelSecondDerivativesValues.getEntry(ih) + + work1.getEntry(i) * trustRegionCenterOffset.getEntry(j) + + trustRegionCenterOffset.getEntry(i) * work1.getEntry(j)); + bMatrix.setEntry(npt + i, j, bMatrix.getEntry(npt + j, i)); + ih++; + } + } + for (int i = 0; i < n; i++) { + originShift.setEntry(i, originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i)); + newPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + lowerDifference.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + upperDifference.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + trustRegionCenterOffset.setEntry(i, ZERO); + } + xoptsq = ZERO; + } + if (ntrits == 0) { + state = 210; break; + } + state = 230; break; + + // XBASE is also moved to XOPT by a call of RESCUE. This calculation is + // more expensive than the previous shift, because new matrices BMAT and + // ZMAT are generated from scratch, which may include the replacement of + // interpolation points whose positions seem to be causing near linear + // dependence in the interpolation conditions. Therefore RESCUE is called + // only if rounding errors have reduced by at least a factor of two the + // denominator of the formula for updating the H matrix. It provides a + // useful safeguard, but is not invoked in most applications of BOBYQA. + + } + case 210: { + printState(210); // XXX + // Pick two alternative vectors of variables, relative to XBASE, that + // are suitable as new positions of the KNEW-th interpolation point. + // Firstly, XNEW is set to the point on a line through XOPT and another + // interpolation point that minimizes the predicted value of the next + // denominator, subject to ||XNEW - XOPT|| .LEQ. ADELT and to the SL + // and SU bounds. Secondly, XALT is set to the best feasible point on + // a constrained version of the Cauchy step of the KNEW-th Lagrange + // function, the corresponding value of the square of this function + // being returned in CAUCHY. The choice between these alternatives is + // going to be made when the denominator is calculated. + + final double[] alphaCauchy = altmov(knew, adelt); + alpha = alphaCauchy[0]; + cauchy = alphaCauchy[1]; + + for (int i = 0; i < n; i++) { + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + } + + // Calculate VLAG and BETA for the current choice of D. The scalar + // product of D with XPT(K,.) is going to be held in W(NPT+K) for + // use when VQUAD is calculated. + + } + case 230: { + printState(230); // XXX + for (int k = 0; k < npt; k++) { + double suma = ZERO; + double sumb = ZERO; + double sum = ZERO; + for (int j = 0; j < n; j++) { + suma += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j); + sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + sum += bMatrix.getEntry(k, j) * trialStepPoint.getEntry(j); + } + work3.setEntry(k, suma * (HALF * suma + sumb)); + lagrangeValuesAtNewPoint.setEntry(k, sum); + work2.setEntry(k, suma); + } + beta = ZERO; + for (int m = 0; m < nptm; m++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += zMatrix.getEntry(k, m) * work3.getEntry(k); + } + beta -= sum * sum; + for (int k = 0; k < npt; k++) { + lagrangeValuesAtNewPoint.setEntry(k, lagrangeValuesAtNewPoint.getEntry(k) + sum * zMatrix.getEntry(k, m)); + } + } + dsq = ZERO; + double bsum = ZERO; + double dx = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(j); + dsq += d1 * d1; + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += work3.getEntry(k) * bMatrix.getEntry(k, j); + } + bsum += sum * trialStepPoint.getEntry(j); + final int jp = npt + j; + for (int i = 0; i < n; i++) { + sum += bMatrix.getEntry(jp, i) * trialStepPoint.getEntry(i); + } + lagrangeValuesAtNewPoint.setEntry(jp, sum); + bsum += sum * trialStepPoint.getEntry(j); + dx += trialStepPoint.getEntry(j) * trustRegionCenterOffset.getEntry(j); + } + + beta = dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) + beta - bsum; // Original + // beta += dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) - bsum; // XXX "testAckley" and "testDiffPow" fail. + // beta = dx * dx + dsq * (xoptsq + 2 * dx + HALF * dsq) + beta - bsum; // XXX "testDiffPow" fails. + + lagrangeValuesAtNewPoint.setEntry(trustRegionCenterInterpolationPointIndex, + lagrangeValuesAtNewPoint.getEntry(trustRegionCenterInterpolationPointIndex) + ONE); + + // If NTRITS is zero, the denominator may be increased by replacing + // the step D of ALTMOV by a Cauchy step. Then RESCUE may be called if + // rounding errors have damaged the chosen denominator. + + if (ntrits == 0) { + // Computing 2nd power + final double d1 = lagrangeValuesAtNewPoint.getEntry(knew); + denom = d1 * d1 + alpha * beta; + if (denom < cauchy && cauchy > ZERO) { + for (int i = 0; i < n; i++) { + newPoint.setEntry(i, alternativeNewPoint.getEntry(i)); + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + } + cauchy = ZERO; // XXX Useful statement? + state = 230; break; + } + // Alternatively, if NTRITS is positive, then set KNEW to the index of + // the next interpolation point to be deleted to make room for a trust + // region step. Again RESCUE may be called if rounding errors have damaged_ + // the chosen denominator, which is the reason for attempting to select + // KNEW before calculating the next value of the objective function. + + } else { + final double delsq = delta * delta; + scaden = ZERO; + biglsq = ZERO; + knew = 0; + for (int k = 0; k < npt; k++) { + if (k == trustRegionCenterInterpolationPointIndex) { + continue; + } + double hdiag = ZERO; + for (int m = 0; m < nptm; m++) { + // Computing 2nd power + final double d1 = zMatrix.getEntry(k, m); + hdiag += d1 * d1; + } + // Computing 2nd power + final double d2 = lagrangeValuesAtNewPoint.getEntry(k); + final double den = beta * hdiag + d2 * d2; + distsq = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d3 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j); + distsq += d3 * d3; + } + // Computing MAX + // Computing 2nd power + final double d4 = distsq / delsq; + final double temp = FastMath.max(ONE, d4 * d4); + if (temp * den > scaden) { + scaden = temp * den; + knew = k; + denom = den; + } + // Computing MAX + // Computing 2nd power + final double d5 = lagrangeValuesAtNewPoint.getEntry(k); + biglsq = FastMath.max(biglsq, temp * (d5 * d5)); + } + } + + // Put the variables for the next calculation of the objective function + // in XNEW, with any adjustments for the bounds. + + // Calculate the value of the objective function at XBASE+XNEW, unless + // the limit on the number of calculations of F has been reached. + + } + case 360: { + printState(360); // XXX + for (int i = 0; i < n; i++) { + // Computing MIN + // Computing MAX + final double d3 = lowerBound[i]; + final double d4 = originShift.getEntry(i) + newPoint.getEntry(i); + final double d1 = FastMath.max(d3, d4); + final double d2 = upperBound[i]; + currentBest.setEntry(i, FastMath.min(d1, d2)); + if (newPoint.getEntry(i) == lowerDifference.getEntry(i)) { + currentBest.setEntry(i, lowerBound[i]); + } + if (newPoint.getEntry(i) == upperDifference.getEntry(i)) { + currentBest.setEntry(i, upperBound[i]); + } + } + + f = computeObjectiveValue(currentBest.toArray()); + + if (!isMinimize) { + f = -f; + } + if (ntrits == -1) { + fsave = f; + state = 720; break; + } + + // Use the quadratic model to predict the change in F due to the step D, + // and set DIFF to the error of this prediction. + + final double fopt = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex); + double vquad = ZERO; + int ih = 0; + for (int j = 0; j < n; j++) { + vquad += trialStepPoint.getEntry(j) * gradientAtTrustRegionCenter.getEntry(j); + for (int i = 0; i <= j; i++) { + double temp = trialStepPoint.getEntry(i) * trialStepPoint.getEntry(j); + if (i == j) { + temp *= HALF; + } + vquad += modelSecondDerivativesValues.getEntry(ih) * temp; + ih++; + } + } + for (int k = 0; k < npt; k++) { + // Computing 2nd power + final double d1 = work2.getEntry(k); + final double d2 = d1 * d1; // "d1" must be squared first to prevent test failures. + vquad += HALF * modelSecondDerivativesParameters.getEntry(k) * d2; + } + final double diff = f - fopt - vquad; + diffc = diffb; + diffb = diffa; + diffa = FastMath.abs(diff); + if (dnorm > rho) { + nfsav = getEvaluations(); + } + + // Pick the next value of DELTA after a trust region step. + + if (ntrits > 0) { + if (vquad >= ZERO) { + throw new MathIllegalStateException(LocalizedFormats.TRUST_REGION_STEP_FAILED, vquad); + } + ratio = (f - fopt) / vquad; + final double hDelta = HALF * delta; + if (ratio <= ONE_OVER_TEN) { + // Computing MIN + delta = FastMath.min(hDelta, dnorm); + } else if (ratio <= .7) { + // Computing MAX + delta = FastMath.max(hDelta, dnorm); + } else { + // Computing MAX + delta = FastMath.max(hDelta, 2 * dnorm); + } + if (delta <= rho * 1.5) { + delta = rho; + } + + // Recalculate KNEW and DENOM if the new F is less than FOPT. + + if (f < fopt) { + final int ksav = knew; + final double densav = denom; + final double delsq = delta * delta; + scaden = ZERO; + biglsq = ZERO; + knew = 0; + for (int k = 0; k < npt; k++) { + double hdiag = ZERO; + for (int m = 0; m < nptm; m++) { + // Computing 2nd power + final double d1 = zMatrix.getEntry(k, m); + hdiag += d1 * d1; + } + // Computing 2nd power + final double d1 = lagrangeValuesAtNewPoint.getEntry(k); + final double den = beta * hdiag + d1 * d1; + distsq = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d2 = interpolationPoints.getEntry(k, j) - newPoint.getEntry(j); + distsq += d2 * d2; + } + // Computing MAX + // Computing 2nd power + final double d3 = distsq / delsq; + final double temp = FastMath.max(ONE, d3 * d3); + if (temp * den > scaden) { + scaden = temp * den; + knew = k; + denom = den; + } + // Computing MAX + // Computing 2nd power + final double d4 = lagrangeValuesAtNewPoint.getEntry(k); + final double d5 = temp * (d4 * d4); + biglsq = FastMath.max(biglsq, d5); + } + if (scaden <= HALF * biglsq) { + knew = ksav; + denom = densav; + } + } + } + + // Update BMAT and ZMAT, so that the KNEW-th interpolation point can be + // moved. Also update the second derivative terms of the model. + + update(beta, denom, knew); + + ih = 0; + final double pqold = modelSecondDerivativesParameters.getEntry(knew); + modelSecondDerivativesParameters.setEntry(knew, ZERO); + for (int i = 0; i < n; i++) { + final double temp = pqold * interpolationPoints.getEntry(knew, i); + for (int j = 0; j <= i; j++) { + modelSecondDerivativesValues.setEntry(ih, modelSecondDerivativesValues.getEntry(ih) + temp * interpolationPoints.getEntry(knew, j)); + ih++; + } + } + for (int m = 0; m < nptm; m++) { + final double temp = diff * zMatrix.getEntry(knew, m); + for (int k = 0; k < npt; k++) { + modelSecondDerivativesParameters.setEntry(k, modelSecondDerivativesParameters.getEntry(k) + temp * zMatrix.getEntry(k, m)); + } + } + + // Include the new interpolation point, and make the changes to GOPT at + // the old XOPT that are caused by the updating of the quadratic model. + + fAtInterpolationPoints.setEntry(knew, f); + for (int i = 0; i < n; i++) { + interpolationPoints.setEntry(knew, i, newPoint.getEntry(i)); + work1.setEntry(i, bMatrix.getEntry(knew, i)); + } + for (int k = 0; k < npt; k++) { + double suma = ZERO; + for (int m = 0; m < nptm; m++) { + suma += zMatrix.getEntry(knew, m) * zMatrix.getEntry(k, m); + } + double sumb = ZERO; + for (int j = 0; j < n; j++) { + sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + final double temp = suma * sumb; + for (int i = 0; i < n; i++) { + work1.setEntry(i, work1.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + diff * work1.getEntry(i)); + } + + // Update XOPT, GOPT and KOPT if the new calculated F is less than FOPT. + + if (f < fopt) { + trustRegionCenterInterpolationPointIndex = knew; + xoptsq = ZERO; + ih = 0; + for (int j = 0; j < n; j++) { + trustRegionCenterOffset.setEntry(j, newPoint.getEntry(j)); + // Computing 2nd power + final double d1 = trustRegionCenterOffset.getEntry(j); + xoptsq += d1 * d1; + for (int i = 0; i <= j; i++) { + if (i < j) { + gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(i)); + } + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(j)); + ih++; + } + } + for (int k = 0; k < npt; k++) { + double temp = ZERO; + for (int j = 0; j < n; j++) { + temp += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j); + } + temp *= modelSecondDerivativesParameters.getEntry(k); + for (int i = 0; i < n; i++) { + gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i)); + } + } + } + + // Calculate the parameters of the least Frobenius norm interpolant to + // the current data, the gradient of this interpolant at XOPT being put + // into VLAG(NPT+I), I=1,2,...,N. + + if (ntrits > 0) { + for (int k = 0; k < npt; k++) { + lagrangeValuesAtNewPoint.setEntry(k, fAtInterpolationPoints.getEntry(k) - fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)); + work3.setEntry(k, ZERO); + } + for (int j = 0; j < nptm; j++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += zMatrix.getEntry(k, j) * lagrangeValuesAtNewPoint.getEntry(k); + } + for (int k = 0; k < npt; k++) { + work3.setEntry(k, work3.getEntry(k) + sum * zMatrix.getEntry(k, j)); + } + } + for (int k = 0; k < npt; k++) { + double sum = ZERO; + for (int j = 0; j < n; j++) { + sum += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + work2.setEntry(k, work3.getEntry(k)); + work3.setEntry(k, sum * work3.getEntry(k)); + } + double gqsq = ZERO; + double gisq = ZERO; + for (int i = 0; i < n; i++) { + double sum = ZERO; + for (int k = 0; k < npt; k++) { + sum += bMatrix.getEntry(k, i) * + lagrangeValuesAtNewPoint.getEntry(k) + interpolationPoints.getEntry(k, i) * work3.getEntry(k); + } + if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) { + // Computing MIN + // Computing 2nd power + final double d1 = FastMath.min(ZERO, gradientAtTrustRegionCenter.getEntry(i)); + gqsq += d1 * d1; + // Computing 2nd power + final double d2 = FastMath.min(ZERO, sum); + gisq += d2 * d2; + } else if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) { + // Computing MAX + // Computing 2nd power + final double d1 = FastMath.max(ZERO, gradientAtTrustRegionCenter.getEntry(i)); + gqsq += d1 * d1; + // Computing 2nd power + final double d2 = FastMath.max(ZERO, sum); + gisq += d2 * d2; + } else { + // Computing 2nd power + final double d1 = gradientAtTrustRegionCenter.getEntry(i); + gqsq += d1 * d1; + gisq += sum * sum; + } + lagrangeValuesAtNewPoint.setEntry(npt + i, sum); + } + + // Test whether to replace the new quadratic model by the least Frobenius + // norm interpolant, making the replacement if the test is satisfied. + + ++itest; + if (gqsq < TEN * gisq) { + itest = 0; + } + if (itest >= 3) { + for (int i = 0, max = FastMath.max(npt, nh); i < max; i++) { + if (i < n) { + gradientAtTrustRegionCenter.setEntry(i, lagrangeValuesAtNewPoint.getEntry(npt + i)); + } + if (i < npt) { + modelSecondDerivativesParameters.setEntry(i, work2.getEntry(i)); + } + if (i < nh) { + modelSecondDerivativesValues.setEntry(i, ZERO); + } + itest = 0; + } + } + } + + // If a trust region step has provided a sufficient decrease in F, then + // branch for another trust region calculation. The case NTRITS=0 occurs + // when the new interpolation point was reached by an alternative step. + + if (ntrits == 0) { + state = 60; break; + } + if (f <= fopt + ONE_OVER_TEN * vquad) { + state = 60; break; + } + + // Alternatively, find out if the interpolation points are close enough + // to the best point so far. + + // Computing MAX + // Computing 2nd power + final double d1 = TWO * delta; + // Computing 2nd power + final double d2 = TEN * rho; + distsq = FastMath.max(d1 * d1, d2 * d2); + } + case 650: { + printState(650); // XXX + knew = -1; + for (int k = 0; k < npt; k++) { + double sum = ZERO; + for (int j = 0; j < n; j++) { + // Computing 2nd power + final double d1 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j); + sum += d1 * d1; + } + if (sum > distsq) { + knew = k; + distsq = sum; + } + } + + // If KNEW is positive, then ALTMOV finds alternative new positions for + // the KNEW-th interpolation point within distance ADELT of XOPT. It is + // reached via label 90. Otherwise, there is a branch to label 60 for + // another trust region iteration, unless the calculations with the + // current RHO are complete. + + if (knew >= 0) { + final double dist = FastMath.sqrt(distsq); + if (ntrits == -1) { + // Computing MIN + delta = FastMath.min(ONE_OVER_TEN * delta, HALF * dist); + if (delta <= rho * 1.5) { + delta = rho; + } + } + ntrits = 0; + // Computing MAX + // Computing MIN + final double d1 = FastMath.min(ONE_OVER_TEN * dist, delta); + adelt = FastMath.max(d1, rho); + dsq = adelt * adelt; + state = 90; break; + } + if (ntrits == -1) { + state = 680; break; + } + if (ratio > ZERO) { + state = 60; break; + } + if (FastMath.max(delta, dnorm) > rho) { + state = 60; break; + } + + // The calculations with the current value of RHO are complete. Pick the + // next values of RHO and DELTA. + } + case 680: { + printState(680); // XXX + if (rho > stoppingTrustRegionRadius) { + delta = HALF * rho; + ratio = rho / stoppingTrustRegionRadius; + if (ratio <= SIXTEEN) { + rho = stoppingTrustRegionRadius; + } else if (ratio <= TWO_HUNDRED_FIFTY) { + rho = FastMath.sqrt(ratio) * stoppingTrustRegionRadius; + } else { + rho *= ONE_OVER_TEN; + } + delta = FastMath.max(delta, rho); + ntrits = 0; + nfsav = getEvaluations(); + state = 60; break; + } + + // Return from the calculation, after another Newton-Raphson step, if + // it is too short to have been tried before. + + if (ntrits == -1) { + state = 360; break; + } + } + case 720: { + printState(720); // XXX + if (fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex) <= fsave) { + for (int i = 0; i < n; i++) { + // Computing MIN + // Computing MAX + final double d3 = lowerBound[i]; + final double d4 = originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i); + final double d1 = FastMath.max(d3, d4); + final double d2 = upperBound[i]; + currentBest.setEntry(i, FastMath.min(d1, d2)); + if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) { + currentBest.setEntry(i, lowerBound[i]); + } + if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) { + currentBest.setEntry(i, upperBound[i]); + } + } + f = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex); + } + return f; + } + default: { + throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "bobyqb"); + }}} + } // bobyqb + + // ---------------------------------------------------------------------------------------- + + /** + * The arguments N, NPT, XPT, XOPT, BMAT, ZMAT, NDIM, SL and SU all have + * the same meanings as the corresponding arguments of BOBYQB. + * KOPT is the index of the optimal interpolation point. + * KNEW is the index of the interpolation point that is going to be moved. + * ADELT is the current trust region bound. + * XNEW will be set to a suitable new position for the interpolation point + * XPT(KNEW,.). Specifically, it satisfies the SL, SU and trust region + * bounds and it should provide a large denominator in the next call of + * UPDATE. The step XNEW-XOPT from XOPT is restricted to moves along the + * straight lines through XOPT and another interpolation point. + * XALT also provides a large value of the modulus of the KNEW-th Lagrange + * function subject to the constraints that have been mentioned, its main + * difference from XNEW being that XALT-XOPT is a constrained version of + * the Cauchy step within the trust region. An exception is that XALT is + * not calculated if all components of GLAG (see below) are zero. + * ALPHA will be set to the KNEW-th diagonal element of the H matrix. + * CAUCHY will be set to the square of the KNEW-th Lagrange function at + * the step XALT-XOPT from XOPT for the vector XALT that is returned, + * except that CAUCHY is set to zero if XALT is not calculated. + * GLAG is a working space vector of length N for the gradient of the + * KNEW-th Lagrange function at XOPT. + * HCOL is a working space vector of length NPT for the second derivative + * coefficients of the KNEW-th Lagrange function. + * W is a working space vector of length 2N that is going to hold the + * constrained Cauchy step from XOPT of the Lagrange function, followed + * by the downhill version of XALT when the uphill step is calculated. + * + * Set the first NPT components of W to the leading elements of the + * KNEW-th column of the H matrix. + * @param knew + * @param adelt + */ + private double[] altmov( + int knew, + double adelt + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + + final ArrayRealVector glag = new ArrayRealVector(n); + final ArrayRealVector hcol = new ArrayRealVector(npt); + + final ArrayRealVector work1 = new ArrayRealVector(n); + final ArrayRealVector work2 = new ArrayRealVector(n); + + for (int k = 0; k < npt; k++) { + hcol.setEntry(k, ZERO); + } + for (int j = 0, max = npt - n - 1; j < max; j++) { + final double tmp = zMatrix.getEntry(knew, j); + for (int k = 0; k < npt; k++) { + hcol.setEntry(k, hcol.getEntry(k) + tmp * zMatrix.getEntry(k, j)); + } + } + final double alpha = hcol.getEntry(knew); + final double ha = HALF * alpha; + + // Calculate the gradient of the KNEW-th Lagrange function at XOPT. + + for (int i = 0; i < n; i++) { + glag.setEntry(i, bMatrix.getEntry(knew, i)); + } + for (int k = 0; k < npt; k++) { + double tmp = ZERO; + for (int j = 0; j < n; j++) { + tmp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j); + } + tmp *= hcol.getEntry(k); + for (int i = 0; i < n; i++) { + glag.setEntry(i, glag.getEntry(i) + tmp * interpolationPoints.getEntry(k, i)); + } + } + + // Search for a large denominator along the straight lines through XOPT + // and another interpolation point. SLBD and SUBD will be lower and upper + // bounds on the step along each of these lines in turn. PREDSQ will be + // set to the square of the predicted denominator for each line. PRESAV + // will be set to the largest admissible value of PREDSQ that occurs. + + double presav = ZERO; + double step = Double.NaN; + int ksav = 0; + int ibdsav = 0; + double stpsav = 0; + for (int k = 0; k < npt; k++) { + if (k == trustRegionCenterInterpolationPointIndex) { + continue; + } + double dderiv = ZERO; + double distsq = ZERO; + for (int i = 0; i < n; i++) { + final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i); + dderiv += glag.getEntry(i) * tmp; + distsq += tmp * tmp; + } + double subd = adelt / FastMath.sqrt(distsq); + double slbd = -subd; + int ilbd = 0; + int iubd = 0; + final double sumin = FastMath.min(ONE, subd); + + // Revise SLBD and SUBD if necessary because of the bounds in SL and SU. + + for (int i = 0; i < n; i++) { + final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i); + if (tmp > ZERO) { + if (slbd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + slbd = (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp; + ilbd = -i - 1; + } + if (subd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + // Computing MAX + subd = FastMath.max(sumin, + (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp); + iubd = i + 1; + } + } else if (tmp < ZERO) { + if (slbd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + slbd = (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp; + ilbd = i + 1; + } + if (subd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) { + // Computing MAX + subd = FastMath.max(sumin, + (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp); + iubd = -i - 1; + } + } + } + + // Seek a large modulus of the KNEW-th Lagrange function when the index + // of the other interpolation point on the line through XOPT is KNEW. + + step = slbd; + int isbd = ilbd; + double vlag = Double.NaN; + if (k == knew) { + final double diff = dderiv - ONE; + vlag = slbd * (dderiv - slbd * diff); + final double d1 = subd * (dderiv - subd * diff); + if (FastMath.abs(d1) > FastMath.abs(vlag)) { + step = subd; + vlag = d1; + isbd = iubd; + } + final double d2 = HALF * dderiv; + final double d3 = d2 - diff * slbd; + final double d4 = d2 - diff * subd; + if (d3 * d4 < ZERO) { + final double d5 = d2 * d2 / diff; + if (FastMath.abs(d5) > FastMath.abs(vlag)) { + step = d2 / diff; + vlag = d5; + isbd = 0; + } + } + + // Search along each of the other lines through XOPT and another point. + + } else { + vlag = slbd * (ONE - slbd); + final double tmp = subd * (ONE - subd); + if (FastMath.abs(tmp) > FastMath.abs(vlag)) { + step = subd; + vlag = tmp; + isbd = iubd; + } + if (subd > HALF && FastMath.abs(vlag) < ONE_OVER_FOUR) { + step = HALF; + vlag = ONE_OVER_FOUR; + isbd = 0; + } + vlag *= dderiv; + } + + // Calculate PREDSQ for the current line search and maintain PRESAV. + + final double tmp = step * (ONE - step) * distsq; + final double predsq = vlag * vlag * (vlag * vlag + ha * tmp * tmp); + if (predsq > presav) { + presav = predsq; + ksav = k; + stpsav = step; + ibdsav = isbd; + } + } + + // Construct XNEW in a way that satisfies the bound constraints exactly. + + for (int i = 0; i < n; i++) { + final double tmp = trustRegionCenterOffset.getEntry(i) + stpsav * (interpolationPoints.getEntry(ksav, i) - trustRegionCenterOffset.getEntry(i)); + newPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), + FastMath.min(upperDifference.getEntry(i), tmp))); + } + if (ibdsav < 0) { + newPoint.setEntry(-ibdsav - 1, lowerDifference.getEntry(-ibdsav - 1)); + } + if (ibdsav > 0) { + newPoint.setEntry(ibdsav - 1, upperDifference.getEntry(ibdsav - 1)); + } + + // Prepare for the iterative method that assembles the constrained Cauchy + // step in W. The sum of squares of the fixed components of W is formed in + // WFIXSQ, and the free components of W are set to BIGSTP. + + final double bigstp = adelt + adelt; + int iflag = 0; + double cauchy = Double.NaN; + double csave = ZERO; + while (true) { + double wfixsq = ZERO; + double ggfree = ZERO; + for (int i = 0; i < n; i++) { + final double glagValue = glag.getEntry(i); + work1.setEntry(i, ZERO); + if (FastMath.min(trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i), glagValue) > ZERO || + FastMath.max(trustRegionCenterOffset.getEntry(i) - upperDifference.getEntry(i), glagValue) < ZERO) { + work1.setEntry(i, bigstp); + // Computing 2nd power + ggfree += glagValue * glagValue; + } + } + if (ggfree == ZERO) { + return new double[] { alpha, ZERO }; + } + + // Investigate whether more components of W can be fixed. + final double tmp1 = adelt * adelt - wfixsq; + if (tmp1 > ZERO) { + step = FastMath.sqrt(tmp1 / ggfree); + ggfree = ZERO; + for (int i = 0; i < n; i++) { + if (work1.getEntry(i) == bigstp) { + final double tmp2 = trustRegionCenterOffset.getEntry(i) - step * glag.getEntry(i); + if (tmp2 <= lowerDifference.getEntry(i)) { + work1.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = work1.getEntry(i); + wfixsq += d1 * d1; + } else if (tmp2 >= upperDifference.getEntry(i)) { + work1.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = work1.getEntry(i); + wfixsq += d1 * d1; + } else { + // Computing 2nd power + final double d1 = glag.getEntry(i); + ggfree += d1 * d1; + } + } + } + } + + // Set the remaining free components of W and all components of XALT, + // except that W may be scaled later. + + double gw = ZERO; + for (int i = 0; i < n; i++) { + final double glagValue = glag.getEntry(i); + if (work1.getEntry(i) == bigstp) { + work1.setEntry(i, -step * glagValue); + final double min = FastMath.min(upperDifference.getEntry(i), + trustRegionCenterOffset.getEntry(i) + work1.getEntry(i)); + alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), min)); + } else if (work1.getEntry(i) == ZERO) { + alternativeNewPoint.setEntry(i, trustRegionCenterOffset.getEntry(i)); + } else if (glagValue > ZERO) { + alternativeNewPoint.setEntry(i, lowerDifference.getEntry(i)); + } else { + alternativeNewPoint.setEntry(i, upperDifference.getEntry(i)); + } + gw += glagValue * work1.getEntry(i); + } + + // Set CURV to the curvature of the KNEW-th Lagrange function along W. + // Scale W by a factor less than one if that can reduce the modulus of + // the Lagrange function at XOPT+W. Set CAUCHY to the final value of + // the square of this function. + + double curv = ZERO; + for (int k = 0; k < npt; k++) { + double tmp = ZERO; + for (int j = 0; j < n; j++) { + tmp += interpolationPoints.getEntry(k, j) * work1.getEntry(j); + } + curv += hcol.getEntry(k) * tmp * tmp; + } + if (iflag == 1) { + curv = -curv; + } + if (curv > -gw && + curv < -gw * (ONE + FastMath.sqrt(TWO))) { + final double scale = -gw / curv; + for (int i = 0; i < n; i++) { + final double tmp = trustRegionCenterOffset.getEntry(i) + scale * work1.getEntry(i); + alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), + FastMath.min(upperDifference.getEntry(i), tmp))); + } + // Computing 2nd power + final double d1 = HALF * gw * scale; + cauchy = d1 * d1; + } else { + // Computing 2nd power + final double d1 = gw + HALF * curv; + cauchy = d1 * d1; + } + + // If IFLAG is zero, then XALT is calculated as before after reversing + // the sign of GLAG. Thus two XALT vectors become available. The one that + // is chosen is the one that gives the larger value of CAUCHY. + + if (iflag == 0) { + for (int i = 0; i < n; i++) { + glag.setEntry(i, -glag.getEntry(i)); + work2.setEntry(i, alternativeNewPoint.getEntry(i)); + } + csave = cauchy; + iflag = 1; + } else { + break; + } + } + if (csave > cauchy) { + for (int i = 0; i < n; i++) { + alternativeNewPoint.setEntry(i, work2.getEntry(i)); + } + cauchy = csave; + } + + return new double[] { alpha, cauchy }; + } // altmov + + // ---------------------------------------------------------------------------------------- + + /** + * SUBROUTINE PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ, + * BMAT and ZMAT for the first iteration, and it maintains the values of + * NF and KOPT. The vector X is also changed by PRELIM. + * + * The arguments N, NPT, X, XL, XU, RHOBEG, IPRINT and MAXFUN are the + * same as the corresponding arguments in SUBROUTINE BOBYQA. + * The arguments XBASE, XPT, FVAL, HQ, PQ, BMAT, ZMAT, NDIM, SL and SU + * are the same as the corresponding arguments in BOBYQB, the elements + * of SL and SU being set in BOBYQA. + * GOPT is usually the gradient of the quadratic model at XOPT+XBASE, but + * it is set by PRELIM to the gradient of the quadratic model at XBASE. + * If XOPT is nonzero, BOBYQB will change it to its usual value later. + * NF is maintaned as the number of calls of CALFUN so far. + * KOPT will be such that the least calculated value of F so far is at + * the point XPT(KOPT,.)+XBASE in the space of the variables. + * + * @param lowerBound Lower bounds. + * @param upperBound Upper bounds. + */ + private void prelim(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int ndim = bMatrix.getRowDimension(); + + final double rhosq = initialTrustRegionRadius * initialTrustRegionRadius; + final double recip = 1d / rhosq; + final int np = n + 1; + + // Set XBASE to the initial vector of variables, and set the initial + // elements of XPT, BMAT, HQ, PQ and ZMAT to zero. + + for (int j = 0; j < n; j++) { + originShift.setEntry(j, currentBest.getEntry(j)); + for (int k = 0; k < npt; k++) { + interpolationPoints.setEntry(k, j, ZERO); + } + for (int i = 0; i < ndim; i++) { + bMatrix.setEntry(i, j, ZERO); + } + } + for (int i = 0, max = n * np / 2; i < max; i++) { + modelSecondDerivativesValues.setEntry(i, ZERO); + } + for (int k = 0; k < npt; k++) { + modelSecondDerivativesParameters.setEntry(k, ZERO); + for (int j = 0, max = npt - np; j < max; j++) { + zMatrix.setEntry(k, j, ZERO); + } + } + + // Begin the initialization procedure. NF becomes one more than the number + // of function values so far. The coordinates of the displacement of the + // next initial interpolation point from XBASE are set in XPT(NF+1,.). + + int ipt = 0; + int jpt = 0; + double fbeg = Double.NaN; + do { + final int nfm = getEvaluations(); + final int nfx = nfm - n; + final int nfmm = nfm - 1; + final int nfxm = nfx - 1; + double stepa = 0; + double stepb = 0; + if (nfm <= 2 * n) { + if (nfm >= 1 && + nfm <= n) { + stepa = initialTrustRegionRadius; + if (upperDifference.getEntry(nfmm) == ZERO) { + stepa = -stepa; + // throw new PathIsExploredException(); // XXX + } + interpolationPoints.setEntry(nfm, nfmm, stepa); + } else if (nfm > n) { + stepa = interpolationPoints.getEntry(nfx, nfxm); + stepb = -initialTrustRegionRadius; + if (lowerDifference.getEntry(nfxm) == ZERO) { + stepb = FastMath.min(TWO * initialTrustRegionRadius, upperDifference.getEntry(nfxm)); + // throw new PathIsExploredException(); // XXX + } + if (upperDifference.getEntry(nfxm) == ZERO) { + stepb = FastMath.max(-TWO * initialTrustRegionRadius, lowerDifference.getEntry(nfxm)); + // throw new PathIsExploredException(); // XXX + } + interpolationPoints.setEntry(nfm, nfxm, stepb); + } + } else { + final int tmp1 = (nfm - np) / n; + jpt = nfm - tmp1 * n - n; + ipt = jpt + tmp1; + if (ipt > n) { + final int tmp2 = jpt; + jpt = ipt - n; + ipt = tmp2; +// throw new PathIsExploredException(); // XXX + } + final int iptMinus1 = ipt - 1; + final int jptMinus1 = jpt - 1; + interpolationPoints.setEntry(nfm, iptMinus1, interpolationPoints.getEntry(ipt, iptMinus1)); + interpolationPoints.setEntry(nfm, jptMinus1, interpolationPoints.getEntry(jpt, jptMinus1)); + } + + // Calculate the next value of F. The least function value so far and + // its index are required. + + for (int j = 0; j < n; j++) { + currentBest.setEntry(j, FastMath.min(FastMath.max(lowerBound[j], + originShift.getEntry(j) + interpolationPoints.getEntry(nfm, j)), + upperBound[j])); + if (interpolationPoints.getEntry(nfm, j) == lowerDifference.getEntry(j)) { + currentBest.setEntry(j, lowerBound[j]); + } + if (interpolationPoints.getEntry(nfm, j) == upperDifference.getEntry(j)) { + currentBest.setEntry(j, upperBound[j]); + } + } + + final double objectiveValue = computeObjectiveValue(currentBest.toArray()); + final double f = isMinimize ? objectiveValue : -objectiveValue; + final int numEval = getEvaluations(); // nfm + 1 + fAtInterpolationPoints.setEntry(nfm, f); + + if (numEval == 1) { + fbeg = f; + trustRegionCenterInterpolationPointIndex = 0; + } else if (f < fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)) { + trustRegionCenterInterpolationPointIndex = nfm; + } + + // Set the nonzero initial elements of BMAT and the quadratic model in the + // cases when NF is at most 2*N+1. If NF exceeds N+1, then the positions + // of the NF-th and (NF-N)-th interpolation points may be switched, in + // order that the function value at the first of them contributes to the + // off-diagonal second derivative terms of the initial quadratic model. + + if (numEval <= 2 * n + 1) { + if (numEval >= 2 && + numEval <= n + 1) { + gradientAtTrustRegionCenter.setEntry(nfmm, (f - fbeg) / stepa); + if (npt < numEval + n) { + final double oneOverStepA = ONE / stepa; + bMatrix.setEntry(0, nfmm, -oneOverStepA); + bMatrix.setEntry(nfm, nfmm, oneOverStepA); + bMatrix.setEntry(npt + nfmm, nfmm, -HALF * rhosq); + // throw new PathIsExploredException(); // XXX + } + } else if (numEval >= n + 2) { + final int ih = nfx * (nfx + 1) / 2 - 1; + final double tmp = (f - fbeg) / stepb; + final double diff = stepb - stepa; + modelSecondDerivativesValues.setEntry(ih, TWO * (tmp - gradientAtTrustRegionCenter.getEntry(nfxm)) / diff); + gradientAtTrustRegionCenter.setEntry(nfxm, (gradientAtTrustRegionCenter.getEntry(nfxm) * stepb - tmp * stepa) / diff); + if (stepa * stepb < ZERO && f < fAtInterpolationPoints.getEntry(nfm - n)) { + fAtInterpolationPoints.setEntry(nfm, fAtInterpolationPoints.getEntry(nfm - n)); + fAtInterpolationPoints.setEntry(nfm - n, f); + if (trustRegionCenterInterpolationPointIndex == nfm) { + trustRegionCenterInterpolationPointIndex = nfm - n; + } + interpolationPoints.setEntry(nfm - n, nfxm, stepb); + interpolationPoints.setEntry(nfm, nfxm, stepa); + } + bMatrix.setEntry(0, nfxm, -(stepa + stepb) / (stepa * stepb)); + bMatrix.setEntry(nfm, nfxm, -HALF / interpolationPoints.getEntry(nfm - n, nfxm)); + bMatrix.setEntry(nfm - n, nfxm, + -bMatrix.getEntry(0, nfxm) - bMatrix.getEntry(nfm, nfxm)); + zMatrix.setEntry(0, nfxm, FastMath.sqrt(TWO) / (stepa * stepb)); + zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) / rhosq); + // zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) * recip); // XXX "testAckley" and "testDiffPow" fail. + zMatrix.setEntry(nfm - n, nfxm, + -zMatrix.getEntry(0, nfxm) - zMatrix.getEntry(nfm, nfxm)); + } + + // Set the off-diagonal second derivatives of the Lagrange functions and + // the initial quadratic model. + + } else { + zMatrix.setEntry(0, nfxm, recip); + zMatrix.setEntry(nfm, nfxm, recip); + zMatrix.setEntry(ipt, nfxm, -recip); + zMatrix.setEntry(jpt, nfxm, -recip); + + final int ih = ipt * (ipt - 1) / 2 + jpt - 1; + final double tmp = interpolationPoints.getEntry(nfm, ipt - 1) * interpolationPoints.getEntry(nfm, jpt - 1); + modelSecondDerivativesValues.setEntry(ih, (fbeg - fAtInterpolationPoints.getEntry(ipt) - fAtInterpolationPoints.getEntry(jpt) + f) / tmp); +// throw new PathIsExploredException(); // XXX + } + } while (getEvaluations() < npt); + } // prelim + + + // ---------------------------------------------------------------------------------------- + + /** + * A version of the truncated conjugate gradient is applied. If a line + * search is restricted by a constraint, then the procedure is restarted, + * the values of the variables that are at their bounds being fixed. If + * the trust region boundary is reached, then further changes may be made + * to D, each one being in the two dimensional space that is spanned + * by the current D and the gradient of Q at XOPT+D, staying on the trust + * region boundary. Termination occurs when the reduction in Q seems to + * be close to the greatest reduction that can be achieved. + * The arguments N, NPT, XPT, XOPT, GOPT, HQ, PQ, SL and SU have the same + * meanings as the corresponding arguments of BOBYQB. + * DELTA is the trust region radius for the present calculation, which + * seeks a small value of the quadratic model within distance DELTA of + * XOPT subject to the bounds on the variables. + * XNEW will be set to a new vector of variables that is approximately + * the one that minimizes the quadratic model within the trust region + * subject to the SL and SU constraints on the variables. It satisfies + * as equations the bounds that become active during the calculation. + * D is the calculated trial step from XOPT, generated iteratively from an + * initial value of zero. Thus XNEW is XOPT+D after the final iteration. + * GNEW holds the gradient of the quadratic model at XOPT+D. It is updated + * when D is updated. + * xbdi.get( is a working space vector. For I=1,2,...,N, the element xbdi.get((I) is + * set to -1.0, 0.0, or 1.0, the value being nonzero if and only if the + * I-th variable has become fixed at a bound, the bound being SL(I) or + * SU(I) in the case xbdi.get((I)=-1.0 or xbdi.get((I)=1.0, respectively. This + * information is accumulated during the construction of XNEW. + * The arrays S, HS and HRED are also used for working space. They hold the + * current search direction, and the changes in the gradient of Q along S + * and the reduced D, respectively, where the reduced D is the same as D, + * except that the components of the fixed variables are zero. + * DSQ will be set to the square of the length of XNEW-XOPT. + * CRVMIN is set to zero if D reaches the trust region boundary. Otherwise + * it is set to the least curvature of H that occurs in the conjugate + * gradient searches that are not restricted by any constraints. The + * value CRVMIN=-1.0D0 is set, however, if all of these searches are + * constrained. + * @param delta + * @param gnew + * @param xbdi + * @param s + * @param hs + * @param hred + */ + private double[] trsbox( + double delta, + ArrayRealVector gnew, + ArrayRealVector xbdi, + ArrayRealVector s, + ArrayRealVector hs, + ArrayRealVector hred + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + + double dsq = Double.NaN; + double crvmin = Double.NaN; + + // Local variables + double ds; + int iu; + double dhd, dhs, cth, shs, sth, ssq, beta=0, sdec, blen; + int iact = -1; + int nact = 0; + double angt = 0, qred; + int isav; + double temp = 0, xsav = 0, xsum = 0, angbd = 0, dredg = 0, sredg = 0; + int iterc; + double resid = 0, delsq = 0, ggsav = 0, tempa = 0, tempb = 0, + redmax = 0, dredsq = 0, redsav = 0, gredsq = 0, rednew = 0; + int itcsav = 0; + double rdprev = 0, rdnext = 0, stplen = 0, stepsq = 0; + int itermax = 0; + + // Set some constants. + + // Function Body + + // The sign of GOPT(I) gives the sign of the change to the I-th variable + // that will reduce Q from its value at XOPT. Thus xbdi.get((I) shows whether + // or not to fix the I-th variable at one of its bounds initially, with + // NACT being set to the number of fixed variables. D and GNEW are also + // set for the first iteration. DELSQ is the upper bound on the sum of + // squares of the free variables. QRED is the reduction in Q so far. + + iterc = 0; + nact = 0; + for (int i = 0; i < n; i++) { + xbdi.setEntry(i, ZERO); + if (trustRegionCenterOffset.getEntry(i) <= lowerDifference.getEntry(i)) { + if (gradientAtTrustRegionCenter.getEntry(i) >= ZERO) { + xbdi.setEntry(i, MINUS_ONE); + } + } else if (trustRegionCenterOffset.getEntry(i) >= upperDifference.getEntry(i) && + gradientAtTrustRegionCenter.getEntry(i) <= ZERO) { + xbdi.setEntry(i, ONE); + } + if (xbdi.getEntry(i) != ZERO) { + ++nact; + } + trialStepPoint.setEntry(i, ZERO); + gnew.setEntry(i, gradientAtTrustRegionCenter.getEntry(i)); + } + delsq = delta * delta; + qred = ZERO; + crvmin = MINUS_ONE; + + // Set the next search direction of the conjugate gradient method. It is + // the steepest descent direction initially and when the iterations are + // restarted because a variable has just been fixed by a bound, and of + // course the components of the fixed variables are zero. ITERMAX is an + // upper bound on the indices of the conjugate gradient iterations. + + int state = 20; + for(;;) { + switch (state) { + case 20: { + printState(20); // XXX + beta = ZERO; + } + case 30: { + printState(30); // XXX + stepsq = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) != ZERO) { + s.setEntry(i, ZERO); + } else if (beta == ZERO) { + s.setEntry(i, -gnew.getEntry(i)); + } else { + s.setEntry(i, beta * s.getEntry(i) - gnew.getEntry(i)); + } + // Computing 2nd power + final double d1 = s.getEntry(i); + stepsq += d1 * d1; + } + if (stepsq == ZERO) { + state = 190; break; + } + if (beta == ZERO) { + gredsq = stepsq; + itermax = iterc + n - nact; + } + if (gredsq * delsq <= qred * 1e-4 * qred) { + state = 190; break; + } + + // Multiply the search direction by the second derivative matrix of Q and + // calculate some scalars for the choice of steplength. Then set BLEN to + // the length of the the step to the trust region boundary and STPLEN to + // the steplength, ignoring the simple bounds. + + state = 210; break; + } + case 50: { + printState(50); // XXX + resid = delsq; + ds = ZERO; + shs = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(i); + resid -= d1 * d1; + ds += s.getEntry(i) * trialStepPoint.getEntry(i); + shs += s.getEntry(i) * hs.getEntry(i); + } + } + if (resid <= ZERO) { + state = 90; break; + } + temp = FastMath.sqrt(stepsq * resid + ds * ds); + if (ds < ZERO) { + blen = (temp - ds) / stepsq; + } else { + blen = resid / (temp + ds); + } + stplen = blen; + if (shs > ZERO) { + // Computing MIN + stplen = FastMath.min(blen, gredsq / shs); + } + + // Reduce STPLEN if necessary in order to preserve the simple bounds, + // letting IACT be the index of the new constrained variable. + + iact = -1; + for (int i = 0; i < n; i++) { + if (s.getEntry(i) != ZERO) { + xsum = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i); + if (s.getEntry(i) > ZERO) { + temp = (upperDifference.getEntry(i) - xsum) / s.getEntry(i); + } else { + temp = (lowerDifference.getEntry(i) - xsum) / s.getEntry(i); + } + if (temp < stplen) { + stplen = temp; + iact = i; + } + } + } + + // Update CRVMIN, GNEW and D. Set SDEC to the decrease that occurs in Q. + + sdec = ZERO; + if (stplen > ZERO) { + ++iterc; + temp = shs / stepsq; + if (iact == -1 && temp > ZERO) { + crvmin = FastMath.min(crvmin,temp); + if (crvmin == MINUS_ONE) { + crvmin = temp; + } + } + ggsav = gredsq; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + gnew.setEntry(i, gnew.getEntry(i) + stplen * hs.getEntry(i)); + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + final double d1 = gnew.getEntry(i); + gredsq += d1 * d1; + } + trialStepPoint.setEntry(i, trialStepPoint.getEntry(i) + stplen * s.getEntry(i)); + } + // Computing MAX + final double d1 = stplen * (ggsav - HALF * stplen * shs); + sdec = FastMath.max(d1, ZERO); + qred += sdec; + } + + // Restart the conjugate gradient method if it has hit a new bound. + + if (iact >= 0) { + ++nact; + xbdi.setEntry(iact, ONE); + if (s.getEntry(iact) < ZERO) { + xbdi.setEntry(iact, MINUS_ONE); + } + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(iact); + delsq -= d1 * d1; + if (delsq <= ZERO) { + state = 190; break; + } + state = 20; break; + } + + // If STPLEN is less than BLEN, then either apply another conjugate + // gradient iteration or RETURN. + + if (stplen < blen) { + if (iterc == itermax) { + state = 190; break; + } + if (sdec <= qred * .01) { + state = 190; break; + } + beta = gredsq / ggsav; + state = 30; break; + } + } + case 90: { + printState(90); // XXX + crvmin = ZERO; + + // Prepare for the alternative iteration by calculating some scalars + // and by multiplying the reduced D by the second derivative matrix of + // Q, where S holds the reduced D in the call of GGMULT. + + } + case 100: { + printState(100); // XXX + if (nact >= n - 1) { + state = 190; break; + } + dredsq = ZERO; + dredg = ZERO; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + // Computing 2nd power + double d1 = trialStepPoint.getEntry(i); + dredsq += d1 * d1; + dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i); + // Computing 2nd power + d1 = gnew.getEntry(i); + gredsq += d1 * d1; + s.setEntry(i, trialStepPoint.getEntry(i)); + } else { + s.setEntry(i, ZERO); + } + } + itcsav = iterc; + state = 210; break; + // Let the search direction S be a linear combination of the reduced D + // and the reduced G that is orthogonal to the reduced D. + } + case 120: { + printState(120); // XXX + ++iterc; + temp = gredsq * dredsq - dredg * dredg; + if (temp <= qred * 1e-4 * qred) { + state = 190; break; + } + temp = FastMath.sqrt(temp); + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + s.setEntry(i, (dredg * trialStepPoint.getEntry(i) - dredsq * gnew.getEntry(i)) / temp); + } else { + s.setEntry(i, ZERO); + } + } + sredg = -temp; + + // By considering the simple bounds on the variables, calculate an upper + // bound on the tangent of half the angle of the alternative iteration, + // namely ANGBD, except that, if already a free variable has reached a + // bound, there is a branch back to label 100 after fixing that variable. + + angbd = ONE; + iact = -1; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + tempa = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i) - lowerDifference.getEntry(i); + tempb = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i) - trialStepPoint.getEntry(i); + if (tempa <= ZERO) { + ++nact; + xbdi.setEntry(i, MINUS_ONE); + state = 100; break; + } else if (tempb <= ZERO) { + ++nact; + xbdi.setEntry(i, ONE); + state = 100; break; + } + // Computing 2nd power + double d1 = trialStepPoint.getEntry(i); + // Computing 2nd power + double d2 = s.getEntry(i); + ssq = d1 * d1 + d2 * d2; + // Computing 2nd power + d1 = trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i); + temp = ssq - d1 * d1; + if (temp > ZERO) { + temp = FastMath.sqrt(temp) - s.getEntry(i); + if (angbd * temp > tempa) { + angbd = tempa / temp; + iact = i; + xsav = MINUS_ONE; + } + } + // Computing 2nd power + d1 = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i); + temp = ssq - d1 * d1; + if (temp > ZERO) { + temp = FastMath.sqrt(temp) + s.getEntry(i); + if (angbd * temp > tempb) { + angbd = tempb / temp; + iact = i; + xsav = ONE; + } + } + } + } + + // Calculate HHD and some curvatures for the alternative iteration. + + state = 210; break; + } + case 150: { + printState(150); // XXX + shs = ZERO; + dhs = ZERO; + dhd = ZERO; + for (int i = 0; i < n; i++) { + if (xbdi.getEntry(i) == ZERO) { + shs += s.getEntry(i) * hs.getEntry(i); + dhs += trialStepPoint.getEntry(i) * hs.getEntry(i); + dhd += trialStepPoint.getEntry(i) * hred.getEntry(i); + } + } + + // Seek the greatest reduction in Q for a range of equally spaced values + // of ANGT in [0,ANGBD], where ANGT is the tangent of half the angle of + // the alternative iteration. + + redmax = ZERO; + isav = -1; + redsav = ZERO; + iu = (int) (angbd * 17. + 3.1); + for (int i = 0; i < iu; i++) { + angt = angbd * i / iu; + sth = (angt + angt) / (ONE + angt * angt); + temp = shs + angt * (angt * dhd - dhs - dhs); + rednew = sth * (angt * dredg - sredg - HALF * sth * temp); + if (rednew > redmax) { + redmax = rednew; + isav = i; + rdprev = redsav; + } else if (i == isav + 1) { + rdnext = rednew; + } + redsav = rednew; + } + + // Return if the reduction is zero. Otherwise, set the sine and cosine + // of the angle of the alternative iteration, and calculate SDEC. + + if (isav < 0) { + state = 190; break; + } + if (isav < iu) { + temp = (rdnext - rdprev) / (redmax + redmax - rdprev - rdnext); + angt = angbd * (isav + HALF * temp) / iu; + } + cth = (ONE - angt * angt) / (ONE + angt * angt); + sth = (angt + angt) / (ONE + angt * angt); + temp = shs + angt * (angt * dhd - dhs - dhs); + sdec = sth * (angt * dredg - sredg - HALF * sth * temp); + if (sdec <= ZERO) { + state = 190; break; + } + + // Update GNEW, D and HRED. If the angle of the alternative iteration + // is restricted by a bound on a free variable, that variable is fixed + // at the bound. + + dredg = ZERO; + gredsq = ZERO; + for (int i = 0; i < n; i++) { + gnew.setEntry(i, gnew.getEntry(i) + (cth - ONE) * hred.getEntry(i) + sth * hs.getEntry(i)); + if (xbdi.getEntry(i) == ZERO) { + trialStepPoint.setEntry(i, cth * trialStepPoint.getEntry(i) + sth * s.getEntry(i)); + dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i); + // Computing 2nd power + final double d1 = gnew.getEntry(i); + gredsq += d1 * d1; + } + hred.setEntry(i, cth * hred.getEntry(i) + sth * hs.getEntry(i)); + } + qred += sdec; + if (iact >= 0 && isav == iu) { + ++nact; + xbdi.setEntry(iact, xsav); + state = 100; break; + } + + // If SDEC is sufficiently small, then RETURN after setting XNEW to + // XOPT+D, giving careful attention to the bounds. + + if (sdec > qred * .01) { + state = 120; break; + } + } + case 190: { + printState(190); // XXX + dsq = ZERO; + for (int i = 0; i < n; i++) { + // Computing MAX + // Computing MIN + final double min = FastMath.min(trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i), + upperDifference.getEntry(i)); + newPoint.setEntry(i, FastMath.max(min, lowerDifference.getEntry(i))); + if (xbdi.getEntry(i) == MINUS_ONE) { + newPoint.setEntry(i, lowerDifference.getEntry(i)); + } + if (xbdi.getEntry(i) == ONE) { + newPoint.setEntry(i, upperDifference.getEntry(i)); + } + trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i)); + // Computing 2nd power + final double d1 = trialStepPoint.getEntry(i); + dsq += d1 * d1; + } + return new double[] { dsq, crvmin }; + // The following instructions multiply the current S-vector by the second + // derivative matrix of the quadratic model, putting the product in HS. + // They are reached from three different parts of the software above and + // they can be regarded as an external subroutine. + } + case 210: { + printState(210); // XXX + int ih = 0; + for (int j = 0; j < n; j++) { + hs.setEntry(j, ZERO); + for (int i = 0; i <= j; i++) { + if (i < j) { + hs.setEntry(j, hs.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(i)); + } + hs.setEntry(i, hs.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(j)); + ih++; + } + } + final RealVector tmp = interpolationPoints.operate(s).ebeMultiply(modelSecondDerivativesParameters); + for (int k = 0; k < npt; k++) { + if (modelSecondDerivativesParameters.getEntry(k) != ZERO) { + for (int i = 0; i < n; i++) { + hs.setEntry(i, hs.getEntry(i) + tmp.getEntry(k) * interpolationPoints.getEntry(k, i)); + } + } + } + if (crvmin != ZERO) { + state = 50; break; + } + if (iterc > itcsav) { + state = 150; break; + } + for (int i = 0; i < n; i++) { + hred.setEntry(i, hs.getEntry(i)); + } + state = 120; break; + } + default: { + throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "trsbox"); + }} + } + } // trsbox + + // ---------------------------------------------------------------------------------------- + + /** + * The arrays BMAT and ZMAT are updated, as required by the new position + * of the interpolation point that has the index KNEW. The vector VLAG has + * N+NPT components, set on entry to the first NPT and last N components + * of the product Hw in equation (4.11) of the Powell (2006) paper on + * NEWUOA. Further, BETA is set on entry to the value of the parameter + * with that name, and DENOM is set to the denominator of the updating + * formula. Elements of ZMAT may be treated as zero if their moduli are + * at most ZTEST. The first NDIM elements of W are used for working space. + * @param beta + * @param denom + * @param knew + */ + private void update( + double beta, + double denom, + int knew + ) { + printMethod(); // XXX + + final int n = currentBest.getDimension(); + final int npt = numberOfInterpolationPoints; + final int nptm = npt - n - 1; + + // XXX Should probably be split into two arrays. + final ArrayRealVector work = new ArrayRealVector(npt + n); + + double ztest = ZERO; + for (int k = 0; k < npt; k++) { + for (int j = 0; j < nptm; j++) { + // Computing MAX + ztest = FastMath.max(ztest, FastMath.abs(zMatrix.getEntry(k, j))); + } + } + ztest *= 1e-20; + + // Apply the rotations that put zeros in the KNEW-th row of ZMAT. + + for (int j = 1; j < nptm; j++) { + final double d1 = zMatrix.getEntry(knew, j); + if (FastMath.abs(d1) > ztest) { + // Computing 2nd power + final double d2 = zMatrix.getEntry(knew, 0); + // Computing 2nd power + final double d3 = zMatrix.getEntry(knew, j); + final double d4 = FastMath.sqrt(d2 * d2 + d3 * d3); + final double d5 = zMatrix.getEntry(knew, 0) / d4; + final double d6 = zMatrix.getEntry(knew, j) / d4; + for (int i = 0; i < npt; i++) { + final double d7 = d5 * zMatrix.getEntry(i, 0) + d6 * zMatrix.getEntry(i, j); + zMatrix.setEntry(i, j, d5 * zMatrix.getEntry(i, j) - d6 * zMatrix.getEntry(i, 0)); + zMatrix.setEntry(i, 0, d7); + } + } + zMatrix.setEntry(knew, j, ZERO); + } + + // Put the first NPT components of the KNEW-th column of HLAG into W, + // and calculate the parameters of the updating formula. + + for (int i = 0; i < npt; i++) { + work.setEntry(i, zMatrix.getEntry(knew, 0) * zMatrix.getEntry(i, 0)); + } + final double alpha = work.getEntry(knew); + final double tau = lagrangeValuesAtNewPoint.getEntry(knew); + lagrangeValuesAtNewPoint.setEntry(knew, lagrangeValuesAtNewPoint.getEntry(knew) - ONE); + + // Complete the updating of ZMAT. + + final double sqrtDenom = FastMath.sqrt(denom); + final double d1 = tau / sqrtDenom; + final double d2 = zMatrix.getEntry(knew, 0) / sqrtDenom; + for (int i = 0; i < npt; i++) { + zMatrix.setEntry(i, 0, + d1 * zMatrix.getEntry(i, 0) - d2 * lagrangeValuesAtNewPoint.getEntry(i)); + } + + // Finally, update the matrix BMAT. + + for (int j = 0; j < n; j++) { + final int jp = npt + j; + work.setEntry(jp, bMatrix.getEntry(knew, j)); + final double d3 = (alpha * lagrangeValuesAtNewPoint.getEntry(jp) - tau * work.getEntry(jp)) / denom; + final double d4 = (-beta * work.getEntry(jp) - tau * lagrangeValuesAtNewPoint.getEntry(jp)) / denom; + for (int i = 0; i <= jp; i++) { + bMatrix.setEntry(i, j, + bMatrix.getEntry(i, j) + d3 * lagrangeValuesAtNewPoint.getEntry(i) + d4 * work.getEntry(i)); + if (i >= npt) { + bMatrix.setEntry(jp, (i - npt), bMatrix.getEntry(i, j)); + } + } + } + } // update + + /** + * Performs validity checks. + * + * @param lowerBound Lower bounds (constraints) of the objective variables. + * @param upperBound Upperer bounds (constraints) of the objective variables. + */ + private void setup(double[] lowerBound, + double[] upperBound) { + printMethod(); // XXX + + double[] init = getStartPoint(); + final int dimension = init.length; + + // Check problem dimension. + if (dimension < MINIMUM_PROBLEM_DIMENSION) { + throw new NumberIsTooSmallException(dimension, MINIMUM_PROBLEM_DIMENSION, true); + } + // Check number of interpolation points. + final int[] nPointsInterval = { dimension + 2, (dimension + 2) * (dimension + 1) / 2 }; + if (numberOfInterpolationPoints < nPointsInterval[0] || + numberOfInterpolationPoints > nPointsInterval[1]) { + throw new OutOfRangeException(LocalizedFormats.NUMBER_OF_INTERPOLATION_POINTS, + numberOfInterpolationPoints, + nPointsInterval[0], + nPointsInterval[1]); + } + + // Initialize bound differences. + boundDifference = new double[dimension]; + + double requiredMinDiff = 2 * initialTrustRegionRadius; + double minDiff = Double.POSITIVE_INFINITY; + for (int i = 0; i < dimension; i++) { + boundDifference[i] = upperBound[i] - lowerBound[i]; + minDiff = FastMath.min(minDiff, boundDifference[i]); + } + if (minDiff < requiredMinDiff) { + initialTrustRegionRadius = minDiff / 3.0; + } + + // Initialize the data structures used by the "bobyqa" method. + bMatrix = new Array2DRowRealMatrix(dimension + numberOfInterpolationPoints, + dimension); + zMatrix = new Array2DRowRealMatrix(numberOfInterpolationPoints, + numberOfInterpolationPoints - dimension - 1); + interpolationPoints = new Array2DRowRealMatrix(numberOfInterpolationPoints, + dimension); + originShift = new ArrayRealVector(dimension); + fAtInterpolationPoints = new ArrayRealVector(numberOfInterpolationPoints); + trustRegionCenterOffset = new ArrayRealVector(dimension); + gradientAtTrustRegionCenter = new ArrayRealVector(dimension); + lowerDifference = new ArrayRealVector(dimension); + upperDifference = new ArrayRealVector(dimension); + modelSecondDerivativesParameters = new ArrayRealVector(numberOfInterpolationPoints); + newPoint = new ArrayRealVector(dimension); + alternativeNewPoint = new ArrayRealVector(dimension); + trialStepPoint = new ArrayRealVector(dimension); + lagrangeValuesAtNewPoint = new ArrayRealVector(dimension + numberOfInterpolationPoints); + modelSecondDerivativesValues = new ArrayRealVector(dimension * (dimension + 1) / 2); + } + + // XXX utility for figuring out call sequence. + private static String caller(int n) { + final Throwable t = new Throwable(); + final StackTraceElement[] elements = t.getStackTrace(); + final StackTraceElement e = elements[n]; + return e.getMethodName() + " (at line " + e.getLineNumber() + ")"; + } + // XXX utility for figuring out call sequence. + private static void printState(int s) { + // System.out.println(caller(2) + ": state " + s); + } + // XXX utility for figuring out call sequence. + private static void printMethod() { + // System.out.println(caller(2)); + } + + /** + * Marker for code paths that are not explored with the current unit tests. + * If the path becomes explored, it should just be removed from the code. + */ + private static class PathIsExploredException extends RuntimeException { + /** Serializable UID. */ + private static final long serialVersionUID = 745350979634801853L; + + /** Message string. */ + private static final String PATH_IS_EXPLORED + = "If this exception is thrown, just remove it from the code"; + + PathIsExploredException() { + super(PATH_IS_EXPLORED + " " + BOBYQAOptimizer.caller(3)); + } + } +} +//CHECKSTYLE: resume all diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.java new file mode 100644 index 000000000..3dcbac556 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.optimization.SimpleBounds; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; + +/** + * Base class for implementing optimizers for multivariate scalar functions. + * This base class handles the boiler-plate methods associated to thresholds, + * evaluations counting, initial guess and simple bounds settings. + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.2 + */ +@Deprecated +public abstract class BaseAbstractMultivariateOptimizer + implements BaseMultivariateOptimizer { + /** Evaluations counter. */ + protected final Incrementor evaluations = new Incrementor(); + /** Convergence checker. */ + private ConvergenceChecker checker; + /** Type of optimization. */ + private GoalType goal; + /** Initial guess. */ + private double[] start; + /** Lower bounds. */ + private double[] lowerBound; + /** Upper bounds. */ + private double[] upperBound; + /** Objective function. */ + private MultivariateFunction function; + + /** + * Simple constructor with default settings. + * The convergence check is set to a {@link SimpleValueChecker}. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + protected BaseAbstractMultivariateOptimizer() { + this(new SimpleValueChecker()); + } + /** + * @param checker Convergence checker. + */ + protected BaseAbstractMultivariateOptimizer(ConvergenceChecker checker) { + this.checker = checker; + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return checker; + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at the specified point. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + */ + protected double computeObjectiveValue(double[] point) { + try { + evaluations.incrementCount(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return function.value(point); + } + + /** + * {@inheritDoc} + * + * @deprecated As of 3.1. Please use + * {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])} + * instead. + */ + @Deprecated + public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double[] startPoint) { + return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint)); + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param goalType Optimization type. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link InitialGuess}
      • + *
      • {@link SimpleBounds}
      • + *
      + * @return the point/value pair giving the optimal value of the objective + * function. + * @since 3.1 + */ + public PointValuePair optimize(int maxEval, + FUNC f, + GoalType goalType, + OptimizationData... optData) { + return optimizeInternal(maxEval, f, goalType, optData); + } + + /** + * Optimize an objective function. + * + * @param f Objective function. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param startPoint Start point for optimization. + * @param maxEval Maximum number of function evaluations. + * @return the point/value pair giving the optimal value for objective + * function. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + * @deprecated As of 3.1. Please use + * {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])} + * instead. + */ + @Deprecated + protected PointValuePair optimizeInternal(int maxEval, FUNC f, GoalType goalType, + double[] startPoint) { + return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint)); + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param goalType Optimization type. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link InitialGuess}
      • + *
      • {@link SimpleBounds}
      • + *
      + * @return the point/value pair giving the optimal value of the objective + * function. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @since 3.1 + */ + protected PointValuePair optimizeInternal(int maxEval, + FUNC f, + GoalType goalType, + OptimizationData... optData) + throws TooManyEvaluationsException { + // Set internal state. + evaluations.setMaximalCount(maxEval); + evaluations.resetCount(); + function = f; + goal = goalType; + // Retrieve other settings. + parseOptimizationData(optData); + // Check input consistency. + checkParameters(); + // Perform computation. + return doOptimize(); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link InitialGuess}
      • + *
      • {@link SimpleBounds}
      • + *
      + */ + private void parseOptimizationData(OptimizationData... optData) { + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof InitialGuess) { + start = ((InitialGuess) data).getInitialGuess(); + continue; + } + if (data instanceof SimpleBounds) { + final SimpleBounds bounds = (SimpleBounds) data; + lowerBound = bounds.getLower(); + upperBound = bounds.getUpper(); + continue; + } + } + } + + /** + * @return the optimization type. + */ + public GoalType getGoalType() { + return goal; + } + + /** + * @return the initial guess. + */ + public double[] getStartPoint() { + return start == null ? null : start.clone(); + } + /** + * @return the lower bounds. + * @since 3.1 + */ + public double[] getLowerBound() { + return lowerBound == null ? null : lowerBound.clone(); + } + /** + * @return the upper bounds. + * @since 3.1 + */ + public double[] getUpperBound() { + return upperBound == null ? null : upperBound.clone(); + } + + /** + * Perform the bulk of the optimization algorithm. + * + * @return the point/value pair giving the optimal value of the + * objective function. + */ + protected abstract PointValuePair doOptimize(); + + /** + * Check parameters consistency. + */ + private void checkParameters() { + if (start != null) { + final int dim = start.length; + if (lowerBound != null) { + if (lowerBound.length != dim) { + throw new DimensionMismatchException(lowerBound.length, dim); + } + for (int i = 0; i < dim; i++) { + final double v = start[i]; + final double lo = lowerBound[i]; + if (v < lo) { + throw new NumberIsTooSmallException(v, lo, true); + } + } + } + if (upperBound != null) { + if (upperBound.length != dim) { + throw new DimensionMismatchException(upperBound.length, dim); + } + for (int i = 0; i < dim; i++) { + final double v = start[i]; + final double hi = upperBound[i]; + if (v > hi) { + throw new NumberIsTooLargeException(v, hi, true); + } + } + } + + // If the bounds were not specified, the allowed interval is + // assumed to be [-inf, +inf]. + if (lowerBound == null) { + lowerBound = new double[dim]; + for (int i = 0; i < dim; i++) { + lowerBound[i] = Double.NEGATIVE_INFINITY; + } + } + if (upperBound == null) { + upperBound = new double[dim]; + for (int i = 0; i < dim; i++) { + upperBound[i] = Double.POSITIVE_INFINITY; + } + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.java new file mode 100644 index 000000000..8c23df31b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateSimpleBoundsOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.optimization.SimpleBounds; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; + +/** + * Base class for implementing optimizers for multivariate scalar functions, + * subject to simple bounds: The valid range of the parameters is an interval. + * The interval can possibly be infinite (in one or both directions). + * This base class handles the boiler-plate methods associated to thresholds + * settings, iterations and evaluations counting. + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + * @deprecated As of 3.1 since the {@link BaseAbstractMultivariateOptimizer + * base class} contains similar functionality. + */ +@Deprecated +public abstract class BaseAbstractMultivariateSimpleBoundsOptimizer + extends BaseAbstractMultivariateOptimizer + implements BaseMultivariateOptimizer, + BaseMultivariateSimpleBoundsOptimizer { + /** + * Simple constructor with default settings. + * The convergence checker is set to a + * {@link SimpleValueChecker}. + * + * @see BaseAbstractMultivariateOptimizer#BaseAbstractMultivariateOptimizer() + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + protected BaseAbstractMultivariateSimpleBoundsOptimizer() {} + + /** + * @param checker Convergence checker. + */ + protected BaseAbstractMultivariateSimpleBoundsOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** {@inheritDoc} */ + @Override + public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double[] startPoint) { + return super.optimizeInternal(maxEval, f, goalType, + new InitialGuess(startPoint)); + } + + /** {@inheritDoc} */ + public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double[] startPoint, + double[] lower, double[] upper) { + return super.optimizeInternal(maxEval, f, goalType, + new InitialGuess(startPoint), + new SimpleBounds(lower, upper)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java new file mode 100644 index 000000000..a942ee72d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.optimization.Target; +import com.fr.third.org.apache.commons.math3.optimization.Weight; +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optimization.SimpleVectorValueChecker; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Base class for implementing optimizers for multivariate scalar functions. + * This base class handles the boiler-plate methods associated to thresholds + * settings, iterations and evaluations counting. + * + * @param the type of the objective function to be optimized + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public abstract class BaseAbstractMultivariateVectorOptimizer + implements BaseMultivariateVectorOptimizer { + /** Evaluations counter. */ + protected final Incrementor evaluations = new Incrementor(); + /** Convergence checker. */ + private ConvergenceChecker checker; + /** Target value for the objective functions at optimum. */ + private double[] target; + /** Weight matrix. */ + private RealMatrix weightMatrix; + /** Weight for the least squares cost computation. + * @deprecated + */ + @Deprecated + private double[] weight; + /** Initial guess. */ + private double[] start; + /** Objective function. */ + private FUNC function; + + /** + * Simple constructor with default settings. + * The convergence check is set to a {@link SimpleVectorValueChecker}. + * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()} + */ + @Deprecated + protected BaseAbstractMultivariateVectorOptimizer() { + this(new SimpleVectorValueChecker()); + } + /** + * @param checker Convergence checker. + */ + protected BaseAbstractMultivariateVectorOptimizer(ConvergenceChecker checker) { + this.checker = checker; + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** {@inheritDoc} */ + public ConvergenceChecker getConvergenceChecker() { + return checker; + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at the specified point. + * @throws TooManyEvaluationsException if the maximal number of evaluations is + * exceeded. + */ + protected double[] computeObjectiveValue(double[] point) { + try { + evaluations.incrementCount(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return function.value(point); + } + + /** {@inheritDoc} + * + * @deprecated As of 3.1. Please use + * {@link #optimize(int,MultivariateVectorFunction,OptimizationData[])} + * instead. + */ + @Deprecated + public PointVectorValuePair optimize(int maxEval, FUNC f, double[] t, double[] w, + double[] startPoint) { + return optimizeInternal(maxEval, f, t, w, startPoint); + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link Target}
      • + *
      • {@link Weight}
      • + *
      • {@link InitialGuess}
      • + *
      + * @return the point/value pair giving the optimal value of the objective + * function. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + * + * @since 3.1 + */ + protected PointVectorValuePair optimize(int maxEval, + FUNC f, + OptimizationData... optData) + throws TooManyEvaluationsException, + DimensionMismatchException { + return optimizeInternal(maxEval, f, optData); + } + + /** + * Optimize an objective function. + * Optimization is considered to be a weighted least-squares minimization. + * The cost function to be minimized is + * ∑weighti(objectivei - targeti)2 + * + * @param f Objective function. + * @param t Target value for the objective functions at optimum. + * @param w Weights for the least squares cost computation. + * @param startPoint Start point for optimization. + * @return the point/value pair giving the optimal value for objective + * function. + * @param maxEval Maximum number of function evaluations. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + * @deprecated As of 3.1. Please use + * {@link #optimizeInternal(int,MultivariateVectorFunction,OptimizationData[])} + * instead. + */ + @Deprecated + protected PointVectorValuePair optimizeInternal(final int maxEval, final FUNC f, + final double[] t, final double[] w, + final double[] startPoint) { + // Checks. + if (f == null) { + throw new NullArgumentException(); + } + if (t == null) { + throw new NullArgumentException(); + } + if (w == null) { + throw new NullArgumentException(); + } + if (startPoint == null) { + throw new NullArgumentException(); + } + if (t.length != w.length) { + throw new DimensionMismatchException(t.length, w.length); + } + + return optimizeInternal(maxEval, f, + new Target(t), + new Weight(w), + new InitialGuess(startPoint)); + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link Target}
      • + *
      • {@link Weight}
      • + *
      • {@link InitialGuess}
      • + *
      + * @return the point/value pair giving the optimal value of the objective + * function. + * @throws TooManyEvaluationsException if the maximal number of + * evaluations is exceeded. + * @throws DimensionMismatchException if the initial guess, target, and weight + * arguments have inconsistent dimensions. + * + * @since 3.1 + */ + protected PointVectorValuePair optimizeInternal(int maxEval, + FUNC f, + OptimizationData... optData) + throws TooManyEvaluationsException, + DimensionMismatchException { + // Set internal state. + evaluations.setMaximalCount(maxEval); + evaluations.resetCount(); + function = f; + // Retrieve other settings. + parseOptimizationData(optData); + // Check input consistency. + checkParameters(); + // Allow subclasses to reset their own internal state. + setUp(); + // Perform computation. + return doOptimize(); + } + + /** + * Gets the initial values of the optimized parameters. + * + * @return the initial guess. + */ + public double[] getStartPoint() { + return start.clone(); + } + + /** + * Gets the weight matrix of the observations. + * + * @return the weight matrix. + * @since 3.1 + */ + public RealMatrix getWeight() { + return weightMatrix.copy(); + } + /** + * Gets the observed values to be matched by the objective vector + * function. + * + * @return the target values. + * @since 3.1 + */ + public double[] getTarget() { + return target.clone(); + } + + /** + * Gets the objective vector function. + * Note that this access bypasses the evaluation counter. + * + * @return the objective vector function. + * @since 3.1 + */ + protected FUNC getObjectiveFunction() { + return function; + } + + /** + * Perform the bulk of the optimization algorithm. + * + * @return the point/value pair giving the optimal value for the + * objective function. + */ + protected abstract PointVectorValuePair doOptimize(); + + /** + * @return a reference to the {@link #target array}. + * @deprecated As of 3.1. + */ + @Deprecated + protected double[] getTargetRef() { + return target; + } + /** + * @return a reference to the {@link #weight array}. + * @deprecated As of 3.1. + */ + @Deprecated + protected double[] getWeightRef() { + return weight; + } + + /** + * Method which a subclass must override whenever its internal + * state depend on the {@link OptimizationData input} parsed by this base + * class. + * It will be called after the parsing step performed in the + * {@link #optimize(int,MultivariateVectorFunction,OptimizationData[]) + * optimize} method and just before {@link #doOptimize()}. + * + * @since 3.1 + */ + protected void setUp() { + // XXX Temporary code until the new internal data is used everywhere. + final int dim = target.length; + weight = new double[dim]; + for (int i = 0; i < dim; i++) { + weight[i] = weightMatrix.getEntry(i, i); + } + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link Target}
      • + *
      • {@link Weight}
      • + *
      • {@link InitialGuess}
      • + *
      + */ + private void parseOptimizationData(OptimizationData... optData) { + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof Target) { + target = ((Target) data).getTarget(); + continue; + } + if (data instanceof Weight) { + weightMatrix = ((Weight) data).getWeight(); + continue; + } + if (data instanceof InitialGuess) { + start = ((InitialGuess) data).getInitialGuess(); + continue; + } + } + } + + /** + * Check parameters consistency. + * + * @throws DimensionMismatchException if {@link #target} and + * {@link #weightMatrix} have inconsistent dimensions. + */ + private void checkParameters() { + if (target.length != weightMatrix.getColumnDimension()) { + throw new DimensionMismatchException(target.length, + weightMatrix.getColumnDimension()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java new file mode 100644 index 000000000..6be70adde --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java @@ -0,0 +1,1442 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.random.MersenneTwister; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + *

      An implementation of the active Covariance Matrix Adaptation Evolution Strategy (CMA-ES) + * for non-linear, non-convex, non-smooth, global function minimization. + * The CMA-Evolution Strategy (CMA-ES) is a reliable stochastic optimization method + * which should be applied if derivative-based methods, e.g. quasi-Newton BFGS or + * conjugate gradient, fail due to a rugged search landscape (e.g. noise, local + * optima, outlier, etc.) of the objective function. Like a + * quasi-Newton method, the CMA-ES learns and applies a variable metric + * on the underlying search space. Unlike a quasi-Newton method, the + * CMA-ES neither estimates nor uses gradients, making it considerably more + * reliable in terms of finding a good, or even close to optimal, solution.

      + * + *

      In general, on smooth objective functions the CMA-ES is roughly ten times + * slower than BFGS (counting objective function evaluations, no gradients provided). + * For up to N=10 variables also the derivative-free simplex + * direct search method (Nelder and Mead) can be faster, but it is + * far less reliable than CMA-ES.

      + * + *

      The CMA-ES is particularly well suited for non-separable + * and/or badly conditioned problems. To observe the advantage of CMA compared + * to a conventional evolution strategy, it will usually take about + * 30 N function evaluations. On difficult problems the complete + * optimization (a single run) is expected to take roughly between + * 30 N and 300 N2 + * function evaluations.

      + * + *

      This implementation is translated and adapted from the Matlab version + * of the CMA-ES algorithm as implemented in module {@code cmaes.m} version 3.51.

      + * + * For more information, please refer to the following links: + * + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class CMAESOptimizer + extends BaseAbstractMultivariateSimpleBoundsOptimizer + implements MultivariateOptimizer { + /** Default value for {@link #checkFeasableCount}: {@value}. */ + public static final int DEFAULT_CHECKFEASABLECOUNT = 0; + /** Default value for {@link #stopFitness}: {@value}. */ + public static final double DEFAULT_STOPFITNESS = 0; + /** Default value for {@link #isActiveCMA}: {@value}. */ + public static final boolean DEFAULT_ISACTIVECMA = true; + /** Default value for {@link #maxIterations}: {@value}. */ + public static final int DEFAULT_MAXITERATIONS = 30000; + /** Default value for {@link #diagonalOnly}: {@value}. */ + public static final int DEFAULT_DIAGONALONLY = 0; + /** Default value for {@link #random}. */ + public static final RandomGenerator DEFAULT_RANDOMGENERATOR = new MersenneTwister(); + + // global search parameters + /** + * Population size, offspring number. The primary strategy parameter to play + * with, which can be increased from its default value. Increasing the + * population size improves global search properties in exchange to speed. + * Speed decreases, as a rule, at most linearly with increasing population + * size. It is advisable to begin with the default small population size. + */ + private int lambda; // population size + /** + * Covariance update mechanism, default is active CMA. isActiveCMA = true + * turns on "active CMA" with a negative update of the covariance matrix and + * checks for positive definiteness. OPTS.CMA.active = 2 does not check for + * pos. def. and is numerically faster. Active CMA usually speeds up the + * adaptation. + */ + private boolean isActiveCMA; + /** + * Determines how often a new random offspring is generated in case it is + * not feasible / beyond the defined limits, default is 0. + */ + private int checkFeasableCount; + /** + * @see Sigma + */ + private double[] inputSigma; + /** Number of objective variables/problem dimension */ + private int dimension; + /** + * Defines the number of initial iterations, where the covariance matrix + * remains diagonal and the algorithm has internally linear time complexity. + * diagonalOnly = 1 means keeping the covariance matrix always diagonal and + * this setting also exhibits linear space complexity. This can be + * particularly useful for dimension > 100. + * @see A Simple Modification in CMA-ES + */ + private int diagonalOnly = 0; + /** Number of objective variables/problem dimension */ + private boolean isMinimize = true; + /** Indicates whether statistic data is collected. */ + private boolean generateStatistics = false; + + // termination criteria + /** Maximal number of iterations allowed. */ + private int maxIterations; + /** Limit for fitness value. */ + private double stopFitness; + /** Stop if x-changes larger stopTolUpX. */ + private double stopTolUpX; + /** Stop if x-change smaller stopTolX. */ + private double stopTolX; + /** Stop if fun-changes smaller stopTolFun. */ + private double stopTolFun; + /** Stop if back fun-changes smaller stopTolHistFun. */ + private double stopTolHistFun; + + // selection strategy parameters + /** Number of parents/points for recombination. */ + private int mu; // + /** log(mu + 0.5), stored for efficiency. */ + private double logMu2; + /** Array for weighted recombination. */ + private RealMatrix weights; + /** Variance-effectiveness of sum w_i x_i. */ + private double mueff; // + + // dynamic strategy parameters and constants + /** Overall standard deviation - search volume. */ + private double sigma; + /** Cumulation constant. */ + private double cc; + /** Cumulation constant for step-size. */ + private double cs; + /** Damping for step-size. */ + private double damps; + /** Learning rate for rank-one update. */ + private double ccov1; + /** Learning rate for rank-mu update' */ + private double ccovmu; + /** Expectation of ||N(0,I)|| == norm(randn(N,1)). */ + private double chiN; + /** Learning rate for rank-one update - diagonalOnly */ + private double ccov1Sep; + /** Learning rate for rank-mu update - diagonalOnly */ + private double ccovmuSep; + + // CMA internal values - updated each generation + /** Objective variables. */ + private RealMatrix xmean; + /** Evolution path. */ + private RealMatrix pc; + /** Evolution path for sigma. */ + private RealMatrix ps; + /** Norm of ps, stored for efficiency. */ + private double normps; + /** Coordinate system. */ + private RealMatrix B; + /** Scaling. */ + private RealMatrix D; + /** B*D, stored for efficiency. */ + private RealMatrix BD; + /** Diagonal of sqrt(D), stored for efficiency. */ + private RealMatrix diagD; + /** Covariance matrix. */ + private RealMatrix C; + /** Diagonal of C, used for diagonalOnly. */ + private RealMatrix diagC; + /** Number of iterations already performed. */ + private int iterations; + + /** History queue of best values. */ + private double[] fitnessHistory; + /** Size of history queue of best values. */ + private int historySize; + + /** Random generator. */ + private RandomGenerator random; + + /** History of sigma values. */ + private List statisticsSigmaHistory = new ArrayList(); + /** History of mean matrix. */ + private List statisticsMeanHistory = new ArrayList(); + /** History of fitness values. */ + private List statisticsFitnessHistory = new ArrayList(); + /** History of D matrix. */ + private List statisticsDHistory = new ArrayList(); + + /** + * Default constructor, uses default parameters + * + * @deprecated As of version 3.1: Parameter {@code lambda} must be + * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[]) + * optimize} (whereas in the current code it is set to an undocumented value). + */ + @Deprecated + public CMAESOptimizer() { + this(0); + } + + /** + * @param lambda Population size. + * @deprecated As of version 3.1: Parameter {@code lambda} must be + * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[]) + * optimize} (whereas in the current code it is set to an undocumented value).. + */ + @Deprecated + public CMAESOptimizer(int lambda) { + this(lambda, null, DEFAULT_MAXITERATIONS, DEFAULT_STOPFITNESS, + DEFAULT_ISACTIVECMA, DEFAULT_DIAGONALONLY, + DEFAULT_CHECKFEASABLECOUNT, DEFAULT_RANDOMGENERATOR, + false, null); + } + + /** + * @param lambda Population size. + * @param inputSigma Initial standard deviations to sample new points + * around the initial guess. + * @deprecated As of version 3.1: Parameters {@code lambda} and {@code inputSigma} must be + * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[]) + * optimize}. + */ + @Deprecated + public CMAESOptimizer(int lambda, double[] inputSigma) { + this(lambda, inputSigma, DEFAULT_MAXITERATIONS, DEFAULT_STOPFITNESS, + DEFAULT_ISACTIVECMA, DEFAULT_DIAGONALONLY, + DEFAULT_CHECKFEASABLECOUNT, DEFAULT_RANDOMGENERATOR, false); + } + + /** + * @param lambda Population size. + * @param inputSigma Initial standard deviations to sample new points + * around the initial guess. + * @param maxIterations Maximal number of iterations. + * @param stopFitness Whether to stop if objective function value is smaller than + * {@code stopFitness}. + * @param isActiveCMA Chooses the covariance matrix update method. + * @param diagonalOnly Number of initial iterations, where the covariance matrix + * remains diagonal. + * @param checkFeasableCount Determines how often new random objective variables are + * generated in case they are out of bounds. + * @param random Random generator. + * @param generateStatistics Whether statistic data is collected. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + public CMAESOptimizer(int lambda, double[] inputSigma, + int maxIterations, double stopFitness, + boolean isActiveCMA, int diagonalOnly, int checkFeasableCount, + RandomGenerator random, boolean generateStatistics) { + this(lambda, inputSigma, maxIterations, stopFitness, isActiveCMA, + diagonalOnly, checkFeasableCount, random, generateStatistics, + new SimpleValueChecker()); + } + + /** + * @param lambda Population size. + * @param inputSigma Initial standard deviations to sample new points + * around the initial guess. + * @param maxIterations Maximal number of iterations. + * @param stopFitness Whether to stop if objective function value is smaller than + * {@code stopFitness}. + * @param isActiveCMA Chooses the covariance matrix update method. + * @param diagonalOnly Number of initial iterations, where the covariance matrix + * remains diagonal. + * @param checkFeasableCount Determines how often new random objective variables are + * generated in case they are out of bounds. + * @param random Random generator. + * @param generateStatistics Whether statistic data is collected. + * @param checker Convergence checker. + * @deprecated As of version 3.1: Parameters {@code lambda} and {@code inputSigma} must be + * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[]) + * optimize}. + */ + @Deprecated + public CMAESOptimizer(int lambda, double[] inputSigma, + int maxIterations, double stopFitness, + boolean isActiveCMA, int diagonalOnly, int checkFeasableCount, + RandomGenerator random, boolean generateStatistics, + ConvergenceChecker checker) { + super(checker); + this.lambda = lambda; + this.inputSigma = inputSigma == null ? null : (double[]) inputSigma.clone(); + this.maxIterations = maxIterations; + this.stopFitness = stopFitness; + this.isActiveCMA = isActiveCMA; + this.diagonalOnly = diagonalOnly; + this.checkFeasableCount = checkFeasableCount; + this.random = random; + this.generateStatistics = generateStatistics; + } + + /** + * @param maxIterations Maximal number of iterations. + * @param stopFitness Whether to stop if objective function value is smaller than + * {@code stopFitness}. + * @param isActiveCMA Chooses the covariance matrix update method. + * @param diagonalOnly Number of initial iterations, where the covariance matrix + * remains diagonal. + * @param checkFeasableCount Determines how often new random objective variables are + * generated in case they are out of bounds. + * @param random Random generator. + * @param generateStatistics Whether statistic data is collected. + * @param checker Convergence checker. + * + * @since 3.1 + */ + public CMAESOptimizer(int maxIterations, + double stopFitness, + boolean isActiveCMA, + int diagonalOnly, + int checkFeasableCount, + RandomGenerator random, + boolean generateStatistics, + ConvergenceChecker checker) { + super(checker); + this.maxIterations = maxIterations; + this.stopFitness = stopFitness; + this.isActiveCMA = isActiveCMA; + this.diagonalOnly = diagonalOnly; + this.checkFeasableCount = checkFeasableCount; + this.random = random; + this.generateStatistics = generateStatistics; + } + + /** + * @return History of sigma values. + */ + public List getStatisticsSigmaHistory() { + return statisticsSigmaHistory; + } + + /** + * @return History of mean matrix. + */ + public List getStatisticsMeanHistory() { + return statisticsMeanHistory; + } + + /** + * @return History of fitness values. + */ + public List getStatisticsFitnessHistory() { + return statisticsFitnessHistory; + } + + /** + * @return History of D matrix. + */ + public List getStatisticsDHistory() { + return statisticsDHistory; + } + + /** + * Input sigma values. + * They define the initial coordinate-wise standard deviations for + * sampling new search points around the initial guess. + * It is suggested to set them to the estimated distance from the + * initial to the desired optimum. + * Small values induce the search to be more local (and very small + * values are more likely to find a local optimum close to the initial + * guess). + * Too small values might however lead to early termination. + * @since 3.1 + */ + public static class Sigma implements OptimizationData { + /** Sigma values. */ + private final double[] sigma; + + /** + * @param s Sigma values. + * @throws NotPositiveException if any of the array entries is smaller + * than zero. + */ + public Sigma(double[] s) + throws NotPositiveException { + for (int i = 0; i < s.length; i++) { + if (s[i] < 0) { + throw new NotPositiveException(s[i]); + } + } + + sigma = s.clone(); + } + + /** + * @return the sigma values. + */ + public double[] getSigma() { + return sigma.clone(); + } + } + + /** + * Population size. + * The number of offspring is the primary strategy parameter. + * In the absence of better clues, a good default could be an + * integer close to {@code 4 + 3 ln(n)}, where {@code n} is the + * number of optimized parameters. + * Increasing the population size improves global search properties + * at the expense of speed (which in general decreases at most + * linearly with increasing population size). + * @since 3.1 + */ + public static class PopulationSize implements OptimizationData { + /** Population size. */ + private final int lambda; + + /** + * @param size Population size. + * @throws NotStrictlyPositiveException if {@code size <= 0}. + */ + public PopulationSize(int size) + throws NotStrictlyPositiveException { + if (size <= 0) { + throw new NotStrictlyPositiveException(size); + } + lambda = size; + } + + /** + * @return the population size. + */ + public int getPopulationSize() { + return lambda; + } + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param goalType Optimization type. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link InitialGuess InitialGuess}
      • + *
      • {@link Sigma}
      • + *
      • {@link PopulationSize}
      • + *
      + * @return the point/value pair giving the optimal value for objective + * function. + */ + @Override + protected PointValuePair optimizeInternal(int maxEval, MultivariateFunction f, + GoalType goalType, + OptimizationData... optData) { + // Scan "optData" for the input specific to this optimizer. + parseOptimizationData(optData); + + // The parent's method will retrieve the common parameters from + // "optData" and call "doOptimize". + return super.optimizeInternal(maxEval, f, goalType, optData); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + checkParameters(); + // -------------------- Initialization -------------------------------- + isMinimize = getGoalType().equals(GoalType.MINIMIZE); + final FitnessFunction fitfun = new FitnessFunction(); + final double[] guess = getStartPoint(); + // number of objective variables/problem dimension + dimension = guess.length; + initializeCMA(guess); + iterations = 0; + double bestValue = fitfun.value(guess); + push(fitnessHistory, bestValue); + PointValuePair optimum = new PointValuePair(getStartPoint(), + isMinimize ? bestValue : -bestValue); + PointValuePair lastResult = null; + + // -------------------- Generation Loop -------------------------------- + + generationLoop: + for (iterations = 1; iterations <= maxIterations; iterations++) { + // Generate and evaluate lambda offspring + final RealMatrix arz = randn1(dimension, lambda); + final RealMatrix arx = zeros(dimension, lambda); + final double[] fitness = new double[lambda]; + // generate random offspring + for (int k = 0; k < lambda; k++) { + RealMatrix arxk = null; + for (int i = 0; i < checkFeasableCount + 1; i++) { + if (diagonalOnly <= 0) { + arxk = xmean.add(BD.multiply(arz.getColumnMatrix(k)) + .scalarMultiply(sigma)); // m + sig * Normal(0,C) + } else { + arxk = xmean.add(times(diagD,arz.getColumnMatrix(k)) + .scalarMultiply(sigma)); + } + if (i >= checkFeasableCount || + fitfun.isFeasible(arxk.getColumn(0))) { + break; + } + // regenerate random arguments for row + arz.setColumn(k, randn(dimension)); + } + copyColumn(arxk, 0, arx, k); + try { + fitness[k] = fitfun.value(arx.getColumn(k)); // compute fitness + } catch (TooManyEvaluationsException e) { + break generationLoop; + } + } + // Sort by fitness and compute weighted mean into xmean + final int[] arindex = sortedIndices(fitness); + // Calculate new xmean, this is selection and recombination + final RealMatrix xold = xmean; // for speed up of Eq. (2) and (3) + final RealMatrix bestArx = selectColumns(arx, MathArrays.copyOf(arindex, mu)); + xmean = bestArx.multiply(weights); + final RealMatrix bestArz = selectColumns(arz, MathArrays.copyOf(arindex, mu)); + final RealMatrix zmean = bestArz.multiply(weights); + final boolean hsig = updateEvolutionPaths(zmean, xold); + if (diagonalOnly <= 0) { + updateCovariance(hsig, bestArx, arz, arindex, xold); + } else { + updateCovarianceDiagonalOnly(hsig, bestArz); + } + // Adapt step size sigma - Eq. (5) + sigma *= FastMath.exp(FastMath.min(1, (normps/chiN - 1) * cs / damps)); + final double bestFitness = fitness[arindex[0]]; + final double worstFitness = fitness[arindex[arindex.length - 1]]; + if (bestValue > bestFitness) { + bestValue = bestFitness; + lastResult = optimum; + optimum = new PointValuePair(fitfun.repair(bestArx.getColumn(0)), + isMinimize ? bestFitness : -bestFitness); + if (getConvergenceChecker() != null && lastResult != null && + getConvergenceChecker().converged(iterations, optimum, lastResult)) { + break generationLoop; + } + } + // handle termination criteria + // Break, if fitness is good enough + if (stopFitness != 0 && bestFitness < (isMinimize ? stopFitness : -stopFitness)) { + break generationLoop; + } + final double[] sqrtDiagC = sqrt(diagC).getColumn(0); + final double[] pcCol = pc.getColumn(0); + for (int i = 0; i < dimension; i++) { + if (sigma * FastMath.max(FastMath.abs(pcCol[i]), sqrtDiagC[i]) > stopTolX) { + break; + } + if (i >= dimension - 1) { + break generationLoop; + } + } + for (int i = 0; i < dimension; i++) { + if (sigma * sqrtDiagC[i] > stopTolUpX) { + break generationLoop; + } + } + final double historyBest = min(fitnessHistory); + final double historyWorst = max(fitnessHistory); + if (iterations > 2 && + FastMath.max(historyWorst, worstFitness) - + FastMath.min(historyBest, bestFitness) < stopTolFun) { + break generationLoop; + } + if (iterations > fitnessHistory.length && + historyWorst-historyBest < stopTolHistFun) { + break generationLoop; + } + // condition number of the covariance matrix exceeds 1e14 + if (max(diagD)/min(diagD) > 1e7) { + break generationLoop; + } + // user defined termination + if (getConvergenceChecker() != null) { + final PointValuePair current + = new PointValuePair(bestArx.getColumn(0), + isMinimize ? bestFitness : -bestFitness); + if (lastResult != null && + getConvergenceChecker().converged(iterations, current, lastResult)) { + break generationLoop; + } + lastResult = current; + } + // Adjust step size in case of equal function values (flat fitness) + if (bestValue == fitness[arindex[(int)(0.1+lambda/4.)]]) { + sigma *= FastMath.exp(0.2 + cs / damps); + } + if (iterations > 2 && FastMath.max(historyWorst, bestFitness) - + FastMath.min(historyBest, bestFitness) == 0) { + sigma *= FastMath.exp(0.2 + cs / damps); + } + // store best in history + push(fitnessHistory,bestFitness); + fitfun.setValueRange(worstFitness-bestFitness); + if (generateStatistics) { + statisticsSigmaHistory.add(sigma); + statisticsFitnessHistory.add(bestFitness); + statisticsMeanHistory.add(xmean.transpose()); + statisticsDHistory.add(diagD.transpose().scalarMultiply(1E5)); + } + } + return optimum; + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link Sigma}
      • + *
      • {@link PopulationSize}
      • + *
      + */ + private void parseOptimizationData(OptimizationData... optData) { + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof Sigma) { + inputSigma = ((Sigma) data).getSigma(); + continue; + } + if (data instanceof PopulationSize) { + lambda = ((PopulationSize) data).getPopulationSize(); + continue; + } + } + } + + /** + * Checks dimensions and values of boundaries and inputSigma if defined. + */ + private void checkParameters() { + final double[] init = getStartPoint(); + final double[] lB = getLowerBound(); + final double[] uB = getUpperBound(); + + if (inputSigma != null) { + if (inputSigma.length != init.length) { + throw new DimensionMismatchException(inputSigma.length, init.length); + } + for (int i = 0; i < init.length; i++) { + if (inputSigma[i] < 0) { + // XXX Remove this block in 4.0 (check performed in "Sigma" class). + throw new NotPositiveException(inputSigma[i]); + } + if (inputSigma[i] > uB[i] - lB[i]) { + throw new OutOfRangeException(inputSigma[i], 0, uB[i] - lB[i]); + } + } + } + } + + /** + * Initialization of the dynamic search parameters + * + * @param guess Initial guess for the arguments of the fitness function. + */ + private void initializeCMA(double[] guess) { + if (lambda <= 0) { + // XXX Line below to replace the current one in 4.0 (MATH-879). + // throw new NotStrictlyPositiveException(lambda); + lambda = 4 + (int) (3 * FastMath.log(dimension)); + } + // initialize sigma + final double[][] sigmaArray = new double[guess.length][1]; + for (int i = 0; i < guess.length; i++) { + // XXX Line below to replace the current one in 4.0 (MATH-868). + // sigmaArray[i][0] = inputSigma[i]; + sigmaArray[i][0] = inputSigma == null ? 0.3 : inputSigma[i]; + } + final RealMatrix insigma = new Array2DRowRealMatrix(sigmaArray, false); + sigma = max(insigma); // overall standard deviation + + // initialize termination criteria + stopTolUpX = 1e3 * max(insigma); + stopTolX = 1e-11 * max(insigma); + stopTolFun = 1e-12; + stopTolHistFun = 1e-13; + + // initialize selection strategy parameters + mu = lambda / 2; // number of parents/points for recombination + logMu2 = FastMath.log(mu + 0.5); + weights = log(sequence(1, mu, 1)).scalarMultiply(-1).scalarAdd(logMu2); + double sumw = 0; + double sumwq = 0; + for (int i = 0; i < mu; i++) { + double w = weights.getEntry(i, 0); + sumw += w; + sumwq += w * w; + } + weights = weights.scalarMultiply(1 / sumw); + mueff = sumw * sumw / sumwq; // variance-effectiveness of sum w_i x_i + + // initialize dynamic strategy parameters and constants + cc = (4 + mueff / dimension) / + (dimension + 4 + 2 * mueff / dimension); + cs = (mueff + 2) / (dimension + mueff + 3.); + damps = (1 + 2 * FastMath.max(0, FastMath.sqrt((mueff - 1) / + (dimension + 1)) - 1)) * + FastMath.max(0.3, + 1 - dimension / (1e-6 + maxIterations)) + cs; // minor increment + ccov1 = 2 / ((dimension + 1.3) * (dimension + 1.3) + mueff); + ccovmu = FastMath.min(1 - ccov1, 2 * (mueff - 2 + 1 / mueff) / + ((dimension + 2) * (dimension + 2) + mueff)); + ccov1Sep = FastMath.min(1, ccov1 * (dimension + 1.5) / 3); + ccovmuSep = FastMath.min(1 - ccov1, ccovmu * (dimension + 1.5) / 3); + chiN = FastMath.sqrt(dimension) * + (1 - 1 / ((double) 4 * dimension) + 1 / ((double) 21 * dimension * dimension)); + // intialize CMA internal values - updated each generation + xmean = MatrixUtils.createColumnRealMatrix(guess); // objective variables + diagD = insigma.scalarMultiply(1 / sigma); + diagC = square(diagD); + pc = zeros(dimension, 1); // evolution paths for C and sigma + ps = zeros(dimension, 1); // B defines the coordinate system + normps = ps.getFrobeniusNorm(); + + B = eye(dimension, dimension); + D = ones(dimension, 1); // diagonal D defines the scaling + BD = times(B, repmat(diagD.transpose(), dimension, 1)); + C = B.multiply(diag(square(D)).multiply(B.transpose())); // covariance + historySize = 10 + (int) (3 * 10 * dimension / (double) lambda); + fitnessHistory = new double[historySize]; // history of fitness values + for (int i = 0; i < historySize; i++) { + fitnessHistory[i] = Double.MAX_VALUE; + } + } + + /** + * Update of the evolution paths ps and pc. + * + * @param zmean Weighted row matrix of the gaussian random numbers generating + * the current offspring. + * @param xold xmean matrix of the previous generation. + * @return hsig flag indicating a small correction. + */ + private boolean updateEvolutionPaths(RealMatrix zmean, RealMatrix xold) { + ps = ps.scalarMultiply(1 - cs).add( + B.multiply(zmean).scalarMultiply(FastMath.sqrt(cs * (2 - cs) * mueff))); + normps = ps.getFrobeniusNorm(); + final boolean hsig = normps / + FastMath.sqrt(1 - FastMath.pow(1 - cs, 2 * iterations)) / + chiN < 1.4 + 2 / ((double) dimension + 1); + pc = pc.scalarMultiply(1 - cc); + if (hsig) { + pc = pc.add(xmean.subtract(xold).scalarMultiply(FastMath.sqrt(cc * (2 - cc) * mueff) / sigma)); + } + return hsig; + } + + /** + * Update of the covariance matrix C for diagonalOnly > 0 + * + * @param hsig Flag indicating a small correction. + * @param bestArz Fitness-sorted matrix of the gaussian random values of the + * current offspring. + */ + private void updateCovarianceDiagonalOnly(boolean hsig, + final RealMatrix bestArz) { + // minor correction if hsig==false + double oldFac = hsig ? 0 : ccov1Sep * cc * (2 - cc); + oldFac += 1 - ccov1Sep - ccovmuSep; + diagC = diagC.scalarMultiply(oldFac) // regard old matrix + .add(square(pc).scalarMultiply(ccov1Sep)) // plus rank one update + .add((times(diagC, square(bestArz).multiply(weights))) // plus rank mu update + .scalarMultiply(ccovmuSep)); + diagD = sqrt(diagC); // replaces eig(C) + if (diagonalOnly > 1 && + iterations > diagonalOnly) { + // full covariance matrix from now on + diagonalOnly = 0; + B = eye(dimension, dimension); + BD = diag(diagD); + C = diag(diagC); + } + } + + /** + * Update of the covariance matrix C. + * + * @param hsig Flag indicating a small correction. + * @param bestArx Fitness-sorted matrix of the argument vectors producing the + * current offspring. + * @param arz Unsorted matrix containing the gaussian random values of the + * current offspring. + * @param arindex Indices indicating the fitness-order of the current offspring. + * @param xold xmean matrix of the previous generation. + */ + private void updateCovariance(boolean hsig, final RealMatrix bestArx, + final RealMatrix arz, final int[] arindex, + final RealMatrix xold) { + double negccov = 0; + if (ccov1 + ccovmu > 0) { + final RealMatrix arpos = bestArx.subtract(repmat(xold, 1, mu)) + .scalarMultiply(1 / sigma); // mu difference vectors + final RealMatrix roneu = pc.multiply(pc.transpose()) + .scalarMultiply(ccov1); // rank one update + // minor correction if hsig==false + double oldFac = hsig ? 0 : ccov1 * cc * (2 - cc); + oldFac += 1 - ccov1 - ccovmu; + if (isActiveCMA) { + // Adapt covariance matrix C active CMA + negccov = (1 - ccovmu) * 0.25 * mueff / (FastMath.pow(dimension + 2, 1.5) + 2 * mueff); + // keep at least 0.66 in all directions, small popsize are most + // critical + final double negminresidualvariance = 0.66; + // where to make up for the variance loss + final double negalphaold = 0.5; + // prepare vectors, compute negative updating matrix Cneg + final int[] arReverseIndex = reverse(arindex); + RealMatrix arzneg = selectColumns(arz, MathArrays.copyOf(arReverseIndex, mu)); + RealMatrix arnorms = sqrt(sumRows(square(arzneg))); + final int[] idxnorms = sortedIndices(arnorms.getRow(0)); + final RealMatrix arnormsSorted = selectColumns(arnorms, idxnorms); + final int[] idxReverse = reverse(idxnorms); + final RealMatrix arnormsReverse = selectColumns(arnorms, idxReverse); + arnorms = divide(arnormsReverse, arnormsSorted); + final int[] idxInv = inverse(idxnorms); + final RealMatrix arnormsInv = selectColumns(arnorms, idxInv); + // check and set learning rate negccov + final double negcovMax = (1 - negminresidualvariance) / + square(arnormsInv).multiply(weights).getEntry(0, 0); + if (negccov > negcovMax) { + negccov = negcovMax; + } + arzneg = times(arzneg, repmat(arnormsInv, dimension, 1)); + final RealMatrix artmp = BD.multiply(arzneg); + final RealMatrix Cneg = artmp.multiply(diag(weights)).multiply(artmp.transpose()); + oldFac += negalphaold * negccov; + C = C.scalarMultiply(oldFac) + .add(roneu) // regard old matrix + .add(arpos.scalarMultiply( // plus rank one update + ccovmu + (1 - negalphaold) * negccov) // plus rank mu update + .multiply(times(repmat(weights, 1, dimension), + arpos.transpose()))) + .subtract(Cneg.scalarMultiply(negccov)); + } else { + // Adapt covariance matrix C - nonactive + C = C.scalarMultiply(oldFac) // regard old matrix + .add(roneu) // plus rank one update + .add(arpos.scalarMultiply(ccovmu) // plus rank mu update + .multiply(times(repmat(weights, 1, dimension), + arpos.transpose()))); + } + } + updateBD(negccov); + } + + /** + * Update B and D from C. + * + * @param negccov Negative covariance factor. + */ + private void updateBD(double negccov) { + if (ccov1 + ccovmu + negccov > 0 && + (iterations % 1. / (ccov1 + ccovmu + negccov) / dimension / 10.) < 1) { + // to achieve O(N^2) + C = triu(C, 0).add(triu(C, 1).transpose()); + // enforce symmetry to prevent complex numbers + final EigenDecomposition eig = new EigenDecomposition(C); + B = eig.getV(); // eigen decomposition, B==normalized eigenvectors + D = eig.getD(); + diagD = diag(D); + if (min(diagD) <= 0) { + for (int i = 0; i < dimension; i++) { + if (diagD.getEntry(i, 0) < 0) { + diagD.setEntry(i, 0, 0); + } + } + final double tfac = max(diagD) / 1e14; + C = C.add(eye(dimension, dimension).scalarMultiply(tfac)); + diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac)); + } + if (max(diagD) > 1e14 * min(diagD)) { + final double tfac = max(diagD) / 1e14 - min(diagD); + C = C.add(eye(dimension, dimension).scalarMultiply(tfac)); + diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac)); + } + diagC = diag(C); + diagD = sqrt(diagD); // D contains standard deviations now + BD = times(B, repmat(diagD.transpose(), dimension, 1)); // O(n^2) + } + } + + /** + * Pushes the current best fitness value in a history queue. + * + * @param vals History queue. + * @param val Current best fitness value. + */ + private static void push(double[] vals, double val) { + for (int i = vals.length-1; i > 0; i--) { + vals[i] = vals[i-1]; + } + vals[0] = val; + } + + /** + * Sorts fitness values. + * + * @param doubles Array of values to be sorted. + * @return a sorted array of indices pointing into doubles. + */ + private int[] sortedIndices(final double[] doubles) { + final DoubleIndex[] dis = new DoubleIndex[doubles.length]; + for (int i = 0; i < doubles.length; i++) { + dis[i] = new DoubleIndex(doubles[i], i); + } + Arrays.sort(dis); + final int[] indices = new int[doubles.length]; + for (int i = 0; i < doubles.length; i++) { + indices[i] = dis[i].index; + } + return indices; + } + + /** + * Used to sort fitness values. Sorting is always in lower value first + * order. + */ + private static class DoubleIndex implements Comparable { + /** Value to compare. */ + private final double value; + /** Index into sorted array. */ + private final int index; + + /** + * @param value Value to compare. + * @param index Index into sorted array. + */ + DoubleIndex(double value, int index) { + this.value = value; + this.index = index; + } + + /** {@inheritDoc} */ + public int compareTo(DoubleIndex o) { + return Double.compare(value, o.value); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof DoubleIndex) { + return Double.compare(value, ((DoubleIndex) other).value) == 0; + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + long bits = Double.doubleToLongBits(value); + return (int) ((1438542 ^ (bits >>> 32) ^ bits) & 0xffffffff); + } + } + + /** + * Normalizes fitness values to the range [0,1]. Adds a penalty to the + * fitness value if out of range. The penalty is adjusted by calling + * setValueRange(). + */ + private class FitnessFunction { + /** Determines the penalty for boundary violations */ + private double valueRange; + /** + * Flag indicating whether the objective variables are forced into their + * bounds if defined + */ + private final boolean isRepairMode; + + /** Simple constructor. + */ + FitnessFunction() { + valueRange = 1; + isRepairMode = true; + } + + /** + * @param point Normalized objective variables. + * @return the objective value + penalty for violated bounds. + */ + public double value(final double[] point) { + double value; + if (isRepairMode) { + double[] repaired = repair(point); + value = CMAESOptimizer.this.computeObjectiveValue(repaired) + + penalty(point, repaired); + } else { + value = CMAESOptimizer.this.computeObjectiveValue(point); + } + return isMinimize ? value : -value; + } + + /** + * @param x Normalized objective variables. + * @return {@code true} if in bounds. + */ + public boolean isFeasible(final double[] x) { + final double[] lB = CMAESOptimizer.this.getLowerBound(); + final double[] uB = CMAESOptimizer.this.getUpperBound(); + + for (int i = 0; i < x.length; i++) { + if (x[i] < lB[i]) { + return false; + } + if (x[i] > uB[i]) { + return false; + } + } + return true; + } + + /** + * @param valueRange Adjusts the penalty computation. + */ + public void setValueRange(double valueRange) { + this.valueRange = valueRange; + } + + /** + * @param x Normalized objective variables. + * @return the repaired (i.e. all in bounds) objective variables. + */ + private double[] repair(final double[] x) { + final double[] lB = CMAESOptimizer.this.getLowerBound(); + final double[] uB = CMAESOptimizer.this.getUpperBound(); + + final double[] repaired = new double[x.length]; + for (int i = 0; i < x.length; i++) { + if (x[i] < lB[i]) { + repaired[i] = lB[i]; + } else if (x[i] > uB[i]) { + repaired[i] = uB[i]; + } else { + repaired[i] = x[i]; + } + } + return repaired; + } + + /** + * @param x Normalized objective variables. + * @param repaired Repaired objective variables. + * @return Penalty value according to the violation of the bounds. + */ + private double penalty(final double[] x, final double[] repaired) { + double penalty = 0; + for (int i = 0; i < x.length; i++) { + double diff = FastMath.abs(x[i] - repaired[i]); + penalty += diff * valueRange; + } + return isMinimize ? penalty : -penalty; + } + } + + // -----Matrix utility functions similar to the Matlab build in functions------ + + /** + * @param m Input matrix + * @return Matrix representing the element-wise logarithm of m. + */ + private static RealMatrix log(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = FastMath.log(m.getEntry(r, c)); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Matrix representing the element-wise square root of m. + */ + private static RealMatrix sqrt(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = FastMath.sqrt(m.getEntry(r, c)); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Matrix representing the element-wise square of m. + */ + private static RealMatrix square(final RealMatrix m) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + d[r][c] = e * e; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix 1. + * @param n Input matrix 2. + * @return the matrix where the elements of m and n are element-wise multiplied. + */ + private static RealMatrix times(final RealMatrix m, final RealMatrix n) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = m.getEntry(r, c) * n.getEntry(r, c); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix 1. + * @param n Input matrix 2. + * @return Matrix where the elements of m and n are element-wise divided. + */ + private static RealMatrix divide(final RealMatrix m, final RealMatrix n) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = m.getEntry(r, c) / n.getEntry(r, c); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @param cols Columns to select. + * @return Matrix representing the selected columns. + */ + private static RealMatrix selectColumns(final RealMatrix m, final int[] cols) { + final double[][] d = new double[m.getRowDimension()][cols.length]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < cols.length; c++) { + d[r][c] = m.getEntry(r, cols[c]); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @param k Diagonal position. + * @return Upper triangular part of matrix. + */ + private static RealMatrix triu(final RealMatrix m, int k) { + final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()]; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + d[r][c] = r <= c - k ? m.getEntry(r, c) : 0; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return Row matrix representing the sums of the rows. + */ + private static RealMatrix sumRows(final RealMatrix m) { + final double[][] d = new double[1][m.getColumnDimension()]; + for (int c = 0; c < m.getColumnDimension(); c++) { + double sum = 0; + for (int r = 0; r < m.getRowDimension(); r++) { + sum += m.getEntry(r, c); + } + d[0][c] = sum; + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return the diagonal n-by-n matrix if m is a column matrix or the column + * matrix representing the diagonal if m is a n-by-n matrix. + */ + private static RealMatrix diag(final RealMatrix m) { + if (m.getColumnDimension() == 1) { + final double[][] d = new double[m.getRowDimension()][m.getRowDimension()]; + for (int i = 0; i < m.getRowDimension(); i++) { + d[i][i] = m.getEntry(i, 0); + } + return new Array2DRowRealMatrix(d, false); + } else { + final double[][] d = new double[m.getRowDimension()][1]; + for (int i = 0; i < m.getColumnDimension(); i++) { + d[i][0] = m.getEntry(i, i); + } + return new Array2DRowRealMatrix(d, false); + } + } + + /** + * Copies a column from m1 to m2. + * + * @param m1 Source matrix. + * @param col1 Source column. + * @param m2 Target matrix. + * @param col2 Target column. + */ + private static void copyColumn(final RealMatrix m1, int col1, + RealMatrix m2, int col2) { + for (int i = 0; i < m1.getRowDimension(); i++) { + m2.setEntry(i, col2, m1.getEntry(i, col1)); + } + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix filled with 1. + */ + private static RealMatrix ones(int n, int m) { + final double[][] d = new double[n][m]; + for (int r = 0; r < n; r++) { + Arrays.fill(d[r], 1); + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix of 0 values out of diagonal, and 1 values on + * the diagonal. + */ + private static RealMatrix eye(int n, int m) { + final double[][] d = new double[n][m]; + for (int r = 0; r < n; r++) { + if (r < m) { + d[r][r] = 1; + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param n Number of rows. + * @param m Number of columns. + * @return n-by-m matrix of zero values. + */ + private static RealMatrix zeros(int n, int m) { + return new Array2DRowRealMatrix(n, m); + } + + /** + * @param mat Input matrix. + * @param n Number of row replicates. + * @param m Number of column replicates. + * @return a matrix which replicates the input matrix in both directions. + */ + private static RealMatrix repmat(final RealMatrix mat, int n, int m) { + final int rd = mat.getRowDimension(); + final int cd = mat.getColumnDimension(); + final double[][] d = new double[n * rd][m * cd]; + for (int r = 0; r < n * rd; r++) { + for (int c = 0; c < m * cd; c++) { + d[r][c] = mat.getEntry(r % rd, c % cd); + } + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param start Start value. + * @param end End value. + * @param step Step size. + * @return a sequence as column matrix. + */ + private static RealMatrix sequence(double start, double end, double step) { + final int size = (int) ((end - start) / step + 1); + final double[][] d = new double[size][1]; + double value = start; + for (int r = 0; r < size; r++) { + d[r][0] = value; + value += step; + } + return new Array2DRowRealMatrix(d, false); + } + + /** + * @param m Input matrix. + * @return the maximum of the matrix element values. + */ + private static double max(final RealMatrix m) { + double max = -Double.MAX_VALUE; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + if (max < e) { + max = e; + } + } + } + return max; + } + + /** + * @param m Input matrix. + * @return the minimum of the matrix element values. + */ + private static double min(final RealMatrix m) { + double min = Double.MAX_VALUE; + for (int r = 0; r < m.getRowDimension(); r++) { + for (int c = 0; c < m.getColumnDimension(); c++) { + double e = m.getEntry(r, c); + if (min > e) { + min = e; + } + } + } + return min; + } + + /** + * @param m Input array. + * @return the maximum of the array values. + */ + private static double max(final double[] m) { + double max = -Double.MAX_VALUE; + for (int r = 0; r < m.length; r++) { + if (max < m[r]) { + max = m[r]; + } + } + return max; + } + + /** + * @param m Input array. + * @return the minimum of the array values. + */ + private static double min(final double[] m) { + double min = Double.MAX_VALUE; + for (int r = 0; r < m.length; r++) { + if (min > m[r]) { + min = m[r]; + } + } + return min; + } + + /** + * @param indices Input index array. + * @return the inverse of the mapping defined by indices. + */ + private static int[] inverse(final int[] indices) { + final int[] inverse = new int[indices.length]; + for (int i = 0; i < indices.length; i++) { + inverse[indices[i]] = i; + } + return inverse; + } + + /** + * @param indices Input index array. + * @return the indices in inverse order (last is first). + */ + private static int[] reverse(final int[] indices) { + final int[] reverse = new int[indices.length]; + for (int i = 0; i < indices.length; i++) { + reverse[i] = indices[indices.length - i - 1]; + } + return reverse; + } + + /** + * @param size Length of random array. + * @return an array of Gaussian random numbers. + */ + private double[] randn(int size) { + final double[] randn = new double[size]; + for (int i = 0; i < size; i++) { + randn[i] = random.nextGaussian(); + } + return randn; + } + + /** + * @param size Number of rows. + * @param popSize Population size. + * @return a 2-dimensional matrix of Gaussian random numbers. + */ + private RealMatrix randn1(int size, int popSize) { + final double[][] d = new double[size][popSize]; + for (int r = 0; r < size; r++) { + for (int c = 0; c < popSize; c++) { + d[r][c] = random.nextGaussian(); + } + } + return new Array2DRowRealMatrix(d, false); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.java new file mode 100644 index 000000000..8d3207328 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; + +/** + * This class implements the multi-directional direct search method. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class MultiDirectionalSimplex extends AbstractSimplex { + /** Default value for {@link #khi}: {@value}. */ + private static final double DEFAULT_KHI = 2; + /** Default value for {@link #gamma}: {@value}. */ + private static final double DEFAULT_GAMMA = 0.5; + /** Expansion coefficient. */ + private final double khi; + /** Contraction coefficient. */ + private final double gamma; + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param n Dimension of the simplex. + */ + public MultiDirectionalSimplex(final int n) { + this(n, 1d); + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + */ + public MultiDirectionalSimplex(final int n, double sideLength) { + this(n, sideLength, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final int n, + final double khi, final double gamma) { + this(n, 1d, khi, gamma); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final int n, double sideLength, + final double khi, final double gamma) { + super(n, sideLength); + + this.khi = khi; + this.gamma = gamma; + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + */ + public MultiDirectionalSimplex(final double[] steps) { + this(steps, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + * {@link AbstractSimplex#AbstractSimplex(double[])}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + */ + public MultiDirectionalSimplex(final double[] steps, + final double khi, final double gamma) { + super(steps); + + this.khi = khi; + this.gamma = gamma; + } + + /** + * Build a multi-directional simplex with default coefficients. + * The default values are 2.0 for khi and 0.5 for gamma. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + */ + public MultiDirectionalSimplex(final double[][] referenceSimplex) { + this(referenceSimplex, DEFAULT_KHI, DEFAULT_GAMMA); + } + + /** + * Build a multi-directional simplex with specified coefficients. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @throws NotStrictlyPositiveException + * if the reference simplex does not contain at least one point. + * @throws DimensionMismatchException + * if there is a dimension mismatch in the reference simplex. + */ + public MultiDirectionalSimplex(final double[][] referenceSimplex, + final double khi, final double gamma) { + super(referenceSimplex); + + this.khi = khi; + this.gamma = gamma; + } + + /** {@inheritDoc} */ + @Override + public void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // Save the original simplex. + final PointValuePair[] original = getPoints(); + final PointValuePair best = original[0]; + + // Perform a reflection step. + final PointValuePair reflected = evaluateNewSimplex(evaluationFunction, + original, 1, comparator); + if (comparator.compare(reflected, best) < 0) { + // Compute the expanded simplex. + final PointValuePair[] reflectedSimplex = getPoints(); + final PointValuePair expanded = evaluateNewSimplex(evaluationFunction, + original, khi, comparator); + if (comparator.compare(reflected, expanded) <= 0) { + // Keep the reflected simplex. + setPoints(reflectedSimplex); + } + // Keep the expanded simplex. + return; + } + + // Compute the contracted simplex. + evaluateNewSimplex(evaluationFunction, original, gamma, comparator); + + } + + /** + * Compute and evaluate a new simplex. + * + * @param evaluationFunction Evaluation function. + * @param original Original simplex (to be preserved). + * @param coeff Linear coefficient. + * @param comparator Comparator to use to sort simplex vertices from best + * to poorest. + * @return the best point in the transformed simplex. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + */ + private PointValuePair evaluateNewSimplex(final MultivariateFunction evaluationFunction, + final PointValuePair[] original, + final double coeff, + final Comparator comparator) { + final double[] xSmallest = original[0].getPointRef(); + // Perform a linear transformation on all the simplex points, + // except the first one. + setPoint(0, original[0]); + final int dim = getDimension(); + for (int i = 1; i < getSize(); i++) { + final double[] xOriginal = original[i].getPointRef(); + final double[] xTransformed = new double[dim]; + for (int j = 0; j < dim; j++) { + xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]); + } + setPoint(i, new PointValuePair(xTransformed, Double.NaN, false)); + } + + // Evaluate the simplex. + evaluate(evaluationFunction, comparator); + + return getPoint(0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.java new file mode 100644 index 000000000..dd5d08584 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.optimization.BaseMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.function.Logit; +import com.fr.third.org.apache.commons.math3.analysis.function.Sigmoid; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

      Adapter for mapping bounded {@link MultivariateFunction} to unbounded ones.

      + * + *

      + * This adapter can be used to wrap functions subject to simple bounds on + * parameters so they can be used by optimizers that do not directly + * support simple bounds. + *

      + *

      + * The principle is that the user function that will be wrapped will see its + * parameters bounded as required, i.e when its {@code value} method is called + * with argument array {@code point}, the elements array will fulfill requirement + * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components + * may be unbounded or bounded only on one side if the corresponding bound is + * set to an infinite value. The optimizer will not manage the user function by + * itself, but it will handle this adapter and it is this adapter that will take + * care the bounds are fulfilled. The adapter {@link #value(double[])} method will + * be called by the optimizer with unbound parameters, and the adapter will map + * the unbounded value to the bounded range using appropriate functions like + * {@link Sigmoid} for double bounded elements for example. + *

      + *

      + * As the optimizer sees only unbounded parameters, it should be noted that the + * start point or simplex expected by the optimizer should be unbounded, so the + * user is responsible for converting his bounded point to unbounded by calling + * {@link #boundedToUnbounded(double[])} before providing them to the optimizer. + * For the same reason, the point returned by the {@link + * BaseMultivariateOptimizer#optimize(int, + * MultivariateFunction, GoalType, double[])} + * method is unbounded. So to convert this point to bounded, users must call + * {@link #unboundedToBounded(double[])} by themselves!

      + *

      + * This adapter is only a poor man solution to simple bounds optimization constraints + * that can be used with simple optimizers like {@link SimplexOptimizer} with {@link + * NelderMeadSimplex} or {@link MultiDirectionalSimplex}. A better solution is to use + * an optimizer that directly supports simple bounds like {@link CMAESOptimizer} or + * {@link BOBYQAOptimizer}. One caveat of this poor man solution is that behavior near + * the bounds may be numerically unstable as bounds are mapped from infinite values. + * Another caveat is that convergence values are evaluated by the optimizer with respect + * to unbounded variables, so there will be scales differences when converted to bounded + * variables. + *

      + * + * @see MultivariateFunctionPenaltyAdapter + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ + +@Deprecated +public class MultivariateFunctionMappingAdapter implements MultivariateFunction { + + /** Underlying bounded function. */ + private final MultivariateFunction bounded; + + /** Mapping functions. */ + private final Mapper[] mappers; + + /** Simple constructor. + * @param bounded bounded function + * @param lower lower bounds for each element of the input parameters array + * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for + * unbounded values) + * @param upper upper bounds for each element of the input parameters array + * (some elements may be set to {@code Double.POSITIVE_INFINITY} for + * unbounded values) + * @exception DimensionMismatchException if lower and upper bounds are not + * consistent, either according to dimension or to values + */ + public MultivariateFunctionMappingAdapter(final MultivariateFunction bounded, + final double[] lower, final double[] upper) { + + // safety checks + MathUtils.checkNotNull(lower); + MathUtils.checkNotNull(upper); + if (lower.length != upper.length) { + throw new DimensionMismatchException(lower.length, upper.length); + } + for (int i = 0; i < lower.length; ++i) { + // note the following test is written in such a way it also fails for NaN + if (!(upper[i] >= lower[i])) { + throw new NumberIsTooSmallException(upper[i], lower[i], true); + } + } + + this.bounded = bounded; + this.mappers = new Mapper[lower.length]; + for (int i = 0; i < mappers.length; ++i) { + if (Double.isInfinite(lower[i])) { + if (Double.isInfinite(upper[i])) { + // element is unbounded, no transformation is needed + mappers[i] = new NoBoundsMapper(); + } else { + // element is simple-bounded on the upper side + mappers[i] = new UpperBoundMapper(upper[i]); + } + } else { + if (Double.isInfinite(upper[i])) { + // element is simple-bounded on the lower side + mappers[i] = new LowerBoundMapper(lower[i]); + } else { + // element is double-bounded + mappers[i] = new LowerUpperBoundMapper(lower[i], upper[i]); + } + } + } + + } + + /** Map an array from unbounded to bounded. + * @param point unbounded value + * @return bounded value + */ + public double[] unboundedToBounded(double[] point) { + + // map unbounded input point to bounded point + final double[] mapped = new double[mappers.length]; + for (int i = 0; i < mappers.length; ++i) { + mapped[i] = mappers[i].unboundedToBounded(point[i]); + } + + return mapped; + + } + + /** Map an array from bounded to unbounded. + * @param point bounded value + * @return unbounded value + */ + public double[] boundedToUnbounded(double[] point) { + + // map bounded input point to unbounded point + final double[] mapped = new double[mappers.length]; + for (int i = 0; i < mappers.length; ++i) { + mapped[i] = mappers[i].boundedToUnbounded(point[i]); + } + + return mapped; + + } + + /** Compute the underlying function value from an unbounded point. + *

      + * This method simply bounds the unbounded point using the mappings + * set up at construction and calls the underlying function using + * the bounded point. + *

      + * @param point unbounded value + * @return underlying function value + * @see #unboundedToBounded(double[]) + */ + public double value(double[] point) { + return bounded.value(unboundedToBounded(point)); + } + + /** Mapping interface. */ + private interface Mapper { + + /** Map a value from unbounded to bounded. + * @param y unbounded value + * @return bounded value + */ + double unboundedToBounded(double y); + + /** Map a value from bounded to unbounded. + * @param x bounded value + * @return unbounded value + */ + double boundedToUnbounded(double x); + + } + + /** Local class for no bounds mapping. */ + private static class NoBoundsMapper implements Mapper { + + /** Simple constructor. + */ + NoBoundsMapper() { + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return y; + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return x; + } + + } + + /** Local class for lower bounds mapping. */ + private static class LowerBoundMapper implements Mapper { + + /** Low bound. */ + private final double lower; + + /** Simple constructor. + * @param lower lower bound + */ + LowerBoundMapper(final double lower) { + this.lower = lower; + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return lower + FastMath.exp(y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return FastMath.log(x - lower); + } + + } + + /** Local class for upper bounds mapping. */ + private static class UpperBoundMapper implements Mapper { + + /** Upper bound. */ + private final double upper; + + /** Simple constructor. + * @param upper upper bound + */ + UpperBoundMapper(final double upper) { + this.upper = upper; + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return upper - FastMath.exp(-y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return -FastMath.log(upper - x); + } + + } + + /** Local class for lower and bounds mapping. */ + private static class LowerUpperBoundMapper implements Mapper { + + /** Function from unbounded to bounded. */ + private final UnivariateFunction boundingFunction; + + /** Function from bounded to unbounded. */ + private final UnivariateFunction unboundingFunction; + + /** Simple constructor. + * @param lower lower bound + * @param upper upper bound + */ + LowerUpperBoundMapper(final double lower, final double upper) { + boundingFunction = new Sigmoid(lower, upper); + unboundingFunction = new Logit(lower, upper); + } + + /** {@inheritDoc} */ + public double unboundedToBounded(final double y) { + return boundingFunction.value(y); + } + + /** {@inheritDoc} */ + public double boundedToUnbounded(final double x) { + return unboundingFunction.value(x); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java new file mode 100644 index 000000000..6e3a3174a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

      Adapter extending bounded {@link MultivariateFunction} to an unbouded + * domain using a penalty function.

      + * + *

      + * This adapter can be used to wrap functions subject to simple bounds on + * parameters so they can be used by optimizers that do not directly + * support simple bounds. + *

      + *

      + * The principle is that the user function that will be wrapped will see its + * parameters bounded as required, i.e when its {@code value} method is called + * with argument array {@code point}, the elements array will fulfill requirement + * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components + * may be unbounded or bounded only on one side if the corresponding bound is + * set to an infinite value. The optimizer will not manage the user function by + * itself, but it will handle this adapter and it is this adapter that will take + * care the bounds are fulfilled. The adapter {@link #value(double[])} method will + * be called by the optimizer with unbound parameters, and the adapter will check + * if the parameters is within range or not. If it is in range, then the underlying + * user function will be called, and if it is not the value of a penalty function + * will be returned instead. + *

      + *

      + * This adapter is only a poor man solution to simple bounds optimization constraints + * that can be used with simple optimizers like {@link SimplexOptimizer} with {@link + * NelderMeadSimplex} or {@link MultiDirectionalSimplex}. A better solution is to use + * an optimizer that directly supports simple bounds like {@link CMAESOptimizer} or + * {@link BOBYQAOptimizer}. One caveat of this poor man solution is that if start point + * or start simplex is completely outside of the allowed range, only the penalty function + * is used, and the optimizer may converge without ever entering the range. + *

      + * + * @see MultivariateFunctionMappingAdapter + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ + +@Deprecated +public class MultivariateFunctionPenaltyAdapter implements MultivariateFunction { + + /** Underlying bounded function. */ + private final MultivariateFunction bounded; + + /** Lower bounds. */ + private final double[] lower; + + /** Upper bounds. */ + private final double[] upper; + + /** Penalty offset. */ + private final double offset; + + /** Penalty scales. */ + private final double[] scale; + + /** Simple constructor. + *

      + * When the optimizer provided points are out of range, the value of the + * penalty function will be used instead of the value of the underlying + * function. In order for this penalty to be effective in rejecting this + * point during the optimization process, the penalty function value should + * be defined with care. This value is computed as: + *

      +     *   penalty(point) = offset + ∑i[scale[i] * √|point[i]-boundary[i]|]
      +     * 
      + * where indices i correspond to all the components that violates their boundaries. + *

      + *

      + * So when attempting a function minimization, offset should be larger than + * the maximum expected value of the underlying function and scale components + * should all be positive. When attempting a function maximization, offset + * should be lesser than the minimum expected value of the underlying function + * and scale components should all be negative. + * minimization, and lesser than the minimum expected value of the underlying + * function when attempting maximization. + *

      + *

      + * These choices for the penalty function have two properties. First, all out + * of range points will return a function value that is worse than the value + * returned by any in range point. Second, the penalty is worse for large + * boundaries violation than for small violations, so the optimizer has an hint + * about the direction in which it should search for acceptable points. + *

      + * @param bounded bounded function + * @param lower lower bounds for each element of the input parameters array + * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for + * unbounded values) + * @param upper upper bounds for each element of the input parameters array + * (some elements may be set to {@code Double.POSITIVE_INFINITY} for + * unbounded values) + * @param offset base offset of the penalty function + * @param scale scale of the penalty function + * @exception DimensionMismatchException if lower bounds, upper bounds and + * scales are not consistent, either according to dimension or to bounadary + * values + */ + public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded, + final double[] lower, final double[] upper, + final double offset, final double[] scale) { + + // safety checks + MathUtils.checkNotNull(lower); + MathUtils.checkNotNull(upper); + MathUtils.checkNotNull(scale); + if (lower.length != upper.length) { + throw new DimensionMismatchException(lower.length, upper.length); + } + if (lower.length != scale.length) { + throw new DimensionMismatchException(lower.length, scale.length); + } + for (int i = 0; i < lower.length; ++i) { + // note the following test is written in such a way it also fails for NaN + if (!(upper[i] >= lower[i])) { + throw new NumberIsTooSmallException(upper[i], lower[i], true); + } + } + + this.bounded = bounded; + this.lower = lower.clone(); + this.upper = upper.clone(); + this.offset = offset; + this.scale = scale.clone(); + + } + + /** Compute the underlying function value from an unbounded point. + *

      + * This method simply returns the value of the underlying function + * if the unbounded point already fulfills the bounds, and compute + * a replacement value using the offset and scale if bounds are + * violated, without calling the function at all. + *

      + * @param point unbounded point + * @return either underlying function value or penalty function value + */ + public double value(double[] point) { + + for (int i = 0; i < scale.length; ++i) { + if ((point[i] < lower[i]) || (point[i] > upper[i])) { + // bound violation starting at this component + double sum = 0; + for (int j = i; j < scale.length; ++j) { + final double overshoot; + if (point[j] < lower[j]) { + overshoot = scale[j] * (lower[j] - point[j]); + } else if (point[j] > upper[j]) { + overshoot = scale[j] * (point[j] - upper[j]); + } else { + overshoot = 0; + } + sum += FastMath.sqrt(overshoot); + } + return offset + sum; + } + } + + // all boundaries are fulfilled, we are in the expected + // domain of the underlying function + return bounded.value(point); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.java new file mode 100644 index 000000000..5bfa6aab9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; + +/** + * This class implements the Nelder-Mead simplex algorithm. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class NelderMeadSimplex extends AbstractSimplex { + /** Default value for {@link #rho}: {@value}. */ + private static final double DEFAULT_RHO = 1; + /** Default value for {@link #khi}: {@value}. */ + private static final double DEFAULT_KHI = 2; + /** Default value for {@link #gamma}: {@value}. */ + private static final double DEFAULT_GAMMA = 0.5; + /** Default value for {@link #sigma}: {@value}. */ + private static final double DEFAULT_SIGMA = 0.5; + /** Reflection coefficient. */ + private final double rho; + /** Expansion coefficient. */ + private final double khi; + /** Contraction coefficient. */ + private final double gamma; + /** Shrinkage coefficient. */ + private final double sigma; + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param n Dimension of the simplex. + */ + public NelderMeadSimplex(final int n) { + this(n, 1d); + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param n Dimension of the simplex. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + */ + public NelderMeadSimplex(final int n, double sideLength) { + this(n, sideLength, + DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param sideLength Length of the sides of the default (hypercube) + * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + */ + public NelderMeadSimplex(final int n, double sideLength, + final double rho, final double khi, + final double gamma, final double sigma) { + super(n, sideLength); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param n Dimension of the simplex. See + * {@link AbstractSimplex#AbstractSimplex(int)}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + */ + public NelderMeadSimplex(final int n, + final double rho, final double khi, + final double gamma, final double sigma) { + this(n, 1d, rho, khi, gamma, sigma); + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + */ + public NelderMeadSimplex(final double[] steps) { + this(steps, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param steps Steps along the canonical axes representing box edges. + * They may be negative but not zero. See + * {@link AbstractSimplex#AbstractSimplex(double[])}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + * @throws IllegalArgumentException if one of the steps is zero. + */ + public NelderMeadSimplex(final double[] steps, + final double rho, final double khi, + final double gamma, final double sigma) { + super(steps); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** + * Build a Nelder-Mead simplex with default coefficients. + * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + */ + public NelderMeadSimplex(final double[][] referenceSimplex) { + this(referenceSimplex, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA); + } + + /** + * Build a Nelder-Mead simplex with specified coefficients. + * + * @param referenceSimplex Reference simplex. See + * {@link AbstractSimplex#AbstractSimplex(double[][])}. + * @param rho Reflection coefficient. + * @param khi Expansion coefficient. + * @param gamma Contraction coefficient. + * @param sigma Shrinkage coefficient. + * @throws NotStrictlyPositiveException + * if the reference simplex does not contain at least one point. + * @throws DimensionMismatchException + * if there is a dimension mismatch in the reference simplex. + */ + public NelderMeadSimplex(final double[][] referenceSimplex, + final double rho, final double khi, + final double gamma, final double sigma) { + super(referenceSimplex); + + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** {@inheritDoc} */ + @Override + public void iterate(final MultivariateFunction evaluationFunction, + final Comparator comparator) { + // The simplex has n + 1 points if dimension is n. + final int n = getDimension(); + + // Interesting values. + final PointValuePair best = getPoint(0); + final PointValuePair secondBest = getPoint(n - 1); + final PointValuePair worst = getPoint(n); + final double[] xWorst = worst.getPointRef(); + + // Compute the centroid of the best vertices (dismissing the worst + // point at index n). + final double[] centroid = new double[n]; + for (int i = 0; i < n; i++) { + final double[] x = getPoint(i).getPointRef(); + for (int j = 0; j < n; j++) { + centroid[j] += x[j]; + } + } + final double scaling = 1.0 / n; + for (int j = 0; j < n; j++) { + centroid[j] *= scaling; + } + + // compute the reflection point + final double[] xR = new double[n]; + for (int j = 0; j < n; j++) { + xR[j] = centroid[j] + rho * (centroid[j] - xWorst[j]); + } + final PointValuePair reflected + = new PointValuePair(xR, evaluationFunction.value(xR), false); + + if (comparator.compare(best, reflected) <= 0 && + comparator.compare(reflected, secondBest) < 0) { + // Accept the reflected point. + replaceWorstPoint(reflected, comparator); + } else if (comparator.compare(reflected, best) < 0) { + // Compute the expansion point. + final double[] xE = new double[n]; + for (int j = 0; j < n; j++) { + xE[j] = centroid[j] + khi * (xR[j] - centroid[j]); + } + final PointValuePair expanded + = new PointValuePair(xE, evaluationFunction.value(xE), false); + + if (comparator.compare(expanded, reflected) < 0) { + // Accept the expansion point. + replaceWorstPoint(expanded, comparator); + } else { + // Accept the reflected point. + replaceWorstPoint(reflected, comparator); + } + } else { + if (comparator.compare(reflected, worst) < 0) { + // Perform an outside contraction. + final double[] xC = new double[n]; + for (int j = 0; j < n; j++) { + xC[j] = centroid[j] + gamma * (xR[j] - centroid[j]); + } + final PointValuePair outContracted + = new PointValuePair(xC, evaluationFunction.value(xC), false); + if (comparator.compare(outContracted, reflected) <= 0) { + // Accept the contraction point. + replaceWorstPoint(outContracted, comparator); + return; + } + } else { + // Perform an inside contraction. + final double[] xC = new double[n]; + for (int j = 0; j < n; j++) { + xC[j] = centroid[j] - gamma * (centroid[j] - xWorst[j]); + } + final PointValuePair inContracted + = new PointValuePair(xC, evaluationFunction.value(xC), false); + + if (comparator.compare(inContracted, worst) < 0) { + // Accept the contraction point. + replaceWorstPoint(inContracted, comparator); + return; + } + } + + // Perform a shrink. + final double[] xSmallest = getPoint(0).getPointRef(); + for (int i = 1; i <= n; i++) { + final double[] x = getPoint(i).getPoint(); + for (int j = 0; j < n; j++) { + x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]); + } + setPoint(i, new PointValuePair(x, Double.NaN, false)); + } + evaluate(evaluationFunction, comparator); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/PowellOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/PowellOptimizer.java new file mode 100644 index 000000000..bf09d999b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/PowellOptimizer.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.direct; + +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optimization.univariate.BracketFinder; +import com.fr.third.org.apache.commons.math3.optimization.univariate.BrentOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.univariate.SimpleUnivariateValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.univariate.UnivariatePointValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer; + +/** + * 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 + * SciPy). + *
      + * The default stopping criterion is based on the differences of the + * function value between two successive iterations. It is however possible + * to define a custom convergence checker that might terminate the algorithm + * earlier. + *
      + * The internal line search optimizer is a {@link BrentOptimizer} with a + * convergence checker set to {@link SimpleUnivariateValueChecker}. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.2 + */ +@Deprecated +public class PowellOptimizer + extends BaseAbstractMultivariateOptimizer + implements MultivariateOptimizer { + /** + * Minimum relative tolerance. + */ + private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d); + /** + * Relative threshold. + */ + private final double relativeThreshold; + /** + * Absolute threshold. + */ + private final double absoluteThreshold; + /** + * Line search. + */ + private final LineSearch line; + + /** + * This constructor allows to specify a user-defined convergence checker, + * in addition to the parameters that control the default convergence + * checking procedure. + *
      + * The internal line search tolerances are set to the square-root of their + * corresponding value in the multivariate optimizer. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param checker Convergence checker. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs, + ConvergenceChecker checker) { + this(rel, abs, FastMath.sqrt(rel), FastMath.sqrt(abs), checker); + } + + /** + * This constructor allows to specify a user-defined convergence checker, + * in addition to the parameters that control the default convergence + * checking procedure and the line search tolerances. + * + * @param rel Relative threshold for this optimizer. + * @param abs Absolute threshold for this optimizer. + * @param lineRel Relative threshold for the internal line search optimizer. + * @param lineAbs Absolute threshold for the internal line search optimizer. + * @param checker Convergence checker. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs, + double lineRel, + double lineAbs, + ConvergenceChecker checker) { + super(checker); + + if (rel < MIN_RELATIVE_TOLERANCE) { + throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true); + } + if (abs <= 0) { + throw new NotStrictlyPositiveException(abs); + } + relativeThreshold = rel; + absoluteThreshold = abs; + + // Create the line search optimizer. + line = new LineSearch(lineRel, + lineAbs); + } + + /** + * The parameters control the default convergence checking procedure. + *
      + * The internal line search tolerances are set to the square-root of their + * corresponding value in the multivariate optimizer. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public PowellOptimizer(double rel, + double abs) { + this(rel, abs, null); + } + + /** + * Builds an instance with the default convergence checking procedure. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param lineRel Relative threshold for the internal line search optimizer. + * @param lineAbs Absolute threshold for the internal line search optimizer. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + * @since 3.1 + */ + public PowellOptimizer(double rel, + double abs, + double lineRel, + double lineAbs) { + this(rel, abs, lineRel, lineAbs, null); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + final GoalType goal = getGoalType(); + final double[] guess = getStartPoint(); + final int n = guess.length; + + final double[][] direc = new double[n][n]; + for (int i = 0; i < n; i++) { + direc[i][i] = 1; + } + + final ConvergenceChecker checker + = getConvergenceChecker(); + + double[] x = guess; + double fVal = computeObjectiveValue(x); + double[] x1 = x.clone(); + int iter = 0; + while (true) { + ++iter; + + double fX = fVal; + double fX2 = 0; + double delta = 0; + int bigInd = 0; + double alphaMin = 0; + + for (int i = 0; i < n; i++) { + final double[] d = MathArrays.copyOf(direc[i]); + + fX2 = fVal; + + final UnivariatePointValuePair optimum = line.search(x, d); + fVal = optimum.getValue(); + alphaMin = optimum.getPoint(); + final double[][] result = newPointAndDirection(x, d, alphaMin); + x = result[0]; + + if ((fX2 - fVal) > delta) { + delta = fX2 - fVal; + bigInd = i; + } + } + + // Default convergence check. + boolean stop = 2 * (fX - fVal) <= + (relativeThreshold * (FastMath.abs(fX) + FastMath.abs(fVal)) + + absoluteThreshold); + + final PointValuePair previous = new PointValuePair(x1, fX); + final PointValuePair current = new PointValuePair(x, fVal); + if (!stop && checker != null) { + stop = checker.converged(iter, previous, current); + } + if (stop) { + if (goal == GoalType.MINIMIZE) { + return (fVal < fX) ? current : previous; + } else { + return (fVal > fX) ? current : previous; + } + } + + final double[] d = new double[n]; + final double[] x2 = new double[n]; + for (int i = 0; i < n; i++) { + d[i] = x[i] - x1[i]; + x2[i] = 2 * x[i] - x1[i]; + } + + x1 = x.clone(); + fX2 = computeObjectiveValue(x2); + + if (fX > fX2) { + double t = 2 * (fX + fX2 - 2 * fVal); + double temp = fX - fVal - delta; + t *= temp * temp; + temp = fX - fX2; + t -= delta * temp * temp; + + if (t < 0.0) { + final UnivariatePointValuePair optimum = line.search(x, d); + fVal = optimum.getValue(); + alphaMin = optimum.getPoint(); + final double[][] result = newPointAndDirection(x, d, alphaMin); + x = result[0]; + + final int lastInd = n - 1; + direc[bigInd] = direc[lastInd]; + direc[lastInd] = result[1]; + } + } + } + } + + /** + * Compute a new point (in the original space) and a new direction + * vector, resulting from the line search. + * + * @param p Point used in the line search. + * @param d Direction used in the line search. + * @param optimum Optimum found by the line search. + * @return a 2-element array containing the new point (at index 0) and + * the new direction (at index 1). + */ + private double[][] newPointAndDirection(double[] p, + double[] d, + double optimum) { + final int n = p.length; + final double[] nP = new double[n]; + final double[] nD = new double[n]; + for (int i = 0; i < n; i++) { + nD[i] = d[i] * optimum; + nP[i] = p[i] + nD[i]; + } + + final double[][] result = new double[2][]; + result[0] = nP; + result[1] = nD; + + return result; + } + + /** + * Class for finding the minimum of the objective function along a given + * direction. + */ + private class LineSearch extends BrentOptimizer { + /** + * Value that will pass the precondition check for {@link BrentOptimizer} + * but will not pass the convergence check, so that the custom checker + * will always decide when to stop the line search. + */ + private static final double REL_TOL_UNUSED = 1e-15; + /** + * Value that will pass the precondition check for {@link BrentOptimizer} + * but will not pass the convergence check, so that the custom checker + * will always decide when to stop the line search. + */ + private static final double ABS_TOL_UNUSED = Double.MIN_VALUE; + /** + * Automatic bracketing. + */ + private final BracketFinder bracket = new BracketFinder(); + + /** + * The "BrentOptimizer" default stopping criterion uses the tolerances + * to check the domain (point) values, not the function values. + * We thus create a custom checker to use function values. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + */ + LineSearch(double rel, + double abs) { + super(REL_TOL_UNUSED, + ABS_TOL_UNUSED, + new SimpleUnivariateValueChecker(rel, abs)); + } + + /** + * Find the minimum of the function {@code f(p + alpha * d)}. + * + * @param p Starting point. + * @param d Search direction. + * @return the optimum. + * @throws TooManyEvaluationsException + * if the number of evaluations is exceeded. + */ + public UnivariatePointValuePair search(final double[] p, final double[] d) { + final int n = p.length; + final UnivariateFunction f = new UnivariateFunction() { + /** {@inheritDoc} */ + public double value(double alpha) { + final double[] x = new double[n]; + for (int i = 0; i < n; i++) { + x[i] = p[i] + alpha * d[i]; + } + final double obj = PowellOptimizer.this.computeObjectiveValue(x); + return obj; + } + }; + + final GoalType goal = PowellOptimizer.this.getGoalType(); + bracket.search(f, goal, 0, 1); + // Passing "MAX_VALUE" as a dummy value because it is the enclosing + // class that counts the number of evaluations (and will eventually + // generate the exception). + return optimize(Integer.MAX_VALUE, f, goal, + bracket.getLo(), bracket.getHi(), bracket.getMid()); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/SimplexOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/SimplexOptimizer.java new file mode 100644 index 000000000..0872869bf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/SimplexOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.direct; + +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; + +/** + * This class implements simplex-based direct search optimization. + * + *

      + * Direct search methods only use objective function values, they do + * not need derivatives and don't either try to compute approximation + * of the derivatives. According to a 1996 paper by Margaret H. Wright + * (Direct + * Search Methods: Once Scorned, Now Respectable), 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 + * not too bad 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. + *

      + *

      + * 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. + *

      + *

      + * The {@link #setSimplex(AbstractSimplex) setSimplex} method must + * be called prior to calling the {@code optimize} method. + *

      + *

      + * Each call to {@link #optimize(int,MultivariateFunction,GoalType,double[]) + * optimize} will re-use the start configuration of the current simplex and + * move it such that its first vertex is at the provided start point of the + * optimization. If the {@code optimize} method is called to solve a different + * problem and the number of parameters change, the simplex must be + * re-initialized to one with the appropriate dimensions. + *

      + *

      + * Convergence is checked by providing the worst points of + * previous and current simplex to the convergence checker, not the best + * ones. + *

      + *

      + * This simplex optimizer implementation does not directly support constrained + * optimization with simple bounds, so for such optimizations, either a more + * dedicated method must be used like {@link CMAESOptimizer} or {@link + * BOBYQAOptimizer}, or the optimized method must be wrapped in an adapter like + * {@link MultivariateFunctionMappingAdapter} or {@link + * MultivariateFunctionPenaltyAdapter}. + *

      + * + * @see AbstractSimplex + * @see MultivariateFunctionMappingAdapter + * @see MultivariateFunctionPenaltyAdapter + * @see CMAESOptimizer + * @see BOBYQAOptimizer + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@SuppressWarnings("boxing") // deprecated anyway +@Deprecated +public class SimplexOptimizer + extends BaseAbstractMultivariateOptimizer + implements MultivariateOptimizer { + /** Simplex. */ + private AbstractSimplex simplex; + + /** + * Constructor using a default {@link SimpleValueChecker convergence + * checker}. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + public SimplexOptimizer() { + this(new SimpleValueChecker()); + } + + /** + * @param checker Convergence checker. + */ + public SimplexOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * @param rel Relative threshold. + * @param abs Absolute threshold. + */ + public SimplexOptimizer(double rel, double abs) { + this(new SimpleValueChecker(rel, abs)); + } + + /** + * Set the simplex algorithm. + * + * @param simplex Simplex. + * @deprecated As of 3.1. The initial simplex can now be passed as an + * argument of the {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])} + * method. + */ + @Deprecated + public void setSimplex(AbstractSimplex simplex) { + parseOptimizationData(simplex); + } + + /** + * Optimize an objective function. + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param goalType Optimization type. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link InitialGuess InitialGuess}
      • + *
      • {@link AbstractSimplex}
      • + *
      + * @return the point/value pair giving the optimal value for objective + * function. + */ + @Override + protected PointValuePair optimizeInternal(int maxEval, MultivariateFunction f, + GoalType goalType, + OptimizationData... optData) { + // Scan "optData" for the input specific to this optimizer. + parseOptimizationData(optData); + + // The parent's method will retrieve the common parameters from + // "optData" and call "doOptimize". + return super.optimizeInternal(maxEval, f, goalType, optData); + } + + /** + * Scans the list of (required and optional) optimization data that + * characterize the problem. + * + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link AbstractSimplex}
      • + *
      + */ + private void parseOptimizationData(OptimizationData... optData) { + // The existing values (as set by the previous call) are reused if + // not provided in the argument list. + for (OptimizationData data : optData) { + if (data instanceof AbstractSimplex) { + simplex = (AbstractSimplex) data; + continue; + } + } + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + if (simplex == null) { + throw new NullArgumentException(); + } + + // Indirect call to "computeObjectiveValue" in order to update the + // evaluations counter. + final MultivariateFunction evalFunc + = new MultivariateFunction() { + /** {@inheritDoc} */ + public double value(double[] point) { + return computeObjectiveValue(point); + } + }; + + final boolean isMinim = getGoalType() == GoalType.MINIMIZE; + final Comparator comparator + = new Comparator() { + /** {@inheritDoc} */ + public int compare(final PointValuePair o1, + final PointValuePair o2) { + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return isMinim ? Double.compare(v1, v2) : Double.compare(v2, v1); + } + }; + + // Initialize search. + simplex.build(getStartPoint()); + simplex.evaluate(evalFunc, comparator); + + PointValuePair[] previous = null; + int iteration = 0; + final ConvergenceChecker checker = getConvergenceChecker(); + while (true) { + if (iteration > 0) { + boolean converged = true; + for (int i = 0; i < simplex.getSize(); i++) { + PointValuePair prev = previous[i]; + converged = converged && + checker.converged(iteration, prev, simplex.getPoint(i)); + } + if (converged) { + // We have found an optimum. + return simplex.getPoint(0); + } + } + + // We still need to search. + previous = simplex.getPoints(); + simplex.iterate(evalFunc, comparator); + ++iteration; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/package-info.java new file mode 100644 index 000000000..41e01d0a3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/direct/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + *

      + * This package provides optimization algorithms that don't require derivatives. + *

      + * + */ +package com.fr.third.org.apache.commons.math3.optimization.direct; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/CurveFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/CurveFitter.java new file mode 100644 index 000000000..7d33db8ed --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/CurveFitter.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.fitting; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import com.fr.third.org.apache.commons.math3.analysis.ParametricUnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.MultivariateDifferentiableVectorOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.PointVectorValuePair; + +/** Fitter for parametric univariate real functions y = f(x). + *
      + * When a univariate real function y = f(x) does depend on some + * unknown parameters p0, p1 ... pn-1, + * this class can be used to find these parameters. It does this + * by fitting the curve so it remains very close to a set of + * observed points (x0, y0), (x1, + * y1) ... (xk-1, yk-1). This fitting + * is done by finding the parameters values that minimizes the objective + * function ∑(yi-f(xi))2. This is + * really a least squares problem. + * + * @param Function to use for the fit. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class CurveFitter { + + /** Optimizer to use for the fitting. + * @deprecated as of 3.1 replaced by {@link #optimizer} + */ + @Deprecated + private final DifferentiableMultivariateVectorOptimizer oldOptimizer; + + /** Optimizer to use for the fitting. */ + private final MultivariateDifferentiableVectorOptimizer optimizer; + + /** Observed points. */ + private final List observations; + + /** Simple constructor. + * @param optimizer optimizer to use for the fitting + * @deprecated as of 3.1 replaced by {@link #CurveFitter(MultivariateDifferentiableVectorOptimizer)} + */ + @Deprecated + public CurveFitter(final DifferentiableMultivariateVectorOptimizer optimizer) { + this.oldOptimizer = optimizer; + this.optimizer = null; + observations = new ArrayList(); + } + + /** Simple constructor. + * @param optimizer optimizer to use for the fitting + * @since 3.1 + */ + public CurveFitter(final MultivariateDifferentiableVectorOptimizer optimizer) { + this.oldOptimizer = null; + this.optimizer = optimizer; + observations = new ArrayList(); + } + + /** Add an observed (x,y) point to the sample with unit weight. + *

      Calling this method is equivalent to call + * {@code addObservedPoint(1.0, x, y)}.

      + * @param x abscissa of the point + * @param y observed value of the point at x, after fitting we should + * have f(x) as close as possible to this value + * @see #addObservedPoint(double, double, double) + * @see #addObservedPoint(WeightedObservedPoint) + * @see #getObservations() + */ + public void addObservedPoint(double x, double y) { + addObservedPoint(1.0, x, y); + } + + /** Add an observed weighted (x,y) point to the sample. + * @param weight weight of the observed point in the fit + * @param x abscissa of the point + * @param y observed value of the point at x, after fitting we should + * have f(x) as close as possible to this value + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(WeightedObservedPoint) + * @see #getObservations() + */ + public void addObservedPoint(double weight, double x, double y) { + observations.add(new WeightedObservedPoint(weight, x, y)); + } + + /** Add an observed weighted (x,y) point to the sample. + * @param observed observed point to add + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(double, double, double) + * @see #getObservations() + */ + public void addObservedPoint(WeightedObservedPoint observed) { + observations.add(observed); + } + + /** Get the observed points. + * @return observed points + * @see #addObservedPoint(double, double) + * @see #addObservedPoint(double, double, double) + * @see #addObservedPoint(WeightedObservedPoint) + */ + public WeightedObservedPoint[] getObservations() { + return observations.toArray(new WeightedObservedPoint[observations.size()]); + } + + /** + * Remove all observations. + */ + public void clearObservations() { + observations.clear(); + } + + /** + * Fit a curve. + * This method compute the coefficients of the curve that best + * fit the sample of observed points previously given through calls + * to the {@link #addObservedPoint(WeightedObservedPoint) + * addObservedPoint} method. + * + * @param f parametric function to fit. + * @param initialGuess first guess of the function parameters. + * @return the fitted parameters. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + */ + public double[] fit(T f, final double[] initialGuess) { + return fit(Integer.MAX_VALUE, f, initialGuess); + } + + /** + * Fit a curve. + * This method compute the coefficients of the curve that best + * fit the sample of observed points previously given through calls + * to the {@link #addObservedPoint(WeightedObservedPoint) + * addObservedPoint} method. + * + * @param f parametric function to fit. + * @param initialGuess first guess of the function parameters. + * @param maxEval Maximum number of function evaluations. + * @return the fitted parameters. + * @throws TooManyEvaluationsException + * if the number of allowed evaluations is exceeded. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @since 3.0 + */ + public double[] fit(int maxEval, T f, + final double[] initialGuess) { + // prepare least squares problem + double[] target = new double[observations.size()]; + double[] weights = new double[observations.size()]; + int i = 0; + for (WeightedObservedPoint point : observations) { + target[i] = point.getY(); + weights[i] = point.getWeight(); + ++i; + } + + // perform the fit + final PointVectorValuePair optimum; + if (optimizer == null) { + // to be removed in 4.0 + optimum = oldOptimizer.optimize(maxEval, new OldTheoreticalValuesFunction(f), + target, weights, initialGuess); + } else { + optimum = optimizer.optimize(maxEval, new TheoreticalValuesFunction(f), + target, weights, initialGuess); + } + + // extract the coefficients + return optimum.getPointRef(); + } + + /** Vectorial function computing function theoretical values. */ + @Deprecated + private class OldTheoreticalValuesFunction + implements DifferentiableMultivariateVectorFunction { + /** Function to fit. */ + private final ParametricUnivariateFunction f; + + /** Simple constructor. + * @param f function to fit. + */ + OldTheoreticalValuesFunction(final ParametricUnivariateFunction f) { + this.f = f; + } + + /** {@inheritDoc} */ + public MultivariateMatrixFunction jacobian() { + return new MultivariateMatrixFunction() { + /** {@inheritDoc} */ + public double[][] value(double[] point) { + final double[][] jacobian = new double[observations.size()][]; + + int i = 0; + for (WeightedObservedPoint observed : observations) { + jacobian[i++] = f.gradient(observed.getX(), point); + } + + return jacobian; + } + }; + } + + /** {@inheritDoc} */ + public double[] value(double[] point) { + // compute the residuals + final double[] values = new double[observations.size()]; + int i = 0; + for (WeightedObservedPoint observed : observations) { + values[i++] = f.value(observed.getX(), point); + } + + return values; + } + } + + /** Vectorial function computing function theoretical values. */ + private class TheoreticalValuesFunction implements MultivariateDifferentiableVectorFunction { + + /** Function to fit. */ + private final ParametricUnivariateFunction f; + + /** Simple constructor. + * @param f function to fit. + */ + TheoreticalValuesFunction(final ParametricUnivariateFunction f) { + this.f = f; + } + + /** {@inheritDoc} */ + public double[] value(double[] point) { + // compute the residuals + final double[] values = new double[observations.size()]; + int i = 0; + for (WeightedObservedPoint observed : observations) { + values[i++] = f.value(observed.getX(), point); + } + + return values; + } + + /** {@inheritDoc} */ + public DerivativeStructure[] value(DerivativeStructure[] point) { + + // extract parameters + final double[] parameters = new double[point.length]; + for (int k = 0; k < point.length; ++k) { + parameters[k] = point[k].getValue(); + } + + // compute the residuals + final DerivativeStructure[] values = new DerivativeStructure[observations.size()]; + int i = 0; + for (WeightedObservedPoint observed : observations) { + + // build the DerivativeStructure by adding first the value as a constant + // and then adding derivatives + DerivativeStructure vi = new DerivativeStructure(point.length, 1, f.value(observed.getX(), parameters)); + for (int k = 0; k < point.length; ++k) { + vi = vi.add(new DerivativeStructure(point.length, 1, k, 0.0)); + } + + values[i++] = vi; + + } + + return values; + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/GaussianFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/GaussianFitter.java new file mode 100644 index 000000000..e7fad679d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/GaussianFitter.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.fitting; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.function.Gaussian; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Fits points to a {@link + * Gaussian.Parametric Gaussian} function. + *

      + * Usage example: + *

      + *   GaussianFitter fitter = new GaussianFitter(
      + *     new LevenbergMarquardtOptimizer());
      + *   fitter.addObservedPoint(4.0254623,  531026.0);
      + *   fitter.addObservedPoint(4.03128248, 984167.0);
      + *   fitter.addObservedPoint(4.03839603, 1887233.0);
      + *   fitter.addObservedPoint(4.04421621, 2687152.0);
      + *   fitter.addObservedPoint(4.05132976, 3461228.0);
      + *   fitter.addObservedPoint(4.05326982, 3580526.0);
      + *   fitter.addObservedPoint(4.05779662, 3439750.0);
      + *   fitter.addObservedPoint(4.0636168,  2877648.0);
      + *   fitter.addObservedPoint(4.06943698, 2175960.0);
      + *   fitter.addObservedPoint(4.07525716, 1447024.0);
      + *   fitter.addObservedPoint(4.08237071, 717104.0);
      + *   fitter.addObservedPoint(4.08366408, 620014.0);
      + *   double[] parameters = fitter.fit();
      + * 
      + * + * @since 2.2 + * @deprecated As of 3.1 (to be removed in 4.0). + */ +@Deprecated +public class GaussianFitter extends CurveFitter { + /** + * Constructs an instance using the specified optimizer. + * + * @param optimizer Optimizer to use for the fitting. + */ + public GaussianFitter(DifferentiableMultivariateVectorOptimizer optimizer) { + super(optimizer); + } + + /** + * Fits a Gaussian function to the observed points. + * + * @param initialGuess First guess values in the following order: + *
        + *
      • Norm
      • + *
      • Mean
      • + *
      • Sigma
      • + *
      + * @return the parameters of the Gaussian function that best fits the + * observed points (in the same order as above). + * @since 3.0 + */ + public double[] fit(double[] initialGuess) { + final Gaussian.Parametric f = new Gaussian.Parametric() { + /** {@inheritDoc} */ + @Override + public double value(double x, double ... p) { + double v = Double.POSITIVE_INFINITY; + try { + v = super.value(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + + /** {@inheritDoc} */ + @Override + public double[] gradient(double x, double ... p) { + double[] v = { Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY }; + try { + v = super.gradient(x, p); + } catch (NotStrictlyPositiveException e) { // NOPMD + // Do nothing. + } + return v; + } + }; + + return fit(f, initialGuess); + } + + /** + * Fits a Gaussian function to the observed points. + * + * @return the parameters of the Gaussian function that best fits the + * observed points (in the same order as above). + */ + public double[] fit() { + final double[] guess = (new ParameterGuesser(getObservations())).guess(); + return fit(guess); + } + + /** + * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma} + * of a {@link Gaussian.Parametric} + * based on the specified observed points. + */ + public static class ParameterGuesser { + /** Normalization factor. */ + private final double norm; + /** Mean. */ + private final double mean; + /** Standard deviation. */ + private final double sigma; + + /** + * Constructs instance with the specified observed points. + * + * @param observations Observed points from which to guess the + * parameters of the Gaussian. + * @throws NullArgumentException if {@code observations} is + * {@code null}. + * @throws NumberIsTooSmallException if there are less than 3 + * observations. + */ + public ParameterGuesser(WeightedObservedPoint[] observations) { + if (observations == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + if (observations.length < 3) { + throw new NumberIsTooSmallException(observations.length, 3, true); + } + + final WeightedObservedPoint[] sorted = sortObservations(observations); + final double[] params = basicGuess(sorted); + + norm = params[0]; + mean = params[1]; + sigma = params[2]; + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
        + *
      • Normalization factor
      • + *
      • Mean
      • + *
      • Standard deviation
      • + *
      + */ + public double[] guess() { + return new double[] { norm, mean, sigma }; + } + + /** + * Sort the observations. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) { + final WeightedObservedPoint[] observations = unsorted.clone(); + final Comparator cmp + = new Comparator() { + /** {@inheritDoc} */ + public int compare(WeightedObservedPoint p1, + WeightedObservedPoint p2) { + if (p1 == null && p2 == null) { + return 0; + } + if (p1 == null) { + return -1; + } + if (p2 == null) { + return 1; + } + final int cmpX = Double.compare(p1.getX(), p2.getX()); + if (cmpX < 0) { + return -1; + } + if (cmpX > 0) { + return 1; + } + final int cmpY = Double.compare(p1.getY(), p2.getY()); + if (cmpY < 0) { + return -1; + } + if (cmpY > 0) { + return 1; + } + final int cmpW = Double.compare(p1.getWeight(), p2.getWeight()); + if (cmpW < 0) { + return -1; + } + if (cmpW > 0) { + return 1; + } + return 0; + } + }; + + Arrays.sort(observations, cmp); + return observations; + } + + /** + * Guesses the parameters based on the specified observed points. + * + * @param points Observed points, sorted. + * @return the guessed parameters (normalization factor, mean and + * sigma). + */ + private double[] basicGuess(WeightedObservedPoint[] points) { + final int maxYIdx = findMaxY(points); + final double n = points[maxYIdx].getY(); + final double m = points[maxYIdx].getX(); + + double fwhmApprox; + try { + final double halfY = n + ((m - n) / 2); + final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY); + final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY); + fwhmApprox = fwhmX2 - fwhmX1; + } catch (OutOfRangeException e) { + // TODO: Exceptions should not be used for flow control. + fwhmApprox = points[points.length - 1].getX() - points[0].getX(); + } + final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2))); + + return new double[] { n, m, s }; + } + + /** + * Finds index of point in specified points with the largest Y. + * + * @param points Points to search. + * @return the index in specified points array. + */ + private int findMaxY(WeightedObservedPoint[] points) { + int maxYIdx = 0; + for (int i = 1; i < points.length; i++) { + if (points[i].getY() > points[maxYIdx].getY()) { + maxYIdx = i; + } + } + return maxYIdx; + } + + /** + * Interpolates using the specified points to determine X at the + * specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start the search for + * interpolation bounds points. + * @param idxStep Index step for searching interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the value of X for the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private double interpolateXAtY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + final WeightedObservedPoint[] twoPoints + = getInterpolationPointsForY(points, startIdx, idxStep, y); + final WeightedObservedPoint p1 = twoPoints[0]; + final WeightedObservedPoint p2 = twoPoints[1]; + if (p1.getY() == y) { + return p1.getX(); + } + if (p2.getY() == y) { + return p2.getX(); + } + return p1.getX() + (((y - p1.getY()) * (p2.getX() - p1.getX())) / + (p2.getY() - p1.getY())); + } + + /** + * Gets the two bounding interpolation points from the specified points + * suitable for determining X at the specified Y. + * + * @param points Points to use for interpolation. + * @param startIdx Index within points from which to start search for + * interpolation bounds points. + * @param idxStep Index step for search for interpolation bounds points. + * @param y Y value for which X should be determined. + * @return the array containing two points suitable for determining X at + * the specified Y. + * @throws ZeroException if {@code idxStep} is 0. + * @throws OutOfRangeException if specified {@code y} is not within the + * range of the specified {@code points}. + */ + private WeightedObservedPoint[] getInterpolationPointsForY(WeightedObservedPoint[] points, + int startIdx, + int idxStep, + double y) + throws OutOfRangeException { + if (idxStep == 0) { + throw new ZeroException(); + } + for (int i = startIdx; + idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length; + i += idxStep) { + final WeightedObservedPoint p1 = points[i]; + final WeightedObservedPoint p2 = points[i + idxStep]; + if (isBetween(y, p1.getY(), p2.getY())) { + if (idxStep < 0) { + return new WeightedObservedPoint[] { p2, p1 }; + } else { + return new WeightedObservedPoint[] { p1, p2 }; + } + } + } + + // Boundaries are replaced by dummy values because the raised + // exception is caught and the message never displayed. + // TODO: Exceptions should not be used for flow control. + throw new OutOfRangeException(y, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY); + } + + /** + * Determines whether a value is between two other values. + * + * @param value Value to test whether it is between {@code boundary1} + * and {@code boundary2}. + * @param boundary1 One end of the range. + * @param boundary2 Other end of the range. + * @return {@code true} if {@code value} is between {@code boundary1} and + * {@code boundary2} (inclusive), {@code false} otherwise. + */ + private boolean isBetween(double value, + double boundary1, + double boundary2) { + return (value >= boundary1 && value <= boundary2) || + (value >= boundary2 && value <= boundary1); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java new file mode 100644 index 000000000..5823953d4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java @@ -0,0 +1,384 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.fitting; + +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.analysis.function.HarmonicOscillator; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Class that implements a curve fitting specialized for sinusoids. + * + * Harmonic fitting is a very simple case of curve fitting. The + * estimated coefficients are the amplitude a, the pulsation ω and + * the phase φ: f (t) = a cos (ω t + φ). They are + * searched by a least square estimator initialized with a rough guess + * based on integrals. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class HarmonicFitter extends CurveFitter { + /** + * Simple constructor. + * @param optimizer Optimizer to use for the fitting. + */ + public HarmonicFitter(final DifferentiableMultivariateVectorOptimizer optimizer) { + super(optimizer); + } + + /** + * Fit an harmonic function to the observed points. + * + * @param initialGuess First guess values in the following order: + *
        + *
      • Amplitude
      • + *
      • Angular frequency
      • + *
      • Phase
      • + *
      + * @return the parameters of the harmonic function that best fits the + * observed points (in the same order as above). + */ + public double[] fit(double[] initialGuess) { + return fit(new HarmonicOscillator.Parametric(), initialGuess); + } + + /** + * Fit an harmonic function to the observed points. + * An initial guess will be automatically computed. + * + * @return the parameters of the harmonic function that best fits the + * observed points (see the other {@link #fit(double[]) fit} method. + * @throws NumberIsTooSmallException if the sample is too short for the + * the first guess to be computed. + * @throws ZeroException if the first guess cannot be computed because + * the abscissa range is zero. + */ + public double[] fit() { + return fit((new ParameterGuesser(getObservations())).guess()); + } + + /** + * This class guesses harmonic coefficients from a sample. + *

      The algorithm used to guess the coefficients is as follows:

      + * + *

      We know f (t) at some sampling points ti and want to find a, + * ω and φ such that f (t) = a cos (ω t + φ). + *

      + * + *

      From the analytical expression, we can compute two primitives : + *

      +     *     If2  (t) = ∫ f2  = a2 × [t + S (t)] / 2
      +     *     If'2 (t) = ∫ f'2 = a2 ω2 × [t - S (t)] / 2
      +     *     where S (t) = sin (2 (ω t + φ)) / (2 ω)
      +     * 
      + *

      + * + *

      We can remove S between these expressions : + *

      +     *     If'2 (t) = a2 ω2 t - ω2 If2 (t)
      +     * 
      + *

      + * + *

      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) + *

      + * + *

      From the primitive, we can deduce the same form for definite + * integrals between t1 and ti for each ti : + *

      +     *   If2 (ti) - If2 (t1) = A × (ti - t1) + B × (If2 (ti) - If2 (t1))
      +     * 
      + *

      + * + *

      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. + *

      + * + *

      For a bilinear expression z (xi, yi) = A × xi + B × yi, the + * coefficients A and B that minimize a least square criterion + * ∑ (zi - z (xi, yi))2 are given by these expressions:

      + *
      +     *
      +     *         ∑yiyi ∑xizi - ∑xiyi ∑yizi
      +     *     A = ------------------------
      +     *         ∑xixi ∑yiyi - ∑xiyi ∑xiyi
      +     *
      +     *         ∑xixi ∑yizi - ∑xiyi ∑xizi
      +     *     B = ------------------------
      +     *         ∑xixi ∑yiyi - ∑xiyi ∑xiyi
      +     * 
      + *

      + * + * + *

      In fact, we can assume both a and ω are positive and + * compute them directly, knowing that A = a2 ω2 and that + * B = - ω2. The complete algorithm is therefore:

      + *
      +     *
      +     * for each ti from t1 to tn-1, compute:
      +     *   f  (ti)
      +     *   f' (ti) = (f (ti+1) - f(ti-1)) / (ti+1 - ti-1)
      +     *   xi = ti - t1
      +     *   yi = ∫ f2 from t1 to ti
      +     *   zi = ∫ f'2 from t1 to ti
      +     *   update the sums ∑xixi, ∑yiyi, ∑xiyi, ∑xizi and ∑yizi
      +     * end for
      +     *
      +     *            |--------------------------
      +     *         \  | ∑yiyi ∑xizi - ∑xiyi ∑yizi
      +     * a     =  \ | ------------------------
      +     *           \| ∑xiyi ∑xizi - ∑xixi ∑yizi
      +     *
      +     *
      +     *            |--------------------------
      +     *         \  | ∑xiyi ∑xizi - ∑xixi ∑yizi
      +     * ω     =  \ | ------------------------
      +     *           \| ∑xixi ∑yiyi - ∑xiyi ∑xiyi
      +     *
      +     * 
      + *

      + * + *

      Once we know ω, we can compute: + *

      +     *    fc = ω f (t) cos (ω t) - f' (t) sin (ω t)
      +     *    fs = ω f (t) sin (ω t) + f' (t) cos (ω t)
      +     * 
      + *

      + * + *

      It appears that fc = a ω cos (φ) and + * fs = -a ω sin (φ), so we can use these + * expressions to compute φ. The best estimate over the sample is + * given by averaging these expressions. + *

      + * + *

      Since integrals and means are involved in the preceding + * estimations, these operations run in O(n) time, where n is the + * number of measurements.

      + */ + public static class ParameterGuesser { + /** Amplitude. */ + private final double a; + /** Angular frequency. */ + private final double omega; + /** Phase. */ + private final double phi; + + /** + * Simple constructor. + * + * @param observations Sampled observations. + * @throws NumberIsTooSmallException if the sample is too short. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + */ + public ParameterGuesser(WeightedObservedPoint[] observations) { + if (observations.length < 4) { + throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, + observations.length, 4, true); + } + + final WeightedObservedPoint[] sorted = sortObservations(observations); + + final double aOmega[] = guessAOmega(sorted); + a = aOmega[0]; + omega = aOmega[1]; + + phi = guessPhi(sorted); + } + + /** + * Gets an estimation of the parameters. + * + * @return the guessed parameters, in the following order: + *
        + *
      • Amplitude
      • + *
      • Angular frequency
      • + *
      • Phase
      • + *
      + */ + public double[] guess() { + return new double[] { a, omega, phi }; + } + + /** + * Sort the observations with respect to the abscissa. + * + * @param unsorted Input observations. + * @return the input observations, sorted. + */ + private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) { + final WeightedObservedPoint[] observations = unsorted.clone(); + + // Since the samples are almost always already sorted, this + // method is implemented as an insertion sort that reorders the + // elements in place. Insertion sort is very efficient in this case. + WeightedObservedPoint curr = observations[0]; + for (int j = 1; j < observations.length; ++j) { + WeightedObservedPoint prec = curr; + curr = observations[j]; + if (curr.getX() < prec.getX()) { + // the current element should be inserted closer to the beginning + int i = j - 1; + WeightedObservedPoint mI = observations[i]; + while ((i >= 0) && (curr.getX() < mI.getX())) { + observations[i + 1] = mI; + if (i-- != 0) { + mI = observations[i]; + } + } + observations[i + 1] = curr; + curr = observations[j]; + } + } + + return observations; + } + + /** + * Estimate a first guess of the amplitude and angular frequency. + * This method assumes that the {@link #sortObservations(WeightedObservedPoint[])} method + * has been called previously. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @throws ZeroException if the abscissa range is zero. + * @throws MathIllegalStateException when the guessing procedure cannot + * produce sensible results. + * @return the guessed amplitude (at index 0) and circular frequency + * (at index 1). + */ + private double[] guessAOmega(WeightedObservedPoint[] observations) { + final double[] aOmega = new double[2]; + + // initialize the sums for the linear model between the two integrals + double sx2 = 0; + double sy2 = 0; + double sxy = 0; + double sxz = 0; + double syz = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + double f2Integral = 0; + double fPrime2Integral = 0; + final double startX = currentX; + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + + // update the integrals of f2 and f'2 + // considering a linear model for f (and therefore constant f') + final double dx = currentX - previousX; + final double dy = currentY - previousY; + final double f2StepIntegral = + dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3; + final double fPrime2StepIntegral = dy * dy / dx; + + final double x = currentX - startX; + f2Integral += f2StepIntegral; + fPrime2Integral += fPrime2StepIntegral; + + sx2 += x * x; + sy2 += f2Integral * f2Integral; + sxy += x * f2Integral; + sxz += x * fPrime2Integral; + syz += f2Integral * fPrime2Integral; + } + + // compute the amplitude and pulsation coefficients + double c1 = sy2 * sxz - sxy * syz; + double c2 = sxy * sxz - sx2 * syz; + double c3 = sx2 * sy2 - sxy * sxy; + if ((c1 / c2 < 0) || (c2 / c3 < 0)) { + final int last = observations.length - 1; + // Range of the observations, assuming that the + // observations are sorted. + final double xRange = observations[last].getX() - observations[0].getX(); + if (xRange == 0) { + throw new ZeroException(); + } + aOmega[1] = 2 * Math.PI / xRange; + + double yMin = Double.POSITIVE_INFINITY; + double yMax = Double.NEGATIVE_INFINITY; + for (int i = 1; i < observations.length; ++i) { + final double y = observations[i].getY(); + if (y < yMin) { + yMin = y; + } + if (y > yMax) { + yMax = y; + } + } + aOmega[0] = 0.5 * (yMax - yMin); + } else { + if (c2 == 0) { + // In some ill-conditioned cases (cf. MATH-844), the guesser + // procedure cannot produce sensible results. + throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR); + } + + aOmega[0] = FastMath.sqrt(c1 / c2); + aOmega[1] = FastMath.sqrt(c2 / c3); + } + + return aOmega; + } + + /** + * Estimate a first guess of the phase. + * + * @param observations Observations, sorted w.r.t. abscissa. + * @return the guessed phase. + */ + private double guessPhi(WeightedObservedPoint[] observations) { + // initialize the means + double fcMean = 0; + double fsMean = 0; + + double currentX = observations[0].getX(); + double currentY = observations[0].getY(); + for (int i = 1; i < observations.length; ++i) { + // one step forward + final double previousX = currentX; + final double previousY = currentY; + currentX = observations[i].getX(); + currentY = observations[i].getY(); + final double currentYPrime = (currentY - previousY) / (currentX - previousX); + + double omegaX = omega * currentX; + double cosine = FastMath.cos(omegaX); + double sine = FastMath.sin(omegaX); + fcMean += omega * currentY * cosine - currentYPrime * sine; + fsMean += omega * currentY * sine + currentYPrime * cosine; + } + + return FastMath.atan2(-fsMean, fcMean); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/PolynomialFitter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/PolynomialFitter.java new file mode 100644 index 000000000..bc29ee4d5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/PolynomialFitter.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 com.fr.third.org.apache.commons.math3.optimization.fitting; + +import com.fr.third.org.apache.commons.math3.analysis.polynomials.PolynomialFunction; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer; + +/** + * Polynomial fitting is a very simple case of {@link CurveFitter curve fitting}. + * The estimated coefficients are the polynomial coefficients (see the + * {@link #fit(double[]) fit} method). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class PolynomialFitter extends CurveFitter { + /** Polynomial degree. + * @deprecated + */ + @Deprecated + private final int degree; + + /** + * Simple constructor. + *

      The polynomial fitter built this way are complete polynomials, + * ie. a n-degree polynomial has n+1 coefficients.

      + * + * @param degree Maximal degree of the polynomial. + * @param optimizer Optimizer to use for the fitting. + * @deprecated Since 3.1 (to be removed in 4.0). Please use + * {@link #PolynomialFitter(DifferentiableMultivariateVectorOptimizer)} instead. + */ + @Deprecated + public PolynomialFitter(int degree, final DifferentiableMultivariateVectorOptimizer optimizer) { + super(optimizer); + this.degree = degree; + } + + /** + * Simple constructor. + * + * @param optimizer Optimizer to use for the fitting. + * @since 3.1 + */ + public PolynomialFitter(DifferentiableMultivariateVectorOptimizer optimizer) { + super(optimizer); + degree = -1; // To avoid compilation error until the instance variable is removed. + } + + /** + * Get the polynomial fitting the weighted (x, y) points. + * + * @return the coefficients of the polynomial that best fits the observed points. + * @throws ConvergenceException + * if the algorithm failed to converge. + * @deprecated Since 3.1 (to be removed in 4.0). Please use {@link #fit(double[])} instead. + */ + @Deprecated + public double[] fit() { + return fit(new PolynomialFunction.Parametric(), new double[degree + 1]); + } + + /** + * Get the coefficients of the polynomial fitting the weighted data points. + * The degree of the fitting polynomial is {@code guess.length - 1}. + * + * @param guess First guess for the coefficients. They must be sorted in + * increasing order of the polynomial's degree. + * @param maxEval Maximum number of evaluations of the polynomial. + * @return the coefficients of the polynomial that best fits the observed points. + * @throws TooManyEvaluationsException if + * the number of evaluations exceeds {@code maxEval}. + * @throws ConvergenceException + * if the algorithm failed to converge. + * @since 3.1 + */ + public double[] fit(int maxEval, double[] guess) { + return fit(maxEval, new PolynomialFunction.Parametric(), guess); + } + + /** + * Get the coefficients of the polynomial fitting the weighted data points. + * The degree of the fitting polynomial is {@code guess.length - 1}. + * + * @param guess First guess for the coefficients. They must be sorted in + * increasing order of the polynomial's degree. + * @return the coefficients of the polynomial that best fits the observed points. + * @throws ConvergenceException + * if the algorithm failed to converge. + * @since 3.1 + */ + public double[] fit(double[] guess) { + return fit(new PolynomialFunction.Parametric(), guess); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java new file mode 100644 index 000000000..24436b5e9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.fitting; + +import java.io.Serializable; + +/** This class is a simple container for weighted observed point in + * {@link CurveFitter curve fitting}. + *

      Instances of this class are guaranteed to be immutable.

      + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class WeightedObservedPoint implements Serializable { + + /** Serializable version id. */ + private static final long serialVersionUID = 5306874947404636157L; + + /** Weight of the measurement in the fitting process. */ + private final double weight; + + /** Abscissa of the point. */ + private final double x; + + /** Observed value of the function at x. */ + private final double y; + + /** Simple constructor. + * @param weight weight of the measurement in the fitting process + * @param x abscissa of the measurement + * @param y ordinate of the measurement + */ + public WeightedObservedPoint(final double weight, final double x, final double y) { + this.weight = weight; + this.x = x; + this.y = y; + } + + /** Get the weight of the measurement in the fitting process. + * @return weight of the measurement in the fitting process + */ + public double getWeight() { + return weight; + } + + /** Get the abscissa of the point. + * @return abscissa of the point + */ + public double getX() { + return x; + } + + /** Get the observed value of the function at x. + * @return observed value of the function at x + */ + public double getY() { + return y; + } + +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/package-info.java new file mode 100644 index 000000000..1b0847a03 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/fitting/package-info.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * This package provides classes to perform curve fitting. + * + *

      Curve fitting is a special case of a least squares problem + * were the parameters are the coefficients of a function f + * whose graph y=f(x) should pass through sample points, and + * were the objective function is the squared sum of residuals + * f(xi)-yi for observed points + * (xi, yi).

      + * + * + */ +package com.fr.third.org.apache.commons.math3.optimization.fitting; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java new file mode 100644 index 000000000..4ca28ab7a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.general; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.GradientFunction; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer; + +/** + * Base class for implementing optimizers for multivariate scalar + * differentiable functions. + * It contains boiler-plate code for dealing with gradient evaluation. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public abstract class AbstractDifferentiableOptimizer + extends BaseAbstractMultivariateOptimizer { + /** + * Objective function gradient. + */ + private MultivariateVectorFunction gradient; + + /** + * @param checker Convergence checker. + */ + protected AbstractDifferentiableOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Compute the gradient vector. + * + * @param evaluationPoint Point at which the gradient must be evaluated. + * @return the gradient at the specified point. + */ + protected double[] computeObjectiveGradient(final double[] evaluationPoint) { + return gradient.value(evaluationPoint); + } + + /** + * {@inheritDoc} + * + * @deprecated In 3.1. Please use + * {@link #optimizeInternal(int,MultivariateDifferentiableFunction,GoalType,OptimizationData[])} + * instead. + */ + @Override@Deprecated + protected PointValuePair optimizeInternal(final int maxEval, + final MultivariateDifferentiableFunction f, + final GoalType goalType, + final double[] startPoint) { + return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint)); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair optimizeInternal(final int maxEval, + final MultivariateDifferentiableFunction f, + final GoalType goalType, + final OptimizationData... optData) { + // Store optimization problem characteristics. + gradient = new GradientFunction(f); + + // Perform optimization. + return super.optimizeInternal(maxEval, f, goalType, optData); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.java new file mode 100644 index 000000000..3baeb5512 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.general; + +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.SimpleVectorValueChecker; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.DiagonalMatrix; +import com.fr.third.org.apache.commons.math3.linear.DecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.EigenDecomposition; +import com.fr.third.org.apache.commons.math3.optimization.OptimizationData; +import com.fr.third.org.apache.commons.math3.optimization.InitialGuess; +import com.fr.third.org.apache.commons.math3.optimization.Target; +import com.fr.third.org.apache.commons.math3.optimization.Weight; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateVectorOptimizer; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Base class for implementing least squares optimizers. + * It handles the boilerplate methods associated to thresholds settings, + * Jacobian and error estimation. + *
      + * This class constructs the Jacobian matrix of the function argument in method + * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int, + * MultivariateVectorFunction,OptimizationData[]) + * optimize} and assumes that the rows of that matrix iterate on the model + * functions while the columns iterate on the parameters; thus, the numbers + * of rows is equal to the dimension of the + * {@link Target Target} while + * the number of columns is equal to the dimension of the + * {@link InitialGuess InitialGuess}. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 1.2 + */ +@Deprecated +public abstract class AbstractLeastSquaresOptimizer + extends BaseAbstractMultivariateVectorOptimizer + implements DifferentiableMultivariateVectorOptimizer { + /** + * Singularity threshold (cf. {@link #getCovariances(double)}). + * @deprecated As of 3.1. + */ + @Deprecated + private static final double DEFAULT_SINGULARITY_THRESHOLD = 1e-14; + /** + * Jacobian matrix of the weighted residuals. + * This matrix is in canonical form just after the calls to + * {@link #updateJacobian()}, but may be modified by the solver + * in the derived class (the {@link LevenbergMarquardtOptimizer + * Levenberg-Marquardt optimizer} does this). + * @deprecated As of 3.1. To be removed in 4.0. Please use + * {@link #computeWeightedJacobian(double[])} instead. + */ + @Deprecated + protected double[][] weightedResidualJacobian; + /** Number of columns of the jacobian matrix. + * @deprecated As of 3.1. + */ + @Deprecated + protected int cols; + /** Number of rows of the jacobian matrix. + * @deprecated As of 3.1. + */ + @Deprecated + protected int rows; + /** Current point. + * @deprecated As of 3.1. + */ + @Deprecated + protected double[] point; + /** Current objective function value. + * @deprecated As of 3.1. + */ + @Deprecated + protected double[] objective; + /** Weighted residuals + * @deprecated As of 3.1. + */ + @Deprecated + protected double[] weightedResiduals; + /** Cost value (square root of the sum of the residuals). + * @deprecated As of 3.1. Field to become "private" in 4.0. + * Please use {@link #setCost(double)}. + */ + @Deprecated + protected double cost; + /** Objective function derivatives. */ + private MultivariateDifferentiableVectorFunction jF; + /** Number of evaluations of the Jacobian. */ + private int jacobianEvaluations; + /** Square-root of the weight matrix. */ + private RealMatrix weightMatrixSqrt; + + /** + * Simple constructor with default settings. + * The convergence check is set to a {@link + * SimpleVectorValueChecker}. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + protected AbstractLeastSquaresOptimizer() {} + + /** + * @param checker Convergence checker. + */ + protected AbstractLeastSquaresOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * @return the number of evaluations of the Jacobian function. + */ + public int getJacobianEvaluations() { + return jacobianEvaluations; + } + + /** + * Update the jacobian matrix. + * + * @throws DimensionMismatchException if the Jacobian dimension does not + * match problem dimension. + * @deprecated As of 3.1. Please use {@link #computeWeightedJacobian(double[])} + * instead. + */ + @Deprecated + protected void updateJacobian() { + final RealMatrix weightedJacobian = computeWeightedJacobian(point); + weightedResidualJacobian = weightedJacobian.scalarMultiply(-1).getData(); + } + + /** + * Computes the Jacobian matrix. + * + * @param params Model parameters at which to compute the Jacobian. + * @return the weighted Jacobian: W1/2 J. + * @throws DimensionMismatchException if the Jacobian dimension does not + * match problem dimension. + * @since 3.1 + */ + protected RealMatrix computeWeightedJacobian(double[] params) { + ++jacobianEvaluations; + + final DerivativeStructure[] dsPoint = new DerivativeStructure[params.length]; + final int nC = params.length; + for (int i = 0; i < nC; ++i) { + dsPoint[i] = new DerivativeStructure(nC, 1, i, params[i]); + } + final DerivativeStructure[] dsValue = jF.value(dsPoint); + final int nR = getTarget().length; + if (dsValue.length != nR) { + throw new DimensionMismatchException(dsValue.length, nR); + } + final double[][] jacobianData = new double[nR][nC]; + for (int i = 0; i < nR; ++i) { + int[] orders = new int[nC]; + for (int j = 0; j < nC; ++j) { + orders[j] = 1; + jacobianData[i][j] = dsValue[i].getPartialDerivative(orders); + orders[j] = 0; + } + } + + return weightMatrixSqrt.multiply(MatrixUtils.createRealMatrix(jacobianData)); + } + + /** + * Update the residuals array and cost function value. + * @throws DimensionMismatchException if the dimension does not match the + * problem dimension. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @deprecated As of 3.1. Please use {@link #computeResiduals(double[])}, + * {@link #computeObjectiveValue(double[])}, {@link #computeCost(double[])} + * and {@link #setCost(double)} instead. + */ + @Deprecated + protected void updateResidualsAndCost() { + objective = computeObjectiveValue(point); + final double[] res = computeResiduals(objective); + + // Compute cost. + cost = computeCost(res); + + // Compute weighted residuals. + final ArrayRealVector residuals = new ArrayRealVector(res); + weightedResiduals = weightMatrixSqrt.operate(residuals).toArray(); + } + + /** + * Computes the cost. + * + * @param residuals Residuals. + * @return the cost. + * @see #computeResiduals(double[]) + * @since 3.1 + */ + protected double computeCost(double[] residuals) { + final ArrayRealVector r = new ArrayRealVector(residuals); + return FastMath.sqrt(r.dotProduct(getWeight().operate(r))); + } + + /** + * Get the Root Mean Square value. + * Get the Root Mean Square value, i.e. the root of the arithmetic + * mean of the square of all weighted residuals. This is related to the + * criterion that is minimized by the optimizer as follows: if + * c if the criterion, and n is the number of + * measurements, then the RMS is sqrt (c/n). + * + * @return RMS value + */ + public double getRMS() { + return FastMath.sqrt(getChiSquare() / rows); + } + + /** + * Get a Chi-Square-like value assuming the N residuals follow N + * distinct normal distributions centered on 0 and whose variances are + * the reciprocal of the weights. + * @return chi-square value + */ + public double getChiSquare() { + return cost * cost; + } + + /** + * Gets the square-root of the weight matrix. + * + * @return the square-root of the weight matrix. + * @since 3.1 + */ + public RealMatrix getWeightSquareRoot() { + return weightMatrixSqrt.copy(); + } + + /** + * Sets the cost. + * + * @param cost Cost value. + * @since 3.1 + */ + protected void setCost(double cost) { + this.cost = cost; + } + + /** + * Get the covariance matrix of the optimized parameters. + * + * @return the covariance matrix. + * @throws SingularMatrixException + * if the covariance matrix cannot be computed (singular problem). + * @see #getCovariances(double) + * @deprecated As of 3.1. Please use {@link #computeCovariances(double[],double)} + * instead. + */ + @Deprecated + public double[][] getCovariances() { + return getCovariances(DEFAULT_SINGULARITY_THRESHOLD); + } + + /** + * Get the covariance matrix of the optimized parameters. + *
      + * Note that this operation involves the inversion of the + * JTJ matrix, where {@code J} is the + * Jacobian matrix. + * The {@code threshold} parameter is a way for the caller to specify + * that the result of this computation should be considered meaningless, + * and thus trigger an exception. + * + * @param threshold Singularity threshold. + * @return the covariance matrix. + * @throws SingularMatrixException + * if the covariance matrix cannot be computed (singular problem). + * @deprecated As of 3.1. Please use {@link #computeCovariances(double[],double)} + * instead. + */ + @Deprecated + public double[][] getCovariances(double threshold) { + return computeCovariances(point, threshold); + } + + /** + * Get the covariance matrix of the optimized parameters. + *
      + * Note that this operation involves the inversion of the + * JTJ matrix, where {@code J} is the + * Jacobian matrix. + * The {@code threshold} parameter is a way for the caller to specify + * that the result of this computation should be considered meaningless, + * and thus trigger an exception. + * + * @param params Model parameters. + * @param threshold Singularity threshold. + * @return the covariance matrix. + * @throws SingularMatrixException + * if the covariance matrix cannot be computed (singular problem). + * @since 3.1 + */ + public double[][] computeCovariances(double[] params, + double threshold) { + // Set up the Jacobian. + final RealMatrix j = computeWeightedJacobian(params); + + // Compute transpose(J)J. + final RealMatrix jTj = j.transpose().multiply(j); + + // Compute the covariances matrix. + final DecompositionSolver solver + = new QRDecomposition(jTj, threshold).getSolver(); + return solver.getInverse().getData(); + } + + /** + *

      + * Returns an estimate of the standard deviation of each parameter. The + * returned values are the so-called (asymptotic) standard errors on the + * parameters, defined as {@code sd(a[i]) = sqrt(S / (n - m) * C[i][i])}, + * where {@code a[i]} is the optimized value of the {@code i}-th parameter, + * {@code S} is the minimized value of the sum of squares objective function + * (as returned by {@link #getChiSquare()}), {@code n} is the number of + * observations, {@code m} is the number of parameters and {@code C} is the + * covariance matrix. + *

      + *

      + * See also + * Wikipedia, + * or + * MathWorld, + * equations (34) and (35) for a particular case. + *

      + * + * @return an estimate of the standard deviation of the optimized parameters + * @throws SingularMatrixException + * if the covariance matrix cannot be computed. + * @throws NumberIsTooSmallException if the number of degrees of freedom is not + * positive, i.e. the number of measurements is less or equal to the number of + * parameters. + * @deprecated as of version 3.1, {@link #computeSigma(double[],double)} should be used + * instead. It should be emphasized that {@code guessParametersErrors} and + * {@code computeSigma} are not strictly equivalent. + */ + @Deprecated + public double[] guessParametersErrors() { + if (rows <= cols) { + throw new NumberIsTooSmallException(LocalizedFormats.NO_DEGREES_OF_FREEDOM, + rows, cols, false); + } + double[] errors = new double[cols]; + final double c = FastMath.sqrt(getChiSquare() / (rows - cols)); + double[][] covar = computeCovariances(point, 1e-14); + for (int i = 0; i < errors.length; ++i) { + errors[i] = FastMath.sqrt(covar[i][i]) * c; + } + return errors; + } + + /** + * Computes an estimate of the standard deviation of the parameters. The + * returned values are the square root of the diagonal coefficients of the + * covariance matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]} + * is the optimized value of the {@code i}-th parameter, and {@code C} is + * the covariance matrix. + * + * @param params Model parameters. + * @param covarianceSingularityThreshold Singularity threshold (see + * {@link #computeCovariances(double[],double) computeCovariances}). + * @return an estimate of the standard deviation of the optimized parameters + * @throws SingularMatrixException + * if the covariance matrix cannot be computed. + * @since 3.1 + */ + public double[] computeSigma(double[] params, + double covarianceSingularityThreshold) { + final int nC = params.length; + final double[] sig = new double[nC]; + final double[][] cov = computeCovariances(params, covarianceSingularityThreshold); + for (int i = 0; i < nC; ++i) { + sig[i] = FastMath.sqrt(cov[i][i]); + } + return sig; + } + + /** {@inheritDoc} + * @deprecated As of 3.1. Please use + * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int, + * MultivariateVectorFunction,OptimizationData[]) + * optimize(int,MultivariateDifferentiableVectorFunction,OptimizationData...)} + * instead. + */ + @Override + @Deprecated + public PointVectorValuePair optimize(int maxEval, + final DifferentiableMultivariateVectorFunction f, + final double[] target, final double[] weights, + final double[] startPoint) { + return optimizeInternal(maxEval, + FunctionUtils.toMultivariateDifferentiableVectorFunction(f), + new Target(target), + new Weight(weights), + new InitialGuess(startPoint)); + } + + /** + * Optimize an objective function. + * Optimization is considered to be a weighted least-squares minimization. + * The cost function to be minimized is + * ∑weighti(objectivei - targeti)2 + * + * @param f Objective function. + * @param target Target value for the objective functions at optimum. + * @param weights Weights for the least squares cost computation. + * @param startPoint Start point for optimization. + * @return the point/value pair giving the optimal value for objective + * function. + * @param maxEval Maximum number of function evaluations. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + * @deprecated As of 3.1. Please use + * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int, + * MultivariateVectorFunction,OptimizationData[]) + * optimize(int,MultivariateDifferentiableVectorFunction,OptimizationData...)} + * instead. + */ + @Deprecated + public PointVectorValuePair optimize(final int maxEval, + final MultivariateDifferentiableVectorFunction f, + final double[] target, final double[] weights, + final double[] startPoint) { + return optimizeInternal(maxEval, f, + new Target(target), + new Weight(weights), + new InitialGuess(startPoint)); + } + + /** + * Optimize an objective function. + * Optimization is considered to be a weighted least-squares minimization. + * The cost function to be minimized is + * ∑weighti(objectivei - targeti)2 + * + * @param maxEval Allowed number of evaluations of the objective function. + * @param f Objective function. + * @param optData Optimization data. The following data will be looked for: + *
        + *
      • {@link Target}
      • + *
      • {@link Weight}
      • + *
      • {@link InitialGuess}
      • + *
      + * @return the point/value pair giving the optimal value of the objective + * function. + * @throws TooManyEvaluationsException if + * the maximal number of evaluations is exceeded. + * @throws DimensionMismatchException if the target, and weight arguments + * have inconsistent dimensions. + * @see BaseAbstractMultivariateVectorOptimizer#optimizeInternal(int, + * MultivariateVectorFunction,OptimizationData[]) + * @since 3.1 + * @deprecated As of 3.1. Override is necessary only until this class's generic + * argument is changed to {@code MultivariateDifferentiableVectorFunction}. + */ + @Deprecated + protected PointVectorValuePair optimizeInternal(final int maxEval, + final MultivariateDifferentiableVectorFunction f, + OptimizationData... optData) { + // XXX Conversion will be removed when the generic argument of the + // base class becomes "MultivariateDifferentiableVectorFunction". + return super.optimizeInternal(maxEval, FunctionUtils.toDifferentiableMultivariateVectorFunction(f), optData); + } + + /** {@inheritDoc} */ + @Override + protected void setUp() { + super.setUp(); + + // Reset counter. + jacobianEvaluations = 0; + + // Square-root of the weight matrix. + weightMatrixSqrt = squareRoot(getWeight()); + + // Store least squares problem characteristics. + // XXX The conversion won't be necessary when the generic argument of + // the base class becomes "MultivariateDifferentiableVectorFunction". + // XXX "jF" is not strictly necessary anymore but is currently more + // efficient than converting the value returned from "getObjectiveFunction()" + // every time it is used. + jF = FunctionUtils.toMultivariateDifferentiableVectorFunction((DifferentiableMultivariateVectorFunction) getObjectiveFunction()); + + // Arrays shared with "private" and "protected" methods. + point = getStartPoint(); + rows = getTarget().length; + cols = point.length; + } + + /** + * Computes the residuals. + * The residual is the difference between the observed (target) + * values and the model (objective function) value. + * There is one residual for each element of the vector-valued + * function. + * + * @param objectiveValue Value of the the objective function. This is + * the value returned from a call to + * {@link #computeObjectiveValue(double[]) computeObjectiveValue} + * (whose array argument contains the model parameters). + * @return the residuals. + * @throws DimensionMismatchException if {@code params} has a wrong + * length. + * @since 3.1 + */ + protected double[] computeResiduals(double[] objectiveValue) { + final double[] target = getTarget(); + if (objectiveValue.length != target.length) { + throw new DimensionMismatchException(target.length, + objectiveValue.length); + } + + final double[] residuals = new double[target.length]; + for (int i = 0; i < target.length; i++) { + residuals[i] = target[i] - objectiveValue[i]; + } + + return residuals; + } + + /** + * Computes the square-root of the weight matrix. + * + * @param m Symmetric, positive-definite (weight) matrix. + * @return the square-root of the weight matrix. + */ + private RealMatrix squareRoot(RealMatrix m) { + if (m instanceof DiagonalMatrix) { + final int dim = m.getRowDimension(); + final RealMatrix sqrtM = new DiagonalMatrix(dim); + for (int i = 0; i < dim; i++) { + sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i))); + } + return sqrtM; + } else { + final EigenDecomposition dec = new EigenDecomposition(m); + return dec.getSquareRoot(); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.java new file mode 100644 index 000000000..b61312225 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.general; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.analysis.DifferentiableMultivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction; +import com.fr.third.org.apache.commons.math3.optimization.DifferentiableMultivariateOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; + +/** + * Base class for implementing optimizers for multivariate scalar + * differentiable functions. + * It contains boiler-plate code for dealing with gradient evaluation. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public abstract class AbstractScalarDifferentiableOptimizer + extends BaseAbstractMultivariateOptimizer + implements DifferentiableMultivariateOptimizer { + /** + * Objective function gradient. + */ + private MultivariateVectorFunction gradient; + + /** + * Simple constructor with default settings. + * The convergence check is set to a + * {@link SimpleValueChecker + * SimpleValueChecker}. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + protected AbstractScalarDifferentiableOptimizer() {} + + /** + * @param checker Convergence checker. + */ + protected AbstractScalarDifferentiableOptimizer(ConvergenceChecker checker) { + super(checker); + } + + /** + * Compute the gradient vector. + * + * @param evaluationPoint Point at which the gradient must be evaluated. + * @return the gradient at the specified point. + * @throws TooManyEvaluationsException + * if the allowed number of evaluations is exceeded. + */ + protected double[] computeObjectiveGradient(final double[] evaluationPoint) { + return gradient.value(evaluationPoint); + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair optimizeInternal(int maxEval, + final DifferentiableMultivariateFunction f, + final GoalType goalType, + final double[] startPoint) { + // Store optimization problem characteristics. + gradient = f.gradient(); + + return super.optimizeInternal(maxEval, f, goalType, startPoint); + } + + /** + * Optimize an objective function. + * + * @param f Objective function. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param startPoint Start point for optimization. + * @param maxEval Maximum number of function evaluations. + * @return the point/value pair giving the optimal value for objective + * function. + * @throws DimensionMismatchException + * if the start point dimension is wrong. + * @throws TooManyEvaluationsException + * if the maximal number of evaluations is exceeded. + * @throws NullArgumentException if + * any argument is {@code null}. + */ + public PointValuePair optimize(final int maxEval, + final MultivariateDifferentiableFunction f, + final GoalType goalType, + final double[] startPoint) { + return optimizeInternal(maxEval, + FunctionUtils.toDifferentiableMultivariateFunction(f), + goalType, + startPoint); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java new file mode 100644 index 000000000..d66d83d4e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.general; + +/** + * Available choices of update formulas for the β parameter + * in {@link NonLinearConjugateGradientOptimizer}. + *

      + * The β parameter is used to compute the successive conjugate + * search directions. For non-linear conjugate gradients, there are + * two formulas to compute β: + *

        + *
      • Fletcher-Reeves formula
      • + *
      • Polak-Ribière formula
      • + *
      + * 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. + *

      + * @see NonLinearConjugateGradientOptimizer + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public enum ConjugateGradientFormula { + + /** Fletcher-Reeves formula. */ + FLETCHER_REEVES, + + /** Polak-Ribière formula. */ + POLAK_RIBIERE + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java new file mode 100644 index 000000000..f26cdbf39 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.general; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.DecompositionSolver; +import com.fr.third.org.apache.commons.math3.linear.LUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.SimpleVectorValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.PointVectorValuePair; + +/** + * Gauss-Newton least-squares solver. + *

      + * 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. + *

      + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + * + */ +@Deprecated +public class GaussNewtonOptimizer extends AbstractLeastSquaresOptimizer { + /** Indicator for using LU decomposition. */ + private final boolean useLU; + + /** + * Simple constructor with default settings. + * The normal equations will be solved using LU decomposition and the + * convergence check is set to a {@link SimpleVectorValueChecker} + * with default tolerances. + * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()} + */ + @Deprecated + public GaussNewtonOptimizer() { + this(true); + } + + /** + * Simple constructor with default settings. + * The normal equations will be solved using LU decomposition. + * + * @param checker Convergence checker. + */ + public GaussNewtonOptimizer(ConvergenceChecker checker) { + this(true, checker); + } + + /** + * Simple constructor with default settings. + * The convergence check is set to a {@link SimpleVectorValueChecker} + * with default tolerances. + * + * @param useLU If {@code true}, the normal equations will be solved + * using LU decomposition, otherwise they will be solved using QR + * decomposition. + * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()} + */ + @Deprecated + public GaussNewtonOptimizer(final boolean useLU) { + this(useLU, new SimpleVectorValueChecker()); + } + + /** + * @param useLU If {@code true}, the normal equations will be solved + * using LU decomposition, otherwise they will be solved using QR + * decomposition. + * @param checker Convergence checker. + */ + public GaussNewtonOptimizer(final boolean useLU, + ConvergenceChecker checker) { + super(checker); + this.useLU = useLU; + } + + /** {@inheritDoc} */ + @Override + public PointVectorValuePair doOptimize() { + final ConvergenceChecker checker + = getConvergenceChecker(); + + // Computation will be useless without a checker (see "for-loop"). + if (checker == null) { + throw new NullArgumentException(); + } + + final double[] targetValues = getTarget(); + final int nR = targetValues.length; // Number of observed data. + + final RealMatrix weightMatrix = getWeight(); + // Diagonal of the weight matrix. + final double[] residualsWeights = new double[nR]; + for (int i = 0; i < nR; i++) { + residualsWeights[i] = weightMatrix.getEntry(i, i); + } + + final double[] currentPoint = getStartPoint(); + final int nC = currentPoint.length; + + // iterate until convergence is reached + PointVectorValuePair current = null; + int iter = 0; + for (boolean converged = false; !converged;) { + ++iter; + + // evaluate the objective function and its jacobian + PointVectorValuePair previous = current; + // Value of the objective function at "currentPoint". + final double[] currentObjective = computeObjectiveValue(currentPoint); + final double[] currentResiduals = computeResiduals(currentObjective); + final RealMatrix weightedJacobian = computeWeightedJacobian(currentPoint); + current = new PointVectorValuePair(currentPoint, currentObjective); + + // build the linear problem + final double[] b = new double[nC]; + final double[][] a = new double[nC][nC]; + for (int i = 0; i < nR; ++i) { + + final double[] grad = weightedJacobian.getRow(i); + final double weight = residualsWeights[i]; + final double residual = currentResiduals[i]; + + // compute the normal equation + final double wr = weight * residual; + for (int j = 0; j < nC; ++j) { + b[j] += wr * grad[j]; + } + + // build the contribution matrix for measurement i + for (int k = 0; k < nC; ++k) { + double[] ak = a[k]; + double wgk = weight * grad[k]; + for (int l = 0; l < nC; ++l) { + ak[l] += wgk * grad[l]; + } + } + } + + try { + // solve the linearized least squares problem + RealMatrix mA = new BlockRealMatrix(a); + DecompositionSolver solver = useLU ? + new LUDecomposition(mA).getSolver() : + new QRDecomposition(mA).getSolver(); + final double[] dX = solver.solve(new ArrayRealVector(b, false)).toArray(); + // update the estimated parameters + for (int i = 0; i < nC; ++i) { + currentPoint[i] += dX[i]; + } + } catch (SingularMatrixException e) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM); + } + + // Check convergence. + if (previous != null) { + converged = checker.converged(iter, previous, current); + if (converged) { + cost = computeCost(currentResiduals); + // Update (deprecated) "point" field. + point = current.getPoint(); + return current; + } + } + } + // Must never happen. + throw new MathInternalError(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java new file mode 100644 index 000000000..671c4b548 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java @@ -0,0 +1,943 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optimization.general; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optimization.PointVectorValuePair; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * This class solves a least squares problem using the Levenberg-Marquardt algorithm. + * + *

      This implementation should 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.

      + * + *

      The resolution engine is a simple translation of the MINPACK lmder 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 Analyse numérique matricielle + * appliquée à l'art de l'ingénieur, Masson 1986.

      + *

      The authors of the original fortran version are: + *

        + *
      • Argonne National Laboratory. MINPACK project. March 1980
      • + *
      • Burton S. Garbow
      • + *
      • Kenneth E. Hillstrom
      • + *
      • Jorge J. More
      • + *
      + * The redistribution policy for MINPACK is available here, for convenience, it + * is reproduced below.

      + * + * + * + * + *
      + * 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. + *
      3. 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.
      4. + *
      5. 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.
      6. + *
      7. 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.
      8. + *
      9. 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.
      10. + *
        + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + * + */ +@Deprecated +public class LevenbergMarquardtOptimizer extends AbstractLeastSquaresOptimizer { + /** Number of solved point. */ + private int solvedCols; + /** Diagonal elements of the R matrix in the Q.R. decomposition. */ + private double[] diagR; + /** Norms of the columns of the jacobian matrix. */ + private double[] jacNorm; + /** Coefficients of the Householder transforms vectors. */ + private double[] beta; + /** Columns permutation array. */ + private int[] permutation; + /** Rank of the jacobian matrix. */ + private int rank; + /** Levenberg-Marquardt parameter. */ + private double lmPar; + /** Parameters evolution direction associated with lmPar. */ + private double[] lmDir; + /** Positive input variable used in determining the initial step bound. */ + private final double initialStepBoundFactor; + /** Desired relative error in the sum of squares. */ + private final double costRelativeTolerance; + /** Desired relative error in the approximate solution parameters. */ + private final double parRelativeTolerance; + /** Desired max cosine on the orthogonality between the function vector + * and the columns of the jacobian. */ + private final double orthoTolerance; + /** Threshold for QR ranking. */ + private final double qrRankingThreshold; + /** Weighted residuals. */ + private double[] weightedResidual; + /** Weighted Jacobian. */ + private double[][] weightedJacobian; + + /** + * Build an optimizer for least squares problems with default values + * for all the tuning parameters (see the {@link + * #LevenbergMarquardtOptimizer(double,double,double,double,double) + * other contructor}. + * The default values for the algorithm settings are: + *
          + *
        • Initial step bound factor: 100
        • + *
        • Cost relative tolerance: 1e-10
        • + *
        • Parameters relative tolerance: 1e-10
        • + *
        • Orthogonality tolerance: 1e-10
        • + *
        • QR ranking threshold: {@link Precision#SAFE_MIN}
        • + *
        + */ + public LevenbergMarquardtOptimizer() { + this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN); + } + + /** + * Constructor that allows the specification of a custom convergence + * checker. + * Note that all the usual convergence checks will be disabled. + * The default values for the algorithm settings are: + *
          + *
        • Initial step bound factor: 100
        • + *
        • Cost relative tolerance: 1e-10
        • + *
        • Parameters relative tolerance: 1e-10
        • + *
        • Orthogonality tolerance: 1e-10
        • + *
        • QR ranking threshold: {@link Precision#SAFE_MIN}
        • + *
        + * + * @param checker Convergence checker. + */ + public LevenbergMarquardtOptimizer(ConvergenceChecker checker) { + this(100, checker, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN); + } + + /** + * Constructor that allows the specification of a custom convergence + * checker, in addition to the standard ones. + * + * @param initialStepBoundFactor Positive input variable used in + * determining the initial step bound. This bound is set to the + * product of initialStepBoundFactor and the euclidean norm of + * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor} + * itself. In most cases factor should lie in the interval + * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value. + * @param checker Convergence checker. + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + * @param threshold Desired threshold for QR ranking. If the squared norm + * of a column vector is smaller or equal to this threshold during QR + * decomposition, it is considered to be a zero vector and hence the rank + * of the matrix is reduced. + */ + public LevenbergMarquardtOptimizer(double initialStepBoundFactor, + ConvergenceChecker checker, + double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance, + double threshold) { + super(checker); + this.initialStepBoundFactor = initialStepBoundFactor; + this.costRelativeTolerance = costRelativeTolerance; + this.parRelativeTolerance = parRelativeTolerance; + this.orthoTolerance = orthoTolerance; + this.qrRankingThreshold = threshold; + } + + /** + * Build an optimizer for least squares problems with default values + * for some of the tuning parameters (see the {@link + * #LevenbergMarquardtOptimizer(double,double,double,double,double) + * other contructor}. + * The default values for the algorithm settings are: + *
          + *
        • Initial step bound factor}: 100
        • + *
        • QR ranking threshold}: {@link Precision#SAFE_MIN}
        • + *
        + * + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + */ + public LevenbergMarquardtOptimizer(double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance) { + this(100, + costRelativeTolerance, parRelativeTolerance, orthoTolerance, + Precision.SAFE_MIN); + } + + /** + * The arguments control the behaviour of the default convergence checking + * procedure. + * Additional criteria can defined through the setting of a {@link + * ConvergenceChecker}. + * + * @param initialStepBoundFactor Positive input variable used in + * determining the initial step bound. This bound is set to the + * product of initialStepBoundFactor and the euclidean norm of + * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor} + * itself. In most cases factor should lie in the interval + * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value. + * @param costRelativeTolerance Desired relative error in the sum of + * squares. + * @param parRelativeTolerance Desired relative error in the approximate + * solution parameters. + * @param orthoTolerance Desired max cosine on the orthogonality between + * the function vector and the columns of the Jacobian. + * @param threshold Desired threshold for QR ranking. If the squared norm + * of a column vector is smaller or equal to this threshold during QR + * decomposition, it is considered to be a zero vector and hence the rank + * of the matrix is reduced. + */ + public LevenbergMarquardtOptimizer(double initialStepBoundFactor, + double costRelativeTolerance, + double parRelativeTolerance, + double orthoTolerance, + double threshold) { + super(null); // No custom convergence criterion. + this.initialStepBoundFactor = initialStepBoundFactor; + this.costRelativeTolerance = costRelativeTolerance; + this.parRelativeTolerance = parRelativeTolerance; + this.orthoTolerance = orthoTolerance; + this.qrRankingThreshold = threshold; + } + + /** {@inheritDoc} */ + @Override + protected PointVectorValuePair doOptimize() { + final int nR = getTarget().length; // Number of observed data. + final double[] currentPoint = getStartPoint(); + final int nC = currentPoint.length; // Number of parameters. + + // arrays shared with the other private methods + solvedCols = FastMath.min(nR, nC); + diagR = new double[nC]; + jacNorm = new double[nC]; + beta = new double[nC]; + permutation = new int[nC]; + lmDir = new double[nC]; + + // local point + double delta = 0; + double xNorm = 0; + double[] diag = new double[nC]; + double[] oldX = new double[nC]; + double[] oldRes = new double[nR]; + double[] oldObj = new double[nR]; + double[] qtf = new double[nR]; + double[] work1 = new double[nC]; + double[] work2 = new double[nC]; + double[] work3 = new double[nC]; + + final RealMatrix weightMatrixSqrt = getWeightSquareRoot(); + + // Evaluate the function at the starting point and calculate its norm. + double[] currentObjective = computeObjectiveValue(currentPoint); + double[] currentResiduals = computeResiduals(currentObjective); + PointVectorValuePair current = new PointVectorValuePair(currentPoint, currentObjective); + double currentCost = computeCost(currentResiduals); + + // Outer loop. + lmPar = 0; + boolean firstIteration = true; + int iter = 0; + final ConvergenceChecker checker = getConvergenceChecker(); + while (true) { + ++iter; + final PointVectorValuePair previous = current; + + // QR decomposition of the jacobian matrix + qrDecomposition(computeWeightedJacobian(currentPoint)); + + weightedResidual = weightMatrixSqrt.operate(currentResiduals); + for (int i = 0; i < nR; i++) { + qtf[i] = weightedResidual[i]; + } + + // compute Qt.res + qTy(qtf); + + // now we don't need Q anymore, + // so let jacobian contain the R matrix with its diagonal elements + for (int k = 0; k < solvedCols; ++k) { + int pk = permutation[k]; + weightedJacobian[k][pk] = diagR[pk]; + } + + if (firstIteration) { + // scale the point according to the norms of the columns + // of the initial jacobian + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double dk = jacNorm[k]; + if (dk == 0) { + dk = 1.0; + } + double xk = dk * currentPoint[k]; + xNorm += xk * xk; + diag[k] = dk; + } + xNorm = FastMath.sqrt(xNorm); + + // initialize the step bound delta + delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm); + } + + // check orthogonality between function vector and jacobian columns + double maxCosine = 0; + if (currentCost != 0) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = jacNorm[pj]; + if (s != 0) { + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qtf[i]; + } + maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost)); + } + } + } + if (maxCosine <= orthoTolerance) { + // Convergence has been reached. + setCost(currentCost); + // Update (deprecated) "point" field. + point = current.getPoint(); + return current; + } + + // rescale if necessary + for (int j = 0; j < nC; ++j) { + diag[j] = FastMath.max(diag[j], jacNorm[j]); + } + + // Inner loop. + for (double ratio = 0; ratio < 1.0e-4;) { + + // save the state + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + oldX[pj] = currentPoint[pj]; + } + final double previousCost = currentCost; + double[] tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + tmpVec = currentObjective; + currentObjective = oldObj; + oldObj = tmpVec; + + // determine the Levenberg-Marquardt parameter + determineLMParameter(qtf, delta, diag, work1, work2, work3); + + // compute the new point and the norm of the evolution direction + double lmNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + lmDir[pj] = -lmDir[pj]; + currentPoint[pj] = oldX[pj] + lmDir[pj]; + double s = diag[pj] * lmDir[pj]; + lmNorm += s * s; + } + lmNorm = FastMath.sqrt(lmNorm); + // on the first iteration, adjust the initial step bound. + if (firstIteration) { + delta = FastMath.min(delta, lmNorm); + } + + // Evaluate the function at x + p and calculate its norm. + currentObjective = computeObjectiveValue(currentPoint); + currentResiduals = computeResiduals(currentObjective); + current = new PointVectorValuePair(currentPoint, currentObjective); + currentCost = computeCost(currentResiduals); + + // compute the scaled actual reduction + double actRed = -1.0; + if (0.1 * currentCost < previousCost) { + double r = currentCost / previousCost; + actRed = 1.0 - r * r; + } + + // compute the scaled predicted reduction + // and the scaled directional derivative + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double dirJ = lmDir[pj]; + work1[j] = 0; + for (int i = 0; i <= j; ++i) { + work1[i] += weightedJacobian[i][pj] * dirJ; + } + } + double coeff1 = 0; + for (int j = 0; j < solvedCols; ++j) { + coeff1 += work1[j] * work1[j]; + } + double pc2 = previousCost * previousCost; + coeff1 /= pc2; + double coeff2 = lmPar * lmNorm * lmNorm / pc2; + double preRed = coeff1 + 2 * coeff2; + double dirDer = -(coeff1 + coeff2); + + // ratio of the actual to the predicted reduction + ratio = (preRed == 0) ? 0 : (actRed / preRed); + + // update the step bound + if (ratio <= 0.25) { + double tmp = + (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5; + if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) { + tmp = 0.1; + } + delta = tmp * FastMath.min(delta, 10.0 * lmNorm); + lmPar /= tmp; + } else if ((lmPar == 0) || (ratio >= 0.75)) { + delta = 2 * lmNorm; + lmPar *= 0.5; + } + + // test for successful iteration. + if (ratio >= 1.0e-4) { + // successful iteration, update the norm + firstIteration = false; + xNorm = 0; + for (int k = 0; k < nC; ++k) { + double xK = diag[k] * currentPoint[k]; + xNorm += xK * xK; + } + xNorm = FastMath.sqrt(xNorm); + + // tests for convergence. + if (checker != null && checker.converged(iter, previous, current)) { + setCost(currentCost); + // Update (deprecated) "point" field. + point = current.getPoint(); + return current; + } + } else { + // failed iteration, reset the previous values + currentCost = previousCost; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + currentPoint[pj] = oldX[pj]; + } + tmpVec = weightedResidual; + weightedResidual = oldRes; + oldRes = tmpVec; + tmpVec = currentObjective; + currentObjective = oldObj; + oldObj = tmpVec; + // Reset "current" to previous values. + current = new PointVectorValuePair(currentPoint, currentObjective); + } + + // Default convergence criteria. + if ((FastMath.abs(actRed) <= costRelativeTolerance && + preRed <= costRelativeTolerance && + ratio <= 2.0) || + delta <= parRelativeTolerance * xNorm) { + setCost(currentCost); + // Update (deprecated) "point" field. + point = current.getPoint(); + return current; + } + + // tests for termination and stringent tolerances + // (2.2204e-16 is the machine epsilon for IEEE754) + if ((FastMath.abs(actRed) <= 2.2204e-16) && (preRed <= 2.2204e-16) && (ratio <= 2.0)) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE, + costRelativeTolerance); + } else if (delta <= 2.2204e-16 * xNorm) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE, + parRelativeTolerance); + } else if (maxCosine <= 2.2204e-16) { + throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE, + orthoTolerance); + } + } + } + } + + /** + * Determine the Levenberg-Marquardt parameter. + *

        This implementation is a translation in Java of the MINPACK + * lmpar + * routine.

        + *

        This method sets the lmPar and lmDir attributes.

        + *

        The authors of the original fortran function are:

        + *
          + *
        • Argonne National Laboratory. MINPACK project. March 1980
        • + *
        • Burton S. Garbow
        • + *
        • Kenneth E. Hillstrom
        • + *
        • Jorge J. More
        • + *
        + *

        Luc Maisonobe did the Java translation.

        + * + * @param qy array containing qTy + * @param delta upper bound on the euclidean norm of diagR * lmDir + * @param diag diagonal matrix + * @param work1 work array + * @param work2 work array + * @param work3 work array + */ + private void determineLMParameter(double[] qy, double delta, double[] diag, + double[] work1, double[] work2, double[] work3) { + final int nC = weightedJacobian[0].length; + + // compute and store in x the gauss-newton direction, if the + // jacobian is rank-deficient, obtain a least squares solution + for (int j = 0; j < rank; ++j) { + lmDir[permutation[j]] = qy[j]; + } + for (int j = rank; j < nC; ++j) { + lmDir[permutation[j]] = 0; + } + for (int k = rank - 1; k >= 0; --k) { + int pk = permutation[k]; + double ypk = lmDir[pk] / diagR[pk]; + for (int i = 0; i < k; ++i) { + lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk]; + } + lmDir[pk] = ypk; + } + + // evaluate the function at the origin, and test + // for acceptance of the Gauss-Newton direction + double dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work1[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double fp = dxNorm - delta; + if (fp <= 0.1 * delta) { + lmPar = 0; + return; + } + + // if the jacobian is not rank deficient, the Newton step provides + // a lower bound, parl, for the zero of the function, + // otherwise set this bound to zero + double sum2; + double parl = 0; + if (rank == solvedCols) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] *= diag[pj] / dxNorm; + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i < j; ++i) { + sum += weightedJacobian[i][pj] * work1[permutation[i]]; + } + double s = (work1[pj] - sum) / diagR[pj]; + work1[pj] = s; + sum2 += s * s; + } + parl = fp / (delta * sum2); + } + + // calculate an upper bound, paru, for the zero of the function + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qy[i]; + } + sum /= diag[pj]; + sum2 += sum * sum; + } + double gNorm = FastMath.sqrt(sum2); + double paru = gNorm / delta; + if (paru == 0) { + // 2.2251e-308 is the smallest positive real for IEE754 + paru = 2.2251e-308 / FastMath.min(delta, 0.1); + } + + // if the input par lies outside of the interval (parl,paru), + // set par to the closer endpoint + lmPar = FastMath.min(paru, FastMath.max(lmPar, parl)); + if (lmPar == 0) { + lmPar = gNorm / dxNorm; + } + + for (int countdown = 10; countdown >= 0; --countdown) { + + // evaluate the function at the current value of lmPar + if (lmPar == 0) { + lmPar = FastMath.max(2.2251e-308, 0.001 * paru); + } + double sPar = FastMath.sqrt(lmPar); + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = sPar * diag[pj]; + } + determineLMDirection(qy, work1, work2, work3); + + dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work3[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double previousFP = fp; + fp = dxNorm - delta; + + // if the function is small enough, accept the current value + // of lmPar, also test for the exceptional cases where parl is zero + if ((FastMath.abs(fp) <= 0.1 * delta) || + ((parl == 0) && (fp <= previousFP) && (previousFP < 0))) { + return; + } + + // compute the Newton correction + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = work3[pj] * diag[pj] / dxNorm; + } + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] /= work2[j]; + double tmp = work1[pj]; + for (int i = j + 1; i < solvedCols; ++i) { + work1[permutation[i]] -= weightedJacobian[i][pj] * tmp; + } + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + double s = work1[permutation[j]]; + sum2 += s * s; + } + double correction = fp / (delta * sum2); + + // depending on the sign of the function, update parl or paru. + if (fp > 0) { + parl = FastMath.max(parl, lmPar); + } else if (fp < 0) { + paru = FastMath.min(paru, lmPar); + } + + // compute an improved estimate for lmPar + lmPar = FastMath.max(parl, lmPar + correction); + + } + } + + /** + * Solve a*x = b and d*x = 0 in the least squares sense. + *

        This implementation is a translation in Java of the MINPACK + * qrsolv + * routine.

        + *

        This method sets the lmDir and lmDiag attributes.

        + *

        The authors of the original fortran function are:

        + *
          + *
        • Argonne National Laboratory. MINPACK project. March 1980
        • + *
        • Burton S. Garbow
        • + *
        • Kenneth E. Hillstrom
        • + *
        • Jorge J. More
        • + *
        + *

        Luc Maisonobe did the Java translation.

        + * + * @param qy array containing qTy + * @param diag diagonal matrix + * @param lmDiag diagonal elements associated with lmDir + * @param work work array + */ + private void determineLMDirection(double[] qy, double[] diag, + double[] lmDiag, double[] work) { + + // copy R and Qty to preserve input and initialize s + // in particular, save the diagonal elements of R in lmDir + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + for (int i = j + 1; i < solvedCols; ++i) { + weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]]; + } + lmDir[j] = diagR[pj]; + work[j] = qy[j]; + } + + // eliminate the diagonal matrix d using a Givens rotation + for (int j = 0; j < solvedCols; ++j) { + + // prepare the row of d to be eliminated, locating the + // diagonal element using p from the Q.R. factorization + int pj = permutation[j]; + double dpj = diag[pj]; + if (dpj != 0) { + Arrays.fill(lmDiag, j + 1, lmDiag.length, 0); + } + lmDiag[j] = dpj; + + // the transformations to eliminate the row of d + // modify only a single element of Qty + // beyond the first n, which is initially zero. + double qtbpj = 0; + for (int k = j; k < solvedCols; ++k) { + int pk = permutation[k]; + + // determine a Givens rotation which eliminates the + // appropriate element in the current row of d + if (lmDiag[k] != 0) { + + final double sin; + final double cos; + double rkk = weightedJacobian[k][pk]; + if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) { + final double cotan = rkk / lmDiag[k]; + sin = 1.0 / FastMath.sqrt(1.0 + cotan * cotan); + cos = sin * cotan; + } else { + final double tan = lmDiag[k] / rkk; + cos = 1.0 / FastMath.sqrt(1.0 + tan * tan); + sin = cos * tan; + } + + // compute the modified diagonal element of R and + // the modified element of (Qty,0) + weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k]; + final double temp = cos * work[k] + sin * qtbpj; + qtbpj = -sin * work[k] + cos * qtbpj; + work[k] = temp; + + // accumulate the tranformation in the row of s + for (int i = k + 1; i < solvedCols; ++i) { + double rik = weightedJacobian[i][pk]; + final double temp2 = cos * rik + sin * lmDiag[i]; + lmDiag[i] = -sin * rik + cos * lmDiag[i]; + weightedJacobian[i][pk] = temp2; + } + } + } + + // store the diagonal element of s and restore + // the corresponding diagonal element of R + lmDiag[j] = weightedJacobian[j][permutation[j]]; + weightedJacobian[j][permutation[j]] = lmDir[j]; + } + + // solve the triangular system for z, if the system is + // singular, then obtain a least squares solution + int nSing = solvedCols; + for (int j = 0; j < solvedCols; ++j) { + if ((lmDiag[j] == 0) && (nSing == solvedCols)) { + nSing = j; + } + if (nSing < solvedCols) { + work[j] = 0; + } + } + if (nSing > 0) { + for (int j = nSing - 1; j >= 0; --j) { + int pj = permutation[j]; + double sum = 0; + for (int i = j + 1; i < nSing; ++i) { + sum += weightedJacobian[i][pj] * work[i]; + } + work[j] = (work[j] - sum) / lmDiag[j]; + } + } + + // permute the components of z back to components of lmDir + for (int j = 0; j < lmDir.length; ++j) { + lmDir[permutation[j]] = work[j]; + } + } + + /** + * Decompose a matrix A as A.P = Q.R using Householder transforms. + *

        As suggested in the P. Lascaux and R. Theodor book + * Analyse numérique matricielle appliquée à + * l'art de l'ingénieur (Masson, 1986), instead of representing + * the Householder transforms with uk unit vectors such that: + *

        +     * Hk = I - 2uk.ukt
        +     * 
        + * we use k non-unit vectors such that: + *
        +     * Hk = I - betakvk.vkt
        +     * 
        + * where vk = ak - alphak ek. + * The betak coefficients are provided upon exit as recomputing + * them from the vk vectors would be costly.

        + *

        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.

        + * + * @param jacobian Weighted Jacobian matrix at the current point. + * @exception ConvergenceException if the decomposition cannot be performed + */ + private void qrDecomposition(RealMatrix jacobian) throws ConvergenceException { + // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J), + // hence the multiplication by -1. + weightedJacobian = jacobian.scalarMultiply(-1).getData(); + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + // initializations + for (int k = 0; k < nC; ++k) { + permutation[k] = k; + double norm2 = 0; + for (int i = 0; i < nR; ++i) { + double akk = weightedJacobian[i][k]; + norm2 += akk * akk; + } + jacNorm[k] = FastMath.sqrt(norm2); + } + + // transform the matrix column after column + for (int k = 0; k < nC; ++k) { + + // select the column with the greatest norm on active components + int nextColumn = -1; + double ak2 = Double.NEGATIVE_INFINITY; + for (int i = k; i < nC; ++i) { + double norm2 = 0; + for (int j = k; j < nR; ++j) { + double aki = weightedJacobian[j][permutation[i]]; + norm2 += aki * aki; + } + if (Double.isInfinite(norm2) || Double.isNaN(norm2)) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN, + nR, nC); + } + if (norm2 > ak2) { + nextColumn = i; + ak2 = norm2; + } + } + if (ak2 <= qrRankingThreshold) { + rank = k; + return; + } + int pk = permutation[nextColumn]; + permutation[nextColumn] = permutation[k]; + permutation[k] = pk; + + // choose alpha such that Hk.u = alpha ek + double akk = weightedJacobian[k][pk]; + double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2); + double betak = 1.0 / (ak2 - akk * alpha); + beta[pk] = betak; + + // transform the current column + diagR[pk] = alpha; + weightedJacobian[k][pk] -= alpha; + + // transform the remaining columns + for (int dk = nC - 1 - k; dk > 0; --dk) { + double gamma = 0; + for (int j = k; j < nR; ++j) { + gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]]; + } + gamma *= betak; + for (int j = k; j < nR; ++j) { + weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk]; + } + } + } + rank = solvedCols; + } + + /** + * Compute the product Qt.y for some Q.R. decomposition. + * + * @param y vector to multiply (will be overwritten with the result) + */ + private void qTy(double[] y) { + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + for (int k = 0; k < nC; ++k) { + int pk = permutation[k]; + double gamma = 0; + for (int i = k; i < nR; ++i) { + gamma += weightedJacobian[i][pk] * y[i]; + } + gamma *= beta[pk]; + for (int i = k; i < nR; ++i) { + y[i] -= gamma * weightedJacobian[i][pk]; + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java new file mode 100644 index 000000000..f4a923305 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.general; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.solvers.BrentSolver; +import com.fr.third.org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.optimization.SimpleValueChecker; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Non-linear conjugate gradient optimizer. + *

        + * This class supports both the Fletcher-Reeves and the Polak-Ribière + * update formulas for the conjugate search directions. It also supports + * optional preconditioning. + *

        + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + * + */ +@Deprecated +public class NonLinearConjugateGradientOptimizer + extends AbstractScalarDifferentiableOptimizer { + /** Update formula for the beta parameter. */ + private final ConjugateGradientFormula updateFormula; + /** Preconditioner (may be null). */ + private final Preconditioner preconditioner; + /** solver to use in the line search (may be null). */ + private final UnivariateSolver solver; + /** Initial step used to bracket the optimum in line search. */ + private double initialStep; + /** Current point. */ + private double[] point; + + /** + * Constructor with default {@link SimpleValueChecker checker}, + * {@link BrentSolver line search solver} and + * {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link + * ConjugateGradientFormula#POLAK_RIBIERE}. + * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()} + */ + @Deprecated + public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula) { + this(updateFormula, + new SimpleValueChecker()); + } + + /** + * Constructor with default {@link BrentSolver line search solver} and + * {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link + * ConjugateGradientFormula#POLAK_RIBIERE}. + * @param checker Convergence checker. + */ + public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, + ConvergenceChecker checker) { + this(updateFormula, + checker, + new BrentSolver(), + new IdentityPreconditioner()); + } + + + /** + * Constructor with default {@link IdentityPreconditioner preconditioner}. + * + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link + * ConjugateGradientFormula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param lineSearchSolver Solver to use during line search. + */ + public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, + ConvergenceChecker checker, + final UnivariateSolver lineSearchSolver) { + this(updateFormula, + checker, + lineSearchSolver, + new IdentityPreconditioner()); + } + + /** + * @param updateFormula formula to use for updating the β parameter, + * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link + * ConjugateGradientFormula#POLAK_RIBIERE}. + * @param checker Convergence checker. + * @param lineSearchSolver Solver to use during line search. + * @param preconditioner Preconditioner. + */ + public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula, + ConvergenceChecker checker, + final UnivariateSolver lineSearchSolver, + final Preconditioner preconditioner) { + super(checker); + + this.updateFormula = updateFormula; + solver = lineSearchSolver; + this.preconditioner = preconditioner; + initialStep = 1.0; + } + + /** + * Set the initial step used to bracket the optimum in line search. + *

        + * The initial step is a factor with respect to the search direction, + * which itself is roughly related to the gradient of the function + *

        + * @param initialStep initial step used to bracket the optimum in line search, + * if a non-positive value is used, the initial step is reset to its + * default value of 1.0 + */ + public void setInitialStep(final double initialStep) { + if (initialStep <= 0) { + this.initialStep = 1.0; + } else { + this.initialStep = initialStep; + } + } + + /** {@inheritDoc} */ + @Override + protected PointValuePair doOptimize() { + final ConvergenceChecker checker = getConvergenceChecker(); + point = getStartPoint(); + final GoalType goal = getGoalType(); + final int n = point.length; + double[] r = computeObjectiveGradient(point); + if (goal == GoalType.MINIMIZE) { + for (int i = 0; i < n; ++i) { + r[i] = -r[i]; + } + } + + // Initial search direction. + double[] steepestDescent = preconditioner.precondition(point, r); + double[] searchDirection = steepestDescent.clone(); + + double delta = 0; + for (int i = 0; i < n; ++i) { + delta += r[i] * searchDirection[i]; + } + + PointValuePair current = null; + int iter = 0; + int maxEval = getMaxEvaluations(); + while (true) { + ++iter; + + final double objective = computeObjectiveValue(point); + PointValuePair previous = current; + current = new PointValuePair(point, objective); + if (previous != null && checker.converged(iter, previous, current)) { + // We have found an optimum. + return current; + } + + // Find the optimal step in the search direction. + final UnivariateFunction lsf = new LineSearchFunction(searchDirection); + final double uB = findUpperBound(lsf, 0, initialStep); + // XXX Last parameters is set to a value close to zero in order to + // work around the divergence problem in the "testCircleFitting" + // unit test (see MATH-439). + final double step = solver.solve(maxEval, lsf, 0, uB, 1e-15); + maxEval -= solver.getEvaluations(); // Subtract used up evaluations. + + // Validate new point. + for (int i = 0; i < point.length; ++i) { + point[i] += step * searchDirection[i]; + } + + r = computeObjectiveGradient(point); + if (goal == GoalType.MINIMIZE) { + for (int i = 0; i < n; ++i) { + r[i] = -r[i]; + } + } + + // Compute beta. + final double deltaOld = delta; + final double[] newSteepestDescent = preconditioner.precondition(point, r); + delta = 0; + for (int i = 0; i < n; ++i) { + delta += r[i] * newSteepestDescent[i]; + } + + final double beta; + if (updateFormula == ConjugateGradientFormula.FLETCHER_REEVES) { + beta = delta / deltaOld; + } else { + double deltaMid = 0; + for (int i = 0; i < r.length; ++i) { + deltaMid += r[i] * steepestDescent[i]; + } + beta = (delta - deltaMid) / deltaOld; + } + steepestDescent = newSteepestDescent; + + // Compute conjugate search direction. + if (iter % n == 0 || + beta < 0) { + // Break conjugation: reset search direction. + searchDirection = steepestDescent.clone(); + } else { + // Compute new conjugate search direction. + for (int i = 0; i < n; ++i) { + searchDirection[i] = steepestDescent[i] + beta * searchDirection[i]; + } + } + } + } + + /** + * Find the upper bound b ensuring bracketing of a root between a and b. + * + * @param f function whose root must be bracketed. + * @param a lower bound of the interval. + * @param h initial step to try. + * @return b such that f(a) and f(b) have opposite signs. + * @throws MathIllegalStateException if no bracket can be found. + */ + private double findUpperBound(final UnivariateFunction f, + final double a, final double h) { + final double yA = f.value(a); + double yB = yA; + for (double step = h; step < Double.MAX_VALUE; step *= FastMath.max(2, yA / yB)) { + final double b = a + step; + yB = f.value(b); + if (yA * yB <= 0) { + return b; + } + } + throw new MathIllegalStateException(LocalizedFormats.UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH); + } + + /** Default identity preconditioner. */ + public static class IdentityPreconditioner implements Preconditioner { + + /** {@inheritDoc} */ + public double[] precondition(double[] variables, double[] r) { + return r.clone(); + } + } + + /** Internal class for line search. + *

        + * 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. + *

        + */ + private class LineSearchFunction implements UnivariateFunction { + /** Search direction. */ + private final double[] searchDirection; + + /** Simple constructor. + * @param searchDirection search direction + */ + LineSearchFunction(final double[] searchDirection) { + this.searchDirection = searchDirection; + } + + /** {@inheritDoc} */ + public double value(double x) { + // current point in the search direction + final double[] shiftedPoint = point.clone(); + for (int i = 0; i < shiftedPoint.length; ++i) { + shiftedPoint[i] += x * searchDirection[i]; + } + + // gradient of the objective function + final double[] gradient = computeObjectiveGradient(shiftedPoint); + + // dot product with the search direction + double dotProduct = 0; + for (int i = 0; i < gradient.length; ++i) { + dotProduct += gradient[i] * searchDirection[i]; + } + + return dotProduct; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/Preconditioner.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/Preconditioner.java new file mode 100644 index 000000000..c8bc97d62 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/Preconditioner.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.general; + +/** + * This interface represents a preconditioner for differentiable scalar + * objective function optimizers. + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public interface Preconditioner { + /** + * Precondition a search direction. + *

        + * 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. + *

        + * @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-1r where H is the objective function hessian + */ + double[] precondition(double[] point, double[] r); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/package-info.java new file mode 100644 index 000000000..920e64e65 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/general/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * This package provides optimization algorithms that require derivatives. + * + */ +package com.fr.third.org.apache.commons.math3.optimization.general; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java new file mode 100644 index 000000000..86b780ee0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.util.Collection; +import java.util.Collections; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; + +/** + * Base class for implementing linear optimizers. + *

        + * This base class handles the boilerplate methods associated to thresholds + * settings and iterations counters. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public abstract class AbstractLinearOptimizer implements LinearOptimizer { + + /** Default maximal number of iterations allowed. */ + public static final int DEFAULT_MAX_ITERATIONS = 100; + + /** + * Linear objective function. + * @since 2.1 + */ + private LinearObjectiveFunction function; + + /** + * Linear constraints. + * @since 2.1 + */ + private Collection linearConstraints; + + /** + * Type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @since 2.1 + */ + private GoalType goal; + + /** + * Whether to restrict the variables to non-negative values. + * @since 2.1 + */ + private boolean nonNegative; + + /** Maximal number of iterations allowed. */ + private int maxIterations; + + /** Number of iterations already performed. */ + private int iterations; + + /** + * Simple constructor with default settings. + *

        The maximal number of evaluation is set to its default value.

        + */ + protected AbstractLinearOptimizer() { + setMaxIterations(DEFAULT_MAX_ITERATIONS); + } + + /** + * @return {@code true} if the variables are restricted to non-negative values. + */ + protected boolean restrictToNonNegative() { + return nonNegative; + } + + /** + * @return the optimization type. + */ + protected GoalType getGoalType() { + return goal; + } + + /** + * @return the optimization type. + */ + protected LinearObjectiveFunction getFunction() { + return function; + } + + /** + * @return the optimization type. + */ + protected Collection getConstraints() { + return Collections.unmodifiableCollection(linearConstraints); + } + + /** {@inheritDoc} */ + public void setMaxIterations(int maxIterations) { + this.maxIterations = maxIterations; + } + + /** {@inheritDoc} */ + public int getMaxIterations() { + return maxIterations; + } + + /** {@inheritDoc} */ + public int getIterations() { + return iterations; + } + + /** + * Increment the iterations counter by 1. + * @exception MaxCountExceededException if the maximal number of iterations is exceeded + */ + protected void incrementIterationsCounter() + throws MaxCountExceededException { + if (++iterations > maxIterations) { + throw new MaxCountExceededException(maxIterations); + } + } + + /** {@inheritDoc} */ + public PointValuePair optimize(final LinearObjectiveFunction f, + final Collection constraints, + final GoalType goalType, final boolean restrictToNonNegative) + throws MathIllegalStateException { + + // store linear problem characteristics + this.function = f; + this.linearConstraints = constraints; + this.goal = goalType; + this.nonNegative = restrictToNonNegative; + + iterations = 0; + + // solve the problem + return doOptimize(); + + } + + /** + * Perform the bulk of optimization algorithm. + * @return the point/value pair giving the optimal value for objective function + * @exception MathIllegalStateException if no solution fulfilling the constraints + * can be found in the allowed number of iterations + */ + protected abstract PointValuePair doOptimize() throws MathIllegalStateException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearConstraint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearConstraint.java new file mode 100644 index 000000000..9f02fc5ff --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearConstraint.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; + + +/** + * A linear constraint for a linear optimization problem. + *

        + * A linear constraint has one of the forms: + *

          + *
        • c1x1 + ... cnxn = v
        • + *
        • c1x1 + ... cnxn <= v
        • + *
        • c1x1 + ... cnxn >= v
        • + *
        • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
        • + *
        + * The ci, li or ri are the coefficients of the constraints, the xi + * are the coordinates of the current point and v is the value of the constraint. + *

        + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class LinearConstraint implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -764632794033034092L; + + /** Coefficients of the constraint (left hand side). */ + private final transient RealVector coefficients; + + /** Relationship between left and right hand sides (=, <=, >=). */ + private final Relationship relationship; + + /** Value of the constraint (right hand side). */ + private final double value; + + /** + * Build a constraint involving a single linear equation. + *

        + * A linear constraint with a single linear equation has one of the forms: + *

          + *
        • c1x1 + ... cnxn = v
        • + *
        • c1x1 + ... cnxn <= v
        • + *
        • c1x1 + ... cnxn >= v
        • + *
        + *

        + * @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. + *

        + * A linear constraint with a single linear equation has one of the forms: + *

          + *
        • c1x1 + ... cnxn = v
        • + *
        • c1x1 + ... cnxn <= v
        • + *
        • c1x1 + ... cnxn >= v
        • + *
        + *

        + * @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. + *

        + * A linear constraint with two linear equation has one of the forms: + *

          + *
        • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
        • + *
        + *

        + * @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. + *

        + * A linear constraint with two linear equation has one of the forms: + *

          + *
        • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
        • + *
        + *

        + * @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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java new file mode 100644 index 000000000..07ff0f972 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; + +/** + * An objective function for a linear optimization problem. + *

        + * A linear objective function has one the form: + *

        + * c1x1 + ... cnxn + d
        + * 
        + * The ci and d are the coefficients of the equation, + * the xi are the coordinates of the current point. + *

        + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class LinearObjectiveFunction implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -4531815507568396090L; + + /** Coefficients of the constraint (ci). */ + private final transient RealVector coefficients; + + /** Constant term of the linear equation. */ + private final double constantTerm; + + /** + * @param coefficients The coefficients for the linear equation being optimized + * @param constantTerm The constant term of the linear equation + */ + public LinearObjectiveFunction(double[] coefficients, double constantTerm) { + this(new ArrayRealVector(coefficients), constantTerm); + } + + /** + * @param coefficients The coefficients for the linear equation being optimized + * @param constantTerm The constant term of the linear equation + */ + public LinearObjectiveFunction(RealVector coefficients, double constantTerm) { + this.coefficients = coefficients; + this.constantTerm = constantTerm; + } + + /** + * Get the coefficients of the linear equation being optimized. + * @return coefficients of the linear equation being optimized + */ + public RealVector getCoefficients() { + return coefficients; + } + + /** + * Get the constant of the linear equation being optimized. + * @return constant of the linear equation being optimized + */ + public double getConstantTerm() { + return constantTerm; + } + + /** + * Compute the value of the linear equation at the current point + * @param point point at which linear equation must be evaluated + * @return value of the linear equation at the current point + */ + public double getValue(final double[] point) { + return coefficients.dotProduct(new ArrayRealVector(point, false)) + constantTerm; + } + + /** + * Compute the value of the linear equation at the current point + * @param point point at which linear equation must be evaluated + * @return value of the linear equation at the current point + */ + public double getValue(final RealVector point) { + return coefficients.dotProduct(point) + constantTerm; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof LinearObjectiveFunction) { + LinearObjectiveFunction rhs = (LinearObjectiveFunction) other; + return (constantTerm == rhs.constantTerm) && coefficients.equals(rhs.coefficients); + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Double.valueOf(constantTerm).hashCode() ^ coefficients.hashCode(); + } + + /** + * Serialize the instance. + * @param oos stream where object should be written + * @throws IOException if object cannot be written to stream + */ + private void writeObject(ObjectOutputStream oos) + throws IOException { + oos.defaultWriteObject(); + MatrixUtils.serializeRealVector(coefficients, oos); + } + + /** + * Deserialize the instance. + * @param ois stream from which the object should be read + * @throws ClassNotFoundException if a class in the stream cannot be found + * @throws IOException if object cannot be read from the stream + */ + private void readObject(ObjectInputStream ois) + throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + MatrixUtils.deserializeRealVector(this, "coefficients", ois); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearOptimizer.java new file mode 100644 index 000000000..97dffe5df --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/LinearOptimizer.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; + +/** + * This interface represents an optimization algorithm for linear problems. + *

        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 + *

        + * c1x1 + ... cnxn = v
        + * 
        + * and there may be linear constraints too, of one of the forms: + *
          + *
        • c1x1 + ... cnxn = v
        • + *
        • c1x1 + ... cnxn <= v
        • + *
        • c1x1 + ... cnxn >= v
        • + *
        • l1x1 + ... lnxn + lcst = + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst <= + * r1x1 + ... rnxn + rcst
        • + *
        • l1x1 + ... lnxn + lcst >= + * r1x1 + ... rnxn + rcst
        • + *
        + * where the ci, li or ri are the coefficients of + * the constraints, the xi are the coordinates of the current point and + * v is the value of the constraint. + *

        + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public interface LinearOptimizer { + + /** + * Set the maximal number of iterations of the algorithm. + * @param maxIterations maximal number of function calls + */ + void setMaxIterations(int maxIterations); + + /** + * Get the maximal number of iterations of the algorithm. + * @return maximal number of iterations + */ + int getMaxIterations(); + + /** + * Get the number of iterations realized by the algorithm. + *

        + * 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. + *

        + * @return number of iterations + */ + int getIterations(); + + /** + * Optimizes an objective function. + * @param f linear objective function + * @param constraints linear constraints + * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE} + * @param restrictToNonNegative whether to restrict the variables to non-negative values + * @return point/value pair giving the optimal value for objective function + * @exception MathIllegalStateException if no solution fulfilling the constraints + * can be found in the allowed number of iterations + */ + PointValuePair optimize(LinearObjectiveFunction f, Collection constraints, + GoalType goalType, boolean restrictToNonNegative) throws MathIllegalStateException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java new file mode 100644 index 000000000..a9890134c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * This class represents exceptions thrown by optimizers when no solution fulfills the constraints. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class NoFeasibleSolutionException extends MathIllegalStateException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -3044253632189082760L; + + /** + * Simple constructor using a default message. + */ + public NoFeasibleSolutionException() { + super(LocalizedFormats.NO_FEASIBLE_SOLUTION); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/Relationship.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/Relationship.java new file mode 100644 index 000000000..fb67e025a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/Relationship.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +/** + * Types of relationships between two cells in a Solver {@link LinearConstraint}. + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public enum Relationship { + + /** Equality relationship. */ + EQ("="), + + /** Lesser than or equal relationship. */ + LEQ("<="), + + /** Greater than or equal relationship. */ + GEQ(">="); + + /** Display string for the relationship. */ + private final String stringValue; + + /** Simple constructor. + * @param stringValue display string for the relationship + */ + Relationship(String stringValue) { + this.stringValue = stringValue; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return stringValue; + } + + /** + * Get the relationship obtained when multiplying all coefficients by -1. + * @return relationship obtained when multiplying all coefficients by -1 + */ + public Relationship oppositeRelationship() { + switch (this) { + case LEQ : + return GEQ; + case GEQ : + return LEQ; + default : + return EQ; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexSolver.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexSolver.java new file mode 100644 index 000000000..86b4e0e5e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexSolver.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.util.Precision; + + +/** + * Solves a linear problem using the Two-Phase Simplex Method. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class SimplexSolver extends AbstractLinearOptimizer { + + /** Default amount of error to accept for algorithm convergence. */ + private static final double DEFAULT_EPSILON = 1.0e-6; + + /** Default amount of error to accept in floating point comparisons (as ulps). */ + private static final int DEFAULT_ULPS = 10; + + /** Amount of error to accept for algorithm convergence. */ + private final double epsilon; + + /** Amount of error to accept in floating point comparisons (as ulps). */ + private final int maxUlps; + + /** + * Build a simplex solver with default settings. + */ + public SimplexSolver() { + this(DEFAULT_EPSILON, DEFAULT_ULPS); + } + + /** + * Build a simplex solver with a specified accepted amount of error + * @param epsilon the amount of error to accept for algorithm convergence + * @param maxUlps amount of error to accept in floating point comparisons + */ + public SimplexSolver(final double epsilon, final int maxUlps) { + this.epsilon = epsilon; + this.maxUlps = maxUlps; + } + + /** + * Returns the column with the most negative coefficient in the objective function row. + * @param tableau simple tableau for the problem + * @return column with the most negative coefficient + */ + private Integer getPivotColumn(SimplexTableau tableau) { + double minValue = 0; + Integer minPos = null; + for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getWidth() - 1; i++) { + final double entry = tableau.getEntry(0, i); + // check if the entry is strictly smaller than the current minimum + // do not use a ulp/epsilon check + if (entry < minValue) { + minValue = entry; + minPos = i; + } + } + return minPos; + } + + /** + * Returns the row with the minimum ratio as given by the minimum ratio test (MRT). + * @param tableau simple tableau for the problem + * @param col the column to test the ratio of. See {@link #getPivotColumn(SimplexTableau)} + * @return row with the minimum ratio + */ + private Integer getPivotRow(SimplexTableau tableau, final int col) { + // create a list of all the rows that tie for the lowest score in the minimum ratio test + List minRatioPositions = new ArrayList(); + double minRatio = Double.MAX_VALUE; + for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) { + final double rhs = tableau.getEntry(i, tableau.getWidth() - 1); + final double entry = tableau.getEntry(i, col); + + if (Precision.compareTo(entry, 0d, maxUlps) > 0) { + final double ratio = rhs / entry; + // check if the entry is strictly equal to the current min ratio + // do not use a ulp/epsilon check + final int cmp = Double.compare(ratio, minRatio); + if (cmp == 0) { + minRatioPositions.add(i); + } else if (cmp < 0) { + minRatio = ratio; + minRatioPositions = new ArrayList(); + minRatioPositions.add(i); + } + } + } + + if (minRatioPositions.size() == 0) { + return null; + } else if (minRatioPositions.size() > 1) { + // there's a degeneracy as indicated by a tie in the minimum ratio test + + // 1. check if there's an artificial variable that can be forced out of the basis + if (tableau.getNumArtificialVariables() > 0) { + for (Integer row : minRatioPositions) { + for (int i = 0; i < tableau.getNumArtificialVariables(); i++) { + int column = i + tableau.getArtificialVariableOffset(); + final double entry = tableau.getEntry(row, column); + if (Precision.equals(entry, 1d, maxUlps) && row.equals(tableau.getBasicRow(column))) { + return row; + } + } + } + } + + // 2. apply Bland's rule to prevent cycling: + // take the row for which the corresponding basic variable has the smallest index + // + // see http://www.stanford.edu/class/msande310/blandrule.pdf + // see http://en.wikipedia.org/wiki/Bland%27s_rule (not equivalent to the above paper) + // + // Additional heuristic: if we did not get a solution after half of maxIterations + // revert to the simple case of just returning the top-most row + // This heuristic is based on empirical data gathered while investigating MATH-828. + if (getIterations() < getMaxIterations() / 2) { + Integer minRow = null; + int minIndex = tableau.getWidth(); + final int varStart = tableau.getNumObjectiveFunctions(); + final int varEnd = tableau.getWidth() - 1; + for (Integer row : minRatioPositions) { + for (int i = varStart; i < varEnd && !row.equals(minRow); i++) { + final Integer basicRow = tableau.getBasicRow(i); + if (basicRow != null && basicRow.equals(row) && i < minIndex) { + minIndex = i; + minRow = row; + } + } + } + return minRow; + } + } + return minRatioPositions.get(0); + } + + /** + * Runs one iteration of the Simplex method on the given model. + * @param tableau simple tableau for the problem + * @throws MaxCountExceededException if the maximal iteration count has been exceeded + * @throws UnboundedSolutionException if the model is found not to have a bounded solution + */ + protected void doIteration(final SimplexTableau tableau) + throws MaxCountExceededException, UnboundedSolutionException { + + incrementIterationsCounter(); + + Integer pivotCol = getPivotColumn(tableau); + Integer pivotRow = getPivotRow(tableau, pivotCol); + if (pivotRow == null) { + throw new UnboundedSolutionException(); + } + + // set the pivot element to 1 + double pivotVal = tableau.getEntry(pivotRow, pivotCol); + tableau.divideRow(pivotRow, pivotVal); + + // set the rest of the pivot column to 0 + for (int i = 0; i < tableau.getHeight(); i++) { + if (i != pivotRow) { + final double multiplier = tableau.getEntry(i, pivotCol); + tableau.subtractRow(i, pivotRow, multiplier); + } + } + } + + /** + * Solves Phase 1 of the Simplex method. + * @param tableau simple tableau for the problem + * @throws MaxCountExceededException if the maximal iteration count has been exceeded + * @throws UnboundedSolutionException if the model is found not to have a bounded solution + * @throws NoFeasibleSolutionException if there is no feasible solution + */ + protected void solvePhase1(final SimplexTableau tableau) + throws MaxCountExceededException, UnboundedSolutionException, NoFeasibleSolutionException { + + // make sure we're in Phase 1 + if (tableau.getNumArtificialVariables() == 0) { + return; + } + + while (!tableau.isOptimal()) { + doIteration(tableau); + } + + // if W is not zero then we have no feasible solution + if (!Precision.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0d, epsilon)) { + throw new NoFeasibleSolutionException(); + } + } + + /** {@inheritDoc} */ + @Override + public PointValuePair doOptimize() + throws MaxCountExceededException, UnboundedSolutionException, NoFeasibleSolutionException { + final SimplexTableau tableau = + new SimplexTableau(getFunction(), + getConstraints(), + getGoalType(), + restrictToNonNegative(), + epsilon, + maxUlps); + + solvePhase1(tableau); + tableau.dropPhase1Objective(); + + while (!tableau.isOptimal()) { + doIteration(tableau); + } + return tableau.getSolution(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexTableau.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexTableau.java new file mode 100644 index 000000000..3ec0c2773 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/SimplexTableau.java @@ -0,0 +1,637 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.PointValuePair; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * A tableau for use in the Simplex method. + * + *

        + * Example: + *

        + *   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
        + * 
        + * W: Phase 1 objective function
        + * Z: Phase 2 objective function
        + * x1 & x2: Decision variables
        + * x-: Extra decision variable to allow for negative values
        + * s1 & s2: Slack/Surplus variables
        + * a1: Artificial variable
        + * RHS: Right hand side
        + *

        + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +class SimplexTableau implements Serializable { + + /** Column label for negative vars. */ + private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-"; + + /** Default amount of error to accept in floating point comparisons (as ulps). */ + private static final int DEFAULT_ULPS = 10; + + /** The cut-off threshold to zero-out entries. */ + private static final double CUTOFF_THRESHOLD = 1e-12; + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1369660067587938365L; + + /** Linear objective function. */ + private final LinearObjectiveFunction f; + + /** Linear constraints. */ + private final List constraints; + + /** Whether to restrict the variables to non-negative values. */ + private final boolean restrictToNonNegative; + + /** The variables each column represents */ + private final List columnLabels = new ArrayList(); + + /** Simple tableau. */ + private transient RealMatrix tableau; + + /** Number of decision variables. */ + private final int numDecisionVariables; + + /** Number of slack variables. */ + private final int numSlackVariables; + + /** Number of artificial variables. */ + private int numArtificialVariables; + + /** Amount of error to accept when checking for optimality. */ + private final double epsilon; + + /** Amount of error to accept in floating point comparisons. */ + private final int maxUlps; + + /** + * Build a tableau for a linear problem. + * @param f linear objective function + * @param constraints linear constraints + * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE} + * @param restrictToNonNegative whether to restrict the variables to non-negative values + * @param epsilon amount of error to accept when checking for optimality + */ + SimplexTableau(final LinearObjectiveFunction f, + final Collection constraints, + final GoalType goalType, final boolean restrictToNonNegative, + final double epsilon) { + this(f, constraints, goalType, restrictToNonNegative, epsilon, DEFAULT_ULPS); + } + + /** + * Build a tableau for a linear problem. + * @param f linear objective function + * @param constraints linear constraints + * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE} + * @param restrictToNonNegative whether to restrict the variables to non-negative values + * @param epsilon amount of error to accept when checking for optimality + * @param maxUlps amount of error to accept in floating point comparisons + */ + SimplexTableau(final LinearObjectiveFunction f, + final Collection constraints, + final GoalType goalType, final boolean restrictToNonNegative, + final double epsilon, + final int maxUlps) { + this.f = f; + this.constraints = normalizeConstraints(constraints); + this.restrictToNonNegative = restrictToNonNegative; + this.epsilon = epsilon; + this.maxUlps = maxUlps; + this.numDecisionVariables = f.getCoefficients().getDimension() + + (restrictToNonNegative ? 0 : 1); + this.numSlackVariables = getConstraintTypeCounts(Relationship.LEQ) + + getConstraintTypeCounts(Relationship.GEQ); + this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) + + getConstraintTypeCounts(Relationship.GEQ); + this.tableau = createTableau(goalType == GoalType.MAXIMIZE); + initializeColumnLabels(); + } + + /** + * Initialize the labels for the columns. + */ + protected void initializeColumnLabels() { + if (getNumObjectiveFunctions() == 2) { + columnLabels.add("W"); + } + columnLabels.add("Z"); + for (int i = 0; i < getOriginalNumDecisionVariables(); i++) { + columnLabels.add("x" + i); + } + if (!restrictToNonNegative) { + columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL); + } + for (int i = 0; i < getNumSlackVariables(); i++) { + columnLabels.add("s" + i); + } + for (int i = 0; i < getNumArtificialVariables(); i++) { + columnLabels.add("a" + i); + } + columnLabels.add("RHS"); + } + + /** + * Create the tableau by itself. + * @param maximize if true, goal is to maximize the objective function + * @return created tableau + */ + protected RealMatrix createTableau(final boolean maximize) { + + // create a matrix of the correct size + int width = numDecisionVariables + numSlackVariables + + numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS + int height = constraints.size() + getNumObjectiveFunctions(); + Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width); + + // initialize the objective function rows + if (getNumObjectiveFunctions() == 2) { + matrix.setEntry(0, 0, -1); + } + int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1; + matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1); + RealVector objectiveCoefficients = + maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients(); + copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]); + matrix.setEntry(zIndex, width - 1, + maximize ? f.getConstantTerm() : -1 * f.getConstantTerm()); + + if (!restrictToNonNegative) { + matrix.setEntry(zIndex, getSlackVariableOffset() - 1, + getInvertedCoefficientSum(objectiveCoefficients)); + } + + // initialize the constraint rows + int slackVar = 0; + int artificialVar = 0; + for (int i = 0; i < constraints.size(); i++) { + LinearConstraint constraint = constraints.get(i); + int row = getNumObjectiveFunctions() + i; + + // decision variable coefficients + copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]); + + // x- + if (!restrictToNonNegative) { + matrix.setEntry(row, getSlackVariableOffset() - 1, + getInvertedCoefficientSum(constraint.getCoefficients())); + } + + // RHS + matrix.setEntry(row, width - 1, constraint.getValue()); + + // slack variables + if (constraint.getRelationship() == Relationship.LEQ) { + matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1); // slack + } else if (constraint.getRelationship() == Relationship.GEQ) { + matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess + } + + // artificial variables + if ((constraint.getRelationship() == Relationship.EQ) || + (constraint.getRelationship() == Relationship.GEQ)) { + matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1); + matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1); + matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row))); + } + } + + return matrix; + } + + /** + * Get new versions of the constraints which have positive right hand sides. + * @param originalConstraints original (not normalized) constraints + * @return new versions of the constraints + */ + public List normalizeConstraints(Collection originalConstraints) { + List normalized = new ArrayList(originalConstraints.size()); + for (LinearConstraint constraint : originalConstraints) { + normalized.add(normalize(constraint)); + } + return normalized; + } + + /** + * Get a new equation equivalent to this one with a positive right hand side. + * @param constraint reference constraint + * @return new equation + */ + private LinearConstraint normalize(final LinearConstraint constraint) { + if (constraint.getValue() < 0) { + return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1), + constraint.getRelationship().oppositeRelationship(), + -1 * constraint.getValue()); + } + return new LinearConstraint(constraint.getCoefficients(), + constraint.getRelationship(), constraint.getValue()); + } + + /** + * Get the number of objective functions in this tableau. + * @return 2 for Phase 1. 1 for Phase 2. + */ + protected final int getNumObjectiveFunctions() { + return this.numArtificialVariables > 0 ? 2 : 1; + } + + /** + * Get a count of constraints corresponding to a specified relationship. + * @param relationship relationship to count + * @return number of constraint with the specified relationship + */ + private int getConstraintTypeCounts(final Relationship relationship) { + int count = 0; + for (final LinearConstraint constraint : constraints) { + if (constraint.getRelationship() == relationship) { + ++count; + } + } + return count; + } + + /** + * Get the -1 times the sum of all coefficients in the given array. + * @param coefficients coefficients to sum + * @return the -1 times the sum of all coefficients in the given array. + */ + protected static double getInvertedCoefficientSum(final RealVector coefficients) { + double sum = 0; + for (double coefficient : coefficients.toArray()) { + sum -= coefficient; + } + return sum; + } + + /** + * Checks whether the given column is basic. + * @param col index of the column to check + * @return the row that the variable is basic in. null if the column is not basic + */ + protected Integer getBasicRow(final int col) { + Integer row = null; + for (int i = 0; i < getHeight(); i++) { + final double entry = getEntry(i, col); + if (Precision.equals(entry, 1d, maxUlps) && (row == null)) { + row = i; + } else if (!Precision.equals(entry, 0d, maxUlps)) { + return null; + } + } + return row; + } + + /** + * Removes the phase 1 objective function, positive cost non-artificial variables, + * and the non-basic artificial variables from this tableau. + */ + protected void dropPhase1Objective() { + if (getNumObjectiveFunctions() == 1) { + return; + } + + Set columnsToDrop = new TreeSet(); + columnsToDrop.add(0); + + // positive cost non-artificial variables + for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) { + final double entry = tableau.getEntry(0, i); + if (Precision.compareTo(entry, 0d, epsilon) > 0) { + columnsToDrop.add(i); + } + } + + // non-basic artificial variables + for (int i = 0; i < getNumArtificialVariables(); i++) { + int col = i + getArtificialVariableOffset(); + if (getBasicRow(col) == null) { + columnsToDrop.add(col); + } + } + + double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()]; + for (int i = 1; i < getHeight(); i++) { + int col = 0; + for (int j = 0; j < getWidth(); j++) { + if (!columnsToDrop.contains(j)) { + matrix[i - 1][col++] = tableau.getEntry(i, j); + } + } + } + + // remove the columns in reverse order so the indices are correct + Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]); + for (int i = drop.length - 1; i >= 0; i--) { + columnLabels.remove((int) drop[i]); + } + + this.tableau = new Array2DRowRealMatrix(matrix); + this.numArtificialVariables = 0; + } + + /** + * @param src the source array + * @param dest the destination array + */ + private void copyArray(final double[] src, final double[] dest) { + System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length); + } + + /** + * Returns whether the problem is at an optimal state. + * @return whether the model has been solved + */ + boolean isOptimal() { + for (int i = getNumObjectiveFunctions(); i < getWidth() - 1; i++) { + final double entry = tableau.getEntry(0, i); + if (Precision.compareTo(entry, 0d, epsilon) < 0) { + return false; + } + } + return true; + } + + /** + * Get the current solution. + * @return current solution + */ + protected PointValuePair getSolution() { + int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL); + Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null; + double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset()); + + Set basicRows = new HashSet(); + double[] coefficients = new double[getOriginalNumDecisionVariables()]; + for (int i = 0; i < coefficients.length; i++) { + int colIndex = columnLabels.indexOf("x" + i); + if (colIndex < 0) { + coefficients[i] = 0; + continue; + } + Integer basicRow = getBasicRow(colIndex); + if (basicRow != null && basicRow == 0) { + // if the basic row is found to be the objective function row + // set the coefficient to 0 -> this case handles unconstrained + // variables that are still part of the objective function + coefficients[i] = 0; + } else if (basicRows.contains(basicRow)) { + // if multiple variables can take a given value + // then we choose the first and set the rest equal to 0 + coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative); + } else { + basicRows.add(basicRow); + coefficients[i] = + (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) - + (restrictToNonNegative ? 0 : mostNegative); + } + } + return new PointValuePair(coefficients, f.getValue(coefficients)); + } + + /** + * Subtracts a multiple of one row from another. + *

        + * After application of this operation, the following will hold: + *

        minuendRow = minuendRow - multiple * subtrahendRow
        + * + * @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. + *

        + * After application of this operation, the following will hold: + *

        minuendRow = minuendRow - multiple * subtrahendRow
        + * + * @param minuendRow row index + * @param subtrahendRow row index + * @param multiple multiplication factor + */ + protected void subtractRow(final int minuendRow, final int subtrahendRow, + final double multiple) { + for (int i = 0; i < getWidth(); i++) { + double result = tableau.getEntry(minuendRow, i) - tableau.getEntry(subtrahendRow, i) * multiple; + // cut-off values smaller than the CUTOFF_THRESHOLD, otherwise may lead to numerical instabilities + if (FastMath.abs(result) < CUTOFF_THRESHOLD) { + result = 0.0; + } + tableau.setEntry(minuendRow, i, result); + } + } + + /** + * Get the width of the tableau. + * @return width of the tableau + */ + protected final int getWidth() { + return tableau.getColumnDimension(); + } + + /** + * Get the height of the tableau. + * @return height of the tableau + */ + protected final int getHeight() { + return tableau.getRowDimension(); + } + + /** + * Get an entry of the tableau. + * @param row row index + * @param column column index + * @return entry at (row, column) + */ + protected final double getEntry(final int row, final int column) { + return tableau.getEntry(row, column); + } + + /** + * Set an entry of the tableau. + * @param row row index + * @param column column index + * @param value for the entry + */ + protected final void setEntry(final int row, final int column, + final double value) { + tableau.setEntry(row, column, value); + } + + /** + * Get the offset of the first slack variable. + * @return offset of the first slack variable + */ + protected final int getSlackVariableOffset() { + return getNumObjectiveFunctions() + numDecisionVariables; + } + + /** + * Get the offset of the first artificial variable. + * @return offset of the first artificial variable + */ + protected final int getArtificialVariableOffset() { + return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables; + } + + /** + * Get the offset of the right hand side. + * @return offset of the right hand side + */ + protected final int getRhsOffset() { + return getWidth() - 1; + } + + /** + * Get the number of decision variables. + *

        + * If variables are not restricted to positive values, this will include 1 extra decision variable to represent + * the absolute value of the most negative variable. + * + * @return number of decision variables + * @see #getOriginalNumDecisionVariables() + */ + protected final int getNumDecisionVariables() { + return numDecisionVariables; + } + + /** + * Get the original number of decision variables. + * @return original number of decision variables + * @see #getNumDecisionVariables() + */ + protected final int getOriginalNumDecisionVariables() { + return f.getCoefficients().getDimension(); + } + + /** + * Get the number of slack variables. + * @return number of slack variables + */ + protected final int getNumSlackVariables() { + return numSlackVariables; + } + + /** + * Get the number of artificial variables. + * @return number of artificial variables + */ + protected final int getNumArtificialVariables() { + return numArtificialVariables; + } + + /** + * Get the tableau data. + * @return tableau data + */ + protected final double[][] getData() { + return tableau.getData(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof SimplexTableau) { + SimplexTableau rhs = (SimplexTableau) other; + return (restrictToNonNegative == rhs.restrictToNonNegative) && + (numDecisionVariables == rhs.numDecisionVariables) && + (numSlackVariables == rhs.numSlackVariables) && + (numArtificialVariables == rhs.numArtificialVariables) && + (epsilon == rhs.epsilon) && + (maxUlps == rhs.maxUlps) && + f.equals(rhs.f) && + constraints.equals(rhs.constraints) && + tableau.equals(rhs.tableau); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Boolean.valueOf(restrictToNonNegative).hashCode() ^ + numDecisionVariables ^ + numSlackVariables ^ + numArtificialVariables ^ + Double.valueOf(epsilon).hashCode() ^ + maxUlps ^ + f.hashCode() ^ + constraints.hashCode() ^ + tableau.hashCode(); + } + + /** + * Serialize the instance. + * @param oos stream where object should be written + * @throws IOException if object cannot be written to stream + */ + private void writeObject(ObjectOutputStream oos) + throws IOException { + oos.defaultWriteObject(); + MatrixUtils.serializeRealMatrix(tableau, oos); + } + + /** + * Deserialize the instance. + * @param ois stream from which the object should be read + * @throws ClassNotFoundException if a class in the stream cannot be found + * @throws IOException if object cannot be read from the stream + */ + private void readObject(ObjectInputStream ois) + throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + MatrixUtils.deserializeRealMatrix(this, "tableau", ois); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java new file mode 100644 index 000000000..da320bf6c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.linear; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * This class represents exceptions thrown by optimizers when a solution escapes to infinity. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class UnboundedSolutionException extends MathIllegalStateException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 940539497277290619L; + + /** + * Simple constructor using a default message. + */ + public UnboundedSolutionException() { + super(LocalizedFormats.UNBOUNDED_SOLUTION); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/package-info.java new file mode 100644 index 000000000..787b24536 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/linear/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * This package provides optimization algorithms for linear constrained problems. + * + */ +package com.fr.third.org.apache.commons.math3.optimization.linear; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/package-info.java new file mode 100644 index 000000000..f8494f081 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/package-info.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

        All classes and sub-packages of this package are deprecated.

        + *

        Please use their replacements, to be found under + *
          + *
        • {@link org.apache.commons.math3.optim}
        • + *
        • {@link org.apache.commons.math3.fitting}
        • + *
        + *

        + * + *

        + * 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 analysis + * 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. + *

        + * + *

        + * 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: + *

          + *
        • {@link com.fr.third.org.apache.commons.math3.optimization.univariate.UnivariateOptimizer + * UnivariateOptimizer} for {@link com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction + * univariate real functions}
        • + *
        • {@link com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer + * MultivariateOptimizer} for {@link com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction + * multivariate real functions}
        • + *
        • {@link com.fr.third.org.apache.commons.math3.optimization.MultivariateDifferentiableOptimizer + * MultivariateDifferentiableOptimizer} for {@link + * com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction + * multivariate differentiable real functions}
        • + *
        • {@link com.fr.third.org.apache.commons.math3.optimization.MultivariateDifferentiableVectorOptimizer + * MultivariateDifferentiableVectorOptimizer} for {@link + * com.fr.third.org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction + * multivariate differentiable vectorial functions}
        • + *
        + *

        + * + *

        + * Despite there are only four types of supported optimizers, it is possible to optimize a + * transform a {@link com.fr.third.org.apache.commons.math3.analysis.MultivariateVectorFunction + * non-differentiable multivariate vectorial function} by converting it to a {@link + * com.fr.third.org.apache.commons.math3.analysis.MultivariateFunction non-differentiable multivariate + * real function} thanks to the {@link + * com.fr.third.org.apache.commons.math3.optimization.LeastSquaresConverter LeastSquaresConverter} helper class. + * The transformed function can be optimized using any implementation of the {@link + * com.fr.third.org.apache.commons.math3.optimization.MultivariateOptimizer MultivariateOptimizer} interface. + *

        + * + *

        + * For each of the four types of supported optimizers, there is a special implementation which + * wraps a classical optimizer in order to add it a multi-start feature. This feature call the + * underlying optimizer several times in sequence with different starting points and returns + * the best optimum found or all optima if desired. This is a classical way to prevent being + * trapped into a local extremum when looking for a global one. + *

        + * + */ +package com.fr.third.org.apache.commons.math3.optimization; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java new file mode 100644 index 000000000..782e54ed6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; + +/** + * Provide a default implementation for several functions useful to generic + * optimizers. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public abstract class BaseAbstractUnivariateOptimizer + implements UnivariateOptimizer { + /** Convergence checker. */ + private final ConvergenceChecker checker; + /** Evaluations counter. */ + private final Incrementor evaluations = new Incrementor(); + /** Optimization type */ + private GoalType goal; + /** Lower end of search interval. */ + private double searchMin; + /** Higher end of search interval. */ + private double searchMax; + /** Initial guess . */ + private double searchStart; + /** Function to optimize. */ + private UnivariateFunction function; + + /** + * @param checker Convergence checking procedure. + */ + protected BaseAbstractUnivariateOptimizer(ConvergenceChecker checker) { + this.checker = checker; + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** + * @return the optimization type. + */ + public GoalType getGoalType() { + return goal; + } + /** + * @return the lower end of the search interval. + */ + public double getMin() { + return searchMin; + } + /** + * @return the higher end of the search interval. + */ + public double getMax() { + return searchMax; + } + /** + * @return the initial guess. + */ + public double getStartValue() { + return searchStart; + } + + /** + * Compute the objective function value. + * + * @param point Point at which the objective function must be evaluated. + * @return the objective function value at specified point. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + */ + protected double computeObjectiveValue(double point) { + try { + evaluations.incrementCount(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return function.value(point); + } + + /** {@inheritDoc} */ + public UnivariatePointValuePair optimize(int maxEval, UnivariateFunction f, + GoalType goalType, + double min, double max, + double startValue) { + // Checks. + if (f == null) { + throw new NullArgumentException(); + } + if (goalType == null) { + throw new NullArgumentException(); + } + + // Reset. + searchMin = min; + searchMax = max; + searchStart = startValue; + goal = goalType; + function = f; + evaluations.setMaximalCount(maxEval); + evaluations.resetCount(); + + // Perform computation. + return doOptimize(); + } + + /** {@inheritDoc} */ + public UnivariatePointValuePair optimize(int maxEval, + UnivariateFunction f, + GoalType goalType, + double min, double max){ + return optimize(maxEval, f, goalType, min, max, min + 0.5 * (max - min)); + } + + /** + * {@inheritDoc} + */ + public ConvergenceChecker getConvergenceChecker() { + return checker; + } + + /** + * Method for implementing actual optimization algorithms in derived + * classes. + * + * @return the optimum and its corresponding function value. + * @throws TooManyEvaluationsException if the maximal number of evaluations + * is exceeded. + */ + protected abstract UnivariatePointValuePair doOptimize(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.java new file mode 100644 index 000000000..55ad67525 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.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 com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.BaseOptimizer; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; + +/** + * This interface is mainly intended to enforce the internal coherence of + * Commons-Math. Users of the API are advised to base their code on + * the following interfaces: + *
          + *
        • {@link UnivariateOptimizer}
        • + *
        + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface BaseUnivariateOptimizer + extends BaseOptimizer { + /** + * Find an optimum in the given interval. + * + * An optimizer may require that the interval brackets a single optimum. + * + * @param f Function to optimize. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param maxEval Maximum number of function evaluations. + * @return a (point, value) pair where the function is optimum. + * @throws TooManyEvaluationsException + * if the maximum evaluation count is exceeded. + * @throws ConvergenceException + * if the optimizer detects a convergence problem. + * @throws IllegalArgumentException if {@code min > max} or the endpoints + * do not satisfy the requirements specified by the optimizer. + */ + UnivariatePointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double min, double max); + + /** + * Find an optimum in the given interval, start at startValue. + * An optimizer may require that the interval brackets a single optimum. + * + * @param f Function to optimize. + * @param goalType Type of optimization goal: either + * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}. + * @param min Lower bound for the interval. + * @param max Upper bound for the interval. + * @param startValue Start value to use. + * @param maxEval Maximum number of function evaluations. + * @return a (point, value) pair where the function is optimum. + * @throws TooManyEvaluationsException + * if the maximum evaluation count is exceeded. + * @throws ConvergenceException if the + * optimizer detects a convergence problem. + * @throws IllegalArgumentException if {@code min > max} or the endpoints + * do not satisfy the requirements specified by the optimizer. + * @throws NullArgumentException if any + * argument is {@code null}. + */ + UnivariatePointValuePair optimize(int maxEval, FUNC f, GoalType goalType, + double min, double max, + double startValue); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BracketFinder.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BracketFinder.java new file mode 100644 index 000000000..563a7ffb4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BracketFinder.java @@ -0,0 +1,289 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Incrementor; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.TooManyEvaluationsException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; + +/** + * Provide an interval that brackets a local optimum of a function. + * This code is based on a Python implementation (from SciPy, + * module {@code optimize.py} v0.5). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.2 + */ +@Deprecated +public class BracketFinder { + /** Tolerance to avoid division by zero. */ + private static final double EPS_MIN = 1e-21; + /** + * Golden section. + */ + private static final double GOLD = 1.618034; + /** + * Factor for expanding the interval. + */ + private final double growLimit; + /** + * Counter for function evaluations. + */ + private final Incrementor evaluations = new Incrementor(); + /** + * Lower bound of the bracket. + */ + private double lo; + /** + * Higher bound of the bracket. + */ + private double hi; + /** + * Point inside the bracket. + */ + private double mid; + /** + * Function value at {@link #lo}. + */ + private double fLo; + /** + * Function value at {@link #hi}. + */ + private double fHi; + /** + * Function value at {@link #mid}. + */ + private double fMid; + + /** + * Constructor with default values {@code 100, 50} (see the + * {@link #BracketFinder(double,int) other constructor}). + */ + public BracketFinder() { + this(100, 50); + } + + /** + * Create a bracketing interval finder. + * + * @param growLimit Expanding factor. + * @param maxEvaluations Maximum number of evaluations allowed for finding + * a bracketing interval. + */ + public BracketFinder(double growLimit, + int maxEvaluations) { + if (growLimit <= 0) { + throw new NotStrictlyPositiveException(growLimit); + } + if (maxEvaluations <= 0) { + throw new NotStrictlyPositiveException(maxEvaluations); + } + + this.growLimit = growLimit; + evaluations.setMaximalCount(maxEvaluations); + } + + /** + * Search new points that bracket a local optimum of the function. + * + * @param func Function whose optimum should be bracketed. + * @param goal {@link GoalType Goal type}. + * @param xA Initial point. + * @param xB Initial point. + * @throws TooManyEvaluationsException if the maximum number of evaluations + * is exceeded. + */ + public void search(UnivariateFunction func, GoalType goal, double xA, double xB) { + evaluations.resetCount(); + final boolean isMinim = goal == GoalType.MINIMIZE; + + double fA = eval(func, xA); + double fB = eval(func, xB); + if (isMinim ? + fA < fB : + fA > fB) { + + double tmp = xA; + xA = xB; + xB = tmp; + + tmp = fA; + fA = fB; + fB = tmp; + } + + double xC = xB + GOLD * (xB - xA); + double fC = eval(func, xC); + + while (isMinim ? fC < fB : fC > fB) { + double tmp1 = (xB - xA) * (fB - fC); + double tmp2 = (xB - xC) * (fB - fA); + + double val = tmp2 - tmp1; + double denom = FastMath.abs(val) < EPS_MIN ? 2 * EPS_MIN : 2 * val; + + double w = xB - ((xB - xC) * tmp2 - (xB - xA) * tmp1) / denom; + double wLim = xB + growLimit * (xC - xB); + + double fW; + if ((w - xC) * (xB - w) > 0) { + fW = eval(func, w); + if (isMinim ? + fW < fC : + fW > fC) { + xA = xB; + xB = w; + fA = fB; + fB = fW; + break; + } else if (isMinim ? + fW > fB : + fW < fB) { + xC = w; + fC = fW; + break; + } + w = xC + GOLD * (xC - xB); + fW = eval(func, w); + } else if ((w - wLim) * (wLim - xC) >= 0) { + w = wLim; + fW = eval(func, w); + } else if ((w - wLim) * (xC - w) > 0) { + fW = eval(func, w); + if (isMinim ? + fW < fC : + fW > fC) { + xB = xC; + xC = w; + w = xC + GOLD * (xC - xB); + fB = fC; + fC =fW; + fW = eval(func, w); + } + } else { + w = xC + GOLD * (xC - xB); + fW = eval(func, w); + } + + xA = xB; + fA = fB; + xB = xC; + fB = fC; + xC = w; + fC = fW; + } + + lo = xA; + fLo = fA; + mid = xB; + fMid = fB; + hi = xC; + fHi = fC; + + if (lo > hi) { + double tmp = lo; + lo = hi; + hi = tmp; + + tmp = fLo; + fLo = fHi; + fHi = tmp; + } + } + + /** + * @return the number of evalutations. + */ + public int getMaxEvaluations() { + return evaluations.getMaximalCount(); + } + + /** + * @return the number of evalutations. + */ + public int getEvaluations() { + return evaluations.getCount(); + } + + /** + * @return the lower bound of the bracket. + * @see #getFLo() + */ + public double getLo() { + return lo; + } + + /** + * Get function value at {@link #getLo()}. + * @return function value at {@link #getLo()} + */ + public double getFLo() { + return fLo; + } + + /** + * @return the higher bound of the bracket. + * @see #getFHi() + */ + public double getHi() { + return hi; + } + + /** + * Get function value at {@link #getHi()}. + * @return function value at {@link #getHi()} + */ + public double getFHi() { + return fHi; + } + + /** + * @return a point in the middle of the bracket. + * @see #getFMid() + */ + public double getMid() { + return mid; + } + + /** + * Get function value at {@link #getMid()}. + * @return function value at {@link #getMid()} + */ + public double getFMid() { + return fMid; + } + + /** + * @param f Function. + * @param x Argument. + * @return {@code f(x)} + * @throws TooManyEvaluationsException if the maximal number of evaluations is + * exceeded. + */ + private double eval(UnivariateFunction f, double x) { + try { + evaluations.incrementCount(); + } catch (MaxCountExceededException e) { + throw new TooManyEvaluationsException(e.getMax()); + } + return f.value(x); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java new file mode 100644 index 000000000..b7d6e4d60 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java @@ -0,0 +1,316 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; + +/** + * For a function defined on some interval {@code (lo, hi)}, this class + * finds an approximation {@code x} to the point at which the function + * attains its minimum. + * It implements Richard Brent's algorithm (from his book "Algorithms for + * Minimization without Derivatives", p. 79) for finding minima of real + * univariate functions. + *
        + * This code is an adaptation, partly based on the Python code from SciPy + * (module "optimize.py" v0.5); the original algorithm is also modified + *
          + *
        • to use an initial guess provided by the user,
        • + *
        • to ensure that the best point encountered is the one returned.
        • + *
        + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 2.0 + */ +@Deprecated +public class BrentOptimizer extends BaseAbstractUnivariateOptimizer { + /** + * Golden section. + */ + private static final double GOLDEN_SECTION = 0.5 * (3 - FastMath.sqrt(5)); + /** + * Minimum relative tolerance. + */ + private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d); + /** + * Relative threshold. + */ + private final double relativeThreshold; + /** + * Absolute threshold. + */ + private final double absoluteThreshold; + + /** + * The arguments are used implement the original stopping criterion + * of Brent's algorithm. + * {@code abs} and {@code rel} define a tolerance + * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than + * 2 macheps and preferably not much less than sqrt(macheps), + * where macheps is the relative machine precision. {@code abs} must + * be positive. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @param checker Additional, user-defined, convergence checking + * procedure. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public BrentOptimizer(double rel, + double abs, + ConvergenceChecker checker) { + super(checker); + + if (rel < MIN_RELATIVE_TOLERANCE) { + throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true); + } + if (abs <= 0) { + throw new NotStrictlyPositiveException(abs); + } + + relativeThreshold = rel; + absoluteThreshold = abs; + } + + /** + * The arguments are used for implementing the original stopping criterion + * of Brent's algorithm. + * {@code abs} and {@code rel} define a tolerance + * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than + * 2 macheps and preferably not much less than sqrt(macheps), + * where macheps is the relative machine precision. {@code abs} must + * be positive. + * + * @param rel Relative threshold. + * @param abs Absolute threshold. + * @throws NotStrictlyPositiveException if {@code abs <= 0}. + * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}. + */ + public BrentOptimizer(double rel, + double abs) { + this(rel, abs, null); + } + + /** {@inheritDoc} */ + @Override + protected UnivariatePointValuePair doOptimize() { + final boolean isMinim = getGoalType() == GoalType.MINIMIZE; + final double lo = getMin(); + final double mid = getStartValue(); + final double hi = getMax(); + + // Optional additional convergence criteria. + final ConvergenceChecker checker + = getConvergenceChecker(); + + double a; + double b; + if (lo < hi) { + a = lo; + b = hi; + } else { + a = hi; + b = lo; + } + + double x = mid; + double v = x; + double w = x; + double d = 0; + double e = 0; + double fx = computeObjectiveValue(x); + if (!isMinim) { + fx = -fx; + } + double fv = fx; + double fw = fx; + + UnivariatePointValuePair previous = null; + UnivariatePointValuePair current + = new UnivariatePointValuePair(x, isMinim ? fx : -fx); + // Best point encountered so far (which is the initial guess). + UnivariatePointValuePair best = current; + + int iter = 0; + while (true) { + final double m = 0.5 * (a + b); + final double tol1 = relativeThreshold * FastMath.abs(x) + absoluteThreshold; + final double tol2 = 2 * tol1; + + // Default stopping criterion. + final boolean stop = FastMath.abs(x - m) <= tol2 - 0.5 * (b - a); + if (!stop) { + double p = 0; + double q = 0; + double r = 0; + double u = 0; + + if (FastMath.abs(e) > tol1) { // Fit parabola. + r = (x - w) * (fx - fv); + q = (x - v) * (fx - fw); + p = (x - v) * q - (x - w) * r; + q = 2 * (q - r); + + if (q > 0) { + p = -p; + } else { + q = -q; + } + + r = e; + e = d; + + if (p > q * (a - x) && + p < q * (b - x) && + FastMath.abs(p) < FastMath.abs(0.5 * q * r)) { + // Parabolic interpolation step. + d = p / q; + u = x + d; + + // f must not be evaluated too close to a or b. + if (u - a < tol2 || b - u < tol2) { + if (x <= m) { + d = tol1; + } else { + d = -tol1; + } + } + } else { + // Golden section step. + if (x < m) { + e = b - x; + } else { + e = a - x; + } + d = GOLDEN_SECTION * e; + } + } else { + // Golden section step. + if (x < m) { + e = b - x; + } else { + e = a - x; + } + d = GOLDEN_SECTION * e; + } + + // Update by at least "tol1". + if (FastMath.abs(d) < tol1) { + if (d >= 0) { + u = x + tol1; + } else { + u = x - tol1; + } + } else { + u = x + d; + } + + double fu = computeObjectiveValue(u); + if (!isMinim) { + fu = -fu; + } + + // User-defined convergence checker. + previous = current; + current = new UnivariatePointValuePair(u, isMinim ? fu : -fu); + best = best(best, + best(previous, + current, + isMinim), + isMinim); + + if (checker != null && checker.converged(iter, previous, current)) { + return best; + } + + // Update a, b, v, w and x. + if (fu <= fx) { + if (u < x) { + b = x; + } else { + a = x; + } + v = w; + fv = fw; + w = x; + fw = fx; + x = u; + fx = fu; + } else { + if (u < x) { + a = u; + } else { + b = u; + } + if (fu <= fw || + Precision.equals(w, x)) { + v = w; + fv = fw; + w = u; + fw = fu; + } else if (fu <= fv || + Precision.equals(v, x) || + Precision.equals(v, w)) { + v = u; + fv = fu; + } + } + } else { // Default termination (Brent's criterion). + return best(best, + best(previous, + current, + isMinim), + isMinim); + } + ++iter; + } + } + + /** + * Selects the best of two points. + * + * @param a Point and value. + * @param b Point and value. + * @param isMinim {@code true} if the selected point must be the one with + * the lowest value. + * @return the best point, or {@code null} if {@code a} and {@code b} are + * both {@code null}. When {@code a} and {@code b} have the same function + * value, {@code a} is returned. + */ + private UnivariatePointValuePair best(UnivariatePointValuePair a, + UnivariatePointValuePair b, + boolean isMinim) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + + if (isMinim) { + return a.getValue() <= b.getValue() ? a : b; + } else { + return a.getValue() >= b.getValue() ? a : b; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.java new file mode 100644 index 000000000..57b057641 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.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 com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.optimization.AbstractConvergenceChecker; + +/** + * Simple implementation of the + * {@link ConvergenceChecker} interface + * that uses only objective function values. + * + * Convergence is considered to have been reached if either the relative + * difference between the objective function values is smaller than a + * threshold or if either the absolute difference between the objective + * function values is smaller than another threshold. + *
        + * The {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair) + * converged} method will also return {@code true} if the number of iterations + * has been set (see {@link #SimpleUnivariateValueChecker(double,double,int) + * this constructor}). + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.1 + */ +@Deprecated +public class SimpleUnivariateValueChecker + extends AbstractConvergenceChecker { + /** + * If {@link #maxIterationCount} is set to this value, the number of + * iterations will never cause + * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)} + * to return {@code true}. + */ + private static final int ITERATION_CHECK_DISABLED = -1; + /** + * Number of iterations after which the + * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)} + * method will return true (unless the check is disabled). + */ + private final int maxIterationCount; + + /** + * Build an instance with default thresholds. + * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()} + */ + @Deprecated + public SimpleUnivariateValueChecker() { + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** Build an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + */ + public SimpleUnivariateValueChecker(final double relativeThreshold, + final double absoluteThreshold) { + super(relativeThreshold, absoluteThreshold); + maxIterationCount = ITERATION_CHECK_DISABLED; + } + + /** + * Builds an instance with specified thresholds. + * + * In order to perform only relative checks, the absolute tolerance + * must be set to a negative value. In order to perform only absolute + * checks, the relative tolerance must be set to a negative value. + * + * @param relativeThreshold relative tolerance threshold + * @param absoluteThreshold absolute tolerance threshold + * @param maxIter Maximum iteration count. + * @throws NotStrictlyPositiveException if {@code maxIter <= 0}. + * + * @since 3.1 + */ + public SimpleUnivariateValueChecker(final double relativeThreshold, + final double absoluteThreshold, + final int maxIter) { + super(relativeThreshold, absoluteThreshold); + + if (maxIter <= 0) { + throw new NotStrictlyPositiveException(maxIter); + } + maxIterationCount = maxIter; + } + + /** + * Check if the optimization algorithm has converged considering the + * last two points. + * This method may be called several time from the same algorithm + * iteration with different points. This can be detected by checking the + * iteration number at each call if needed. Each time this method is + * called, the previous and current point correspond to points with the + * same role at each iteration, so they can be compared. As an example, + * simplex-based algorithms call this method for all points of the simplex, + * not only for the best or worst ones. + * + * @param iteration Index of current iteration + * @param previous Best point in the previous iteration. + * @param current Best point in the current iteration. + * @return {@code true} if the algorithm has converged. + */ + @Override + public boolean converged(final int iteration, + final UnivariatePointValuePair previous, + final UnivariatePointValuePair current) { + if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) { + return true; + } + + final double p = previous.getValue(); + final double c = current.getValue(); + final double difference = FastMath.abs(p - c); + final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c)); + return difference <= size * getRelativeThreshold() || + difference <= getAbsoluteThreshold(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java new file mode 100644 index 000000000..c0f2fd7cb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import java.util.Arrays; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.optimization.GoalType; +import com.fr.third.org.apache.commons.math3.optimization.ConvergenceChecker; + +/** + * Special implementation of the {@link UnivariateOptimizer} interface + * adding multi-start features to an existing optimizer. + * + * This class wraps a classical optimizer to use it several times in + * turn with different starting points in order to avoid being trapped + * into a local extremum when looking for a global one. + * + * @param Type of the objective function to be optimized. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class UnivariateMultiStartOptimizer + implements BaseUnivariateOptimizer { + /** Underlying classical optimizer. */ + private final BaseUnivariateOptimizer optimizer; + /** Maximal number of evaluations allowed. */ + private int maxEvaluations; + /** Number of evaluations already performed for all starts. */ + private int totalEvaluations; + /** Number of starts to go. */ + private int starts; + /** Random generator for multi-start. */ + private RandomGenerator generator; + /** Found optima. */ + private UnivariatePointValuePair[] optima; + + /** + * Create a multi-start optimizer from a single-start optimizer. + * + * @param optimizer Single-start optimizer to wrap. + * @param starts Number of starts to perform. If {@code starts == 1}, + * the {@code optimize} methods will return the same solution as + * {@code optimizer} would. + * @param generator Random generator to use for restarts. + * @throws NullArgumentException if {@code optimizer} or {@code generator} + * is {@code null}. + * @throws NotStrictlyPositiveException if {@code starts < 1}. + */ + public UnivariateMultiStartOptimizer(final BaseUnivariateOptimizer optimizer, + final int starts, + final RandomGenerator generator) { + if (optimizer == null || + generator == null) { + throw new NullArgumentException(); + } + if (starts < 1) { + throw new NotStrictlyPositiveException(starts); + } + + this.optimizer = optimizer; + this.starts = starts; + this.generator = generator; + } + + /** + * {@inheritDoc} + */ + public ConvergenceChecker getConvergenceChecker() { + return optimizer.getConvergenceChecker(); + } + + /** {@inheritDoc} */ + public int getMaxEvaluations() { + return maxEvaluations; + } + + /** {@inheritDoc} */ + public int getEvaluations() { + return totalEvaluations; + } + + /** + * Get all the optima found during the last call to {@link + * #optimize(int,UnivariateFunction,GoalType,double,double) optimize}. + * The optimizer stores all the optima found during a set of + * restarts. The {@link #optimize(int,UnivariateFunction,GoalType,double,double) optimize} + * method returns the best point only. This method returns all the points + * found at the end of each starts, including the best one already + * returned by the {@link #optimize(int,UnivariateFunction,GoalType,double,double) optimize} + * method. + *
        + * The returned array as one element for each start as specified + * in the constructor. It is ordered with the results from the + * runs that did converge first, sorted from best to worst + * objective value (i.e in ascending order if minimizing and in + * descending order if maximizing), followed by {@code null} elements + * corresponding to the runs that did not converge. This means all + * elements will be {@code null} if the {@link + * #optimize(int,UnivariateFunction,GoalType,double,double) optimize} + * method did throw an exception. + * This also means that if the first element is not {@code null}, it is + * the best point found across all starts. + * + * @return an array containing the optima. + * @throws MathIllegalStateException if {@link + * #optimize(int,UnivariateFunction,GoalType,double,double) optimize} + * has not been called. + */ + public UnivariatePointValuePair[] getOptima() { + if (optima == null) { + throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET); + } + return optima.clone(); + } + + /** {@inheritDoc} */ + public UnivariatePointValuePair optimize(int maxEval, final FUNC f, + final GoalType goal, + final double min, final double max) { + return optimize(maxEval, f, goal, min, max, min + 0.5 * (max - min)); + } + + /** {@inheritDoc} */ + public UnivariatePointValuePair optimize(int maxEval, final FUNC f, + final GoalType goal, + final double min, final double max, + final double startValue) { + RuntimeException lastException = null; + optima = new UnivariatePointValuePair[starts]; + totalEvaluations = 0; + + // Multi-start loop. + for (int i = 0; i < starts; ++i) { + // CHECKSTYLE: stop IllegalCatch + try { + final double s = (i == 0) ? startValue : min + generator.nextDouble() * (max - min); + optima[i] = optimizer.optimize(maxEval - totalEvaluations, f, goal, min, max, s); + } catch (RuntimeException mue) { + lastException = mue; + optima[i] = null; + } + // CHECKSTYLE: resume IllegalCatch + + totalEvaluations += optimizer.getEvaluations(); + } + + sortPairs(goal); + + if (optima[0] == null) { + throw lastException; // cannot be null if starts >=1 + } + + // Return the point with the best objective function value. + return optima[0]; + } + + /** + * Sort the optima from best to worst, followed by {@code null} elements. + * + * @param goal Goal type. + */ + private void sortPairs(final GoalType goal) { + Arrays.sort(optima, new Comparator() { + /** {@inheritDoc} */ + public int compare(final UnivariatePointValuePair o1, + final UnivariatePointValuePair o2) { + if (o1 == null) { + return (o2 == null) ? 0 : 1; + } else if (o2 == null) { + return -1; + } + final double v1 = o1.getValue(); + final double v2 = o2.getValue(); + return (goal == GoalType.MINIMIZE) ? + Double.compare(v1, v2) : Double.compare(v2, v1); + } + }); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java new file mode 100644 index 000000000..d9d8d36ab --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; + +/** + * Interface for univariate optimization algorithms. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public interface UnivariateOptimizer + extends BaseUnivariateOptimizer {} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java new file mode 100644 index 000000000..8a0533935 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.optimization.univariate; + +import java.io.Serializable; + +/** + * This class holds a point and the value of an objective function at this + * point. + * This is a simple immutable container. + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ +@Deprecated +public class UnivariatePointValuePair implements Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = 1003888396256744753L; + /** Point. */ + private final double point; + /** Value of the objective function at the point. */ + private final double value; + + /** + * Build a point/objective function value pair. + * + * @param point Point. + * @param value Value of an objective function at the point + */ + public UnivariatePointValuePair(final double point, + final double value) { + this.point = point; + this.value = value; + } + + /** + * Get the point. + * + * @return the point. + */ + public double getPoint() { + return point; + } + + /** + * Get the value of the objective function. + * + * @return the stored value of the objective function. + */ + public double getValue() { + return value; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/package-info.java new file mode 100644 index 000000000..370a8a46b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/optimization/univariate/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Univariate real functions minimum finding algorithms. + * + */ +package com.fr.third.org.apache.commons.math3.optimization.univariate; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/package-info.java new file mode 100644 index 000000000..a9b9e4809 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Common classes used throughout the commons-math library. + */ +package com.fr.third.org.apache.commons.math3; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/PollardRho.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/PollardRho.java new file mode 100644 index 000000000..1514b1f02 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/PollardRho.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 com.fr.third.org.apache.commons.math3.primes; + +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of the Pollard's rho factorization algorithm. + * @since 3.2 + */ +class PollardRho { + + /** + * Hide utility class. + */ + private PollardRho() { + } + + /** + * Factorization using Pollard's rho algorithm. + * @param n number to factors, must be > 0 + * @return the list of prime factors of n. + */ + public static List primeFactors(int n) { + final List factors = new ArrayList(); + + n = SmallPrimes.smallTrialDivision(n, factors); + if (1 == n) { + return factors; + } + + if (SmallPrimes.millerRabinPrimeTest(n)) { + factors.add(n); + return factors; + } + + int divisor = rhoBrent(n); + factors.add(divisor); + factors.add(n / divisor); + return factors; + } + + /** + * Implementation of the Pollard's rho factorization algorithm. + *

        + * This implementation follows the paper "An improved Monte Carlo factorization algorithm" + * by Richard P. Brent. This avoids the triple computation of f(x) typically found in Pollard's + * rho implementations. It also batches several gcd computation into 1. + *

        + * The backtracking is not implemented as we deal only with semi-primes. + * + * @param n number to factor, must be semi-prime. + * @return a prime factor of n. + */ + static int rhoBrent(final int n) { + final int x0 = 2; + final int m = 25; + int cst = SmallPrimes.PRIMES_LAST; + int y = x0; + int r = 1; + do { + int x = y; + for (int i = 0; i < r; i++) { + final long y2 = ((long) y) * y; + y = (int) ((y2 + cst) % n); + } + int k = 0; + do { + final int bound = FastMath.min(m, r - k); + int q = 1; + for (int i = -3; i < bound; i++) { //start at -3 to ensure we enter this loop at least 3 times + final long y2 = ((long) y) * y; + y = (int) ((y2 + cst) % n); + final long divisor = FastMath.abs(x - y); + if (0 == divisor) { + cst += SmallPrimes.PRIMES_LAST; + k = -m; + y = x0; + r = 1; + break; + } + final long prod = divisor * q; + q = (int) (prod % n); + if (0 == q) { + return gcdPositive(FastMath.abs((int) divisor), n); + } + } + final int out = gcdPositive(FastMath.abs(q), n); + if (1 != out) { + return out; + } + k += m; + } while (k < r); + r = 2 * r; + } while (true); + } + + /** + * Gcd between two positive numbers. + *

        + * Gets the greatest common divisor of two numbers, using the "binary gcd" method, + * which avoids division and modulo operations. See Knuth 4.5.2 algorithm B. + * This algorithm is due to Josef Stein (1961). + *

        + * Special cases: + *
          + *
        • The result of {@code gcd(x, x)}, {@code gcd(0, x)} and {@code gcd(x, 0)} is the value of {@code x}.
        • + *
        • The invocation {@code gcd(0, 0)} is the only one which returns {@code 0}.
        • + *
        + * + * @param a first number, must be ≥ 0 + * @param b second number, must be ≥ 0 + * @return gcd(a,b) + */ + static int gcdPositive(int a, int b){ + // both a and b must be positive, it is not checked here + // gdc(a,0) = a + if (a == 0) { + return b; + } else if (b == 0) { + return a; + } + + // make a and b odd, keep in mind the common power of twos + final int aTwos = Integer.numberOfTrailingZeros(a); + a >>= aTwos; + final int bTwos = Integer.numberOfTrailingZeros(b); + b >>= bTwos; + final int shift = FastMath.min(aTwos, bTwos); + + // a and b >0 + // if a > b then gdc(a,b) = gcd(a-b,b) + // if a < b then gcd(a,b) = gcd(b-a,a) + // so next a is the absolute difference and next b is the minimum of current values + while (a != b) { + final int delta = a - b; + b = FastMath.min(a, b); + a = FastMath.abs(delta); + // for speed optimization: + // remove any power of two in a as b is guaranteed to be odd throughout all iterations + a >>= Integer.numberOfTrailingZeros(a); + } + + // gcd(a,a) = a, just "add" the common power of twos + return a << shift; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/Primes.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/Primes.java new file mode 100644 index 000000000..6f5be824a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/Primes.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 com.fr.third.org.apache.commons.math3.primes; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +import java.util.List; + + +/** + * Methods related to prime numbers in the range of int: + *
          + *
        • primality test
        • + *
        • prime number generation
        • + *
        • factorization
        • + *
        + * + * @since 3.2 + */ +public class Primes { + + /** + * Hide utility class. + */ + private Primes() { + } + + /** + * Primality test: tells if the argument is a (provable) prime or not. + *

        + * It uses the Miller-Rabin probabilistic test in such a way that a result is guaranteed: + * it uses the firsts prime numbers as successive base (see Handbook of applied cryptography + * by Menezes, table 4.1). + * + * @param n number to test. + * @return true if n is prime. (All numbers < 2 return false). + */ + public static boolean isPrime(int n) { + if (n < 2) { + return false; + } + + for (int p : SmallPrimes.PRIMES) { + if (0 == (n % p)) { + return n == p; + } + } + return SmallPrimes.millerRabinPrimeTest(n); + } + + /** + * Return the smallest prime greater than or equal to n. + * + * @param n a positive number. + * @return the smallest prime greater than or equal to n. + * @throws MathIllegalArgumentException if n < 0. + */ + public static int nextPrime(int n) { + if (n < 0) { + throw new MathIllegalArgumentException(LocalizedFormats.NUMBER_TOO_SMALL, n, 0); + } + if (n == 2) { + return 2; + } + n |= 1;//make sure n is odd + if (n == 1) { + return 2; + } + + if (isPrime(n)) { + return n; + } + + // prepare entry in the +2, +4 loop: + // n should not be a multiple of 3 + final int rem = n % 3; + if (0 == rem) { // if n % 3 == 0 + n += 2; // n % 3 == 2 + } else if (1 == rem) { // if n % 3 == 1 + // if (isPrime(n)) return n; + n += 4; // n % 3 == 2 + } + while (true) { // this loop skips all multiple of 3 + if (isPrime(n)) { + return n; + } + n += 2; // n % 3 == 1 + if (isPrime(n)) { + return n; + } + n += 4; // n % 3 == 2 + } + } + + /** + * Prime factors decomposition + * + * @param n number to factorize: must be ≥ 2 + * @return list of prime factors of n + * @throws MathIllegalArgumentException if n < 2. + */ + public static List primeFactors(int n) { + + if (n < 2) { + throw new MathIllegalArgumentException(LocalizedFormats.NUMBER_TOO_SMALL, n, 2); + } + // slower than trial div unless we do an awful lot of computation + // (then it finally gets JIT-compiled efficiently + // List out = PollardRho.primeFactors(n); + return SmallPrimes.trialDivision(n); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/SmallPrimes.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/SmallPrimes.java new file mode 100644 index 000000000..d6fb084fb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/SmallPrimes.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 com.fr.third.org.apache.commons.math3.primes; + + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Utility methods to work on primes within the int range. + * @since 3.2 + */ +class SmallPrimes { + + /** + * The first 512 prime numbers. + *

        + * It contains all primes smaller or equal to the cubic square of Integer.MAX_VALUE. + * As a result, int numbers which are not reduced by those primes are guaranteed + * to be either prime or semi prime. + */ + public static final int[] PRIMES = {2, + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, + 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, + 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, + 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, + 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, + 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, + 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, + 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, + 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, + 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, + 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, + 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, + 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, + 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, + 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, + 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, + 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, + 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, + 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, + 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, + 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, + 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, + 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, + 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671}; + + /** The last number in PRIMES. */ + public static final int PRIMES_LAST = PRIMES[PRIMES.length - 1]; + + /** + * Hide utility class. + */ + private SmallPrimes() { + } + + /** + * Extract small factors. + * @param n the number to factor, must be > 0. + * @param factors the list where to add the factors. + * @return the part of n which remains to be factored, it is either a prime or a semi-prime + */ + public static int smallTrialDivision(int n, final List factors) { + for (int p : PRIMES) { + while (0 == n % p) { + n /= p; + factors.add(p); + } + } + return n; + } + + /** + * Extract factors in the range PRIME_LAST+2 to maxFactors. + * @param n the number to factorize, must be >= PRIME_LAST+2 and must not contain any factor below PRIME_LAST+2 + * @param maxFactor the upper bound of trial division: if it is reached, the method gives up and returns n. + * @param factors the list where to add the factors. + * @return n or 1 if factorization is completed. + */ + public static int boundedTrialDivision(int n, int maxFactor, List factors) { + int f = PRIMES_LAST + 2; + // no check is done about n >= f + while (f <= maxFactor) { + if (0 == n % f) { + n /= f; + factors.add(f); + break; + } + f += 4; + if (0 == n % f) { + n /= f; + factors.add(f); + break; + } + f += 2; + } + if (n != 1) { + factors.add(n); + } + return n; + } + + /** + * Factorization by trial division. + * @param n the number to factor + * @return the list of prime factors of n + */ + public static List trialDivision(int n){ + final List factors = new ArrayList(32); + n = smallTrialDivision(n, factors); + if (1 == n) { + return factors; + } + // here we are sure that n is either a prime or a semi prime + final int bound = (int) FastMath.sqrt(n); + boundedTrialDivision(n, bound, factors); + return factors; + } + + /** + * Miller-Rabin probabilistic primality test for int type, used in such a way that a result is always guaranteed. + *

        + * It uses the prime numbers as successive base therefore it is guaranteed to be always correct. + * (see Handbook of applied cryptography by Menezes, table 4.1) + * + * @param n number to test: an odd integer ≥ 3 + * @return true if n is prime. false if n is definitely composite. + */ + public static boolean millerRabinPrimeTest(final int n) { + final int nMinus1 = n - 1; + final int s = Integer.numberOfTrailingZeros(nMinus1); + final int r = nMinus1 >> s; + //r must be odd, it is not checked here + int t = 1; + if (n >= 2047) { + t = 2; + } + if (n >= 1373653) { + t = 3; + } + if (n >= 25326001) { + t = 4; + } // works up to 3.2 billion, int range stops at 2.7 so we are safe :-) + BigInteger br = BigInteger.valueOf(r); + BigInteger bn = BigInteger.valueOf(n); + + for (int i = 0; i < t; i++) { + BigInteger a = BigInteger.valueOf(SmallPrimes.PRIMES[i]); + BigInteger bPow = a.modPow(br, bn); + int y = bPow.intValue(); + if ((1 != y) && (y != nMinus1)) { + int j = 1; + while ((j <= s - 1) && (nMinus1 != y)) { + long square = ((long) y) * y; + y = (int) (square % n); + if (1 == y) { + return false; + } // definitely composite + j++; + } + if (nMinus1 != y) { + return false; + } // definitely composite + } + } + return true; // definitely prime + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/package-info.java new file mode 100644 index 000000000..bbc0c2acd --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/primes/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Methods related to prime numbers like primality test, factor decomposition. + */ +package com.fr.third.org.apache.commons.math3.primes; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractRandomGenerator.java new file mode 100644 index 000000000..614e4dfc5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Abstract class implementing the {@link RandomGenerator} interface. + * Default implementations for all methods other than {@link #nextDouble()} and + * {@link #setSeed(long)} are provided. + *

        + * All data generation methods are based on {@code code nextDouble()}. + * Concrete implementations must override + * this method and should provide better / more + * performant implementations of the other methods if the underlying PRNG + * supplies them.

        + * + * @since 1.1 + */ +public abstract class AbstractRandomGenerator implements RandomGenerator { + + /** + * Cached random normal value. The default implementation for + * {@link #nextGaussian} generates pairs of values and this field caches the + * second value so that the full algorithm is not executed for every + * activation. The value {@code Double.NaN} signals that there is + * no cached value. Use {@link #clear} to clear the cached value. + */ + private double cachedNormalDeviate = Double.NaN; + + /** + * Construct a RandomGenerator. + */ + public AbstractRandomGenerator() { + super(); + + } + + /** + * Clears the cache used by the default implementation of + * {@link #nextGaussian}. Implementations that do not override the + * default implementation of {@code nextGaussian} should call this + * method in the implementation of {@link #setSeed(long)} + */ + public void clear() { + cachedNormalDeviate = Double.NaN; + } + + /** {@inheritDoc} */ + public void setSeed(int seed) { + setSeed((long) seed); + } + + /** {@inheritDoc} */ + public void setSeed(int[] seed) { + // the following number is the largest prime that fits in 32 bits (it is 2^32 - 5) + final long prime = 4294967291l; + + long combined = 0l; + for (int s : seed) { + combined = combined * prime + s; + } + setSeed(combined); + } + + /** + * Sets the seed of the underlying random number generator using a + * {@code long} seed. Sequences of values generated starting with the + * same seeds should be identical. + *

        + * Implementations that do not override the default implementation of + * {@code nextGaussian} should include a call to {@link #clear} in the + * implementation of this method.

        + * + * @param seed the seed value + */ + public abstract void setSeed(long seed); + + /** + * Generates random bytes and places them into a user-supplied + * byte array. The number of random bytes produced is equal to + * the length of the byte array. + *

        + * The default implementation fills the array with bytes extracted from + * random integers generated using {@link #nextInt}.

        + * + * @param bytes the non-null byte array in which to put the + * random bytes + */ + public void nextBytes(byte[] bytes) { + int bytesOut = 0; + while (bytesOut < bytes.length) { + int randInt = nextInt(); + for (int i = 0; i < 3; i++) { + if ( i > 0) { + randInt >>= 8; + } + bytes[bytesOut++] = (byte) randInt; + if (bytesOut == bytes.length) { + return; + } + } + } + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code int} + * value from this random number generator's sequence. + * All 232 possible {@code int} values + * should be produced with (approximately) equal probability. + *

        + * The default implementation provided here returns + *

        +     * (int) (nextDouble() * Integer.MAX_VALUE)
        +     * 

        + * + * @return the next pseudorandom, uniformly distributed {@code int} + * value from this random number generator's sequence + */ + public int nextInt() { + return (int) ((2d * nextDouble() - 1d) * Integer.MAX_VALUE); + } + + /** + * Returns a pseudorandom, uniformly distributed {@code int} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + *

        + * The default implementation returns + *

        +     * (int) (nextDouble() * n
        +     * 

        + * + * @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 + * 264 possible {@code long} values + * should be produced with (approximately) equal probability. + *

        + * The default implementation returns + *

        +     * (long) (nextDouble() * Long.MAX_VALUE)
        +     * 

        + * + * @return the next pseudorandom, uniformly distributed {@code long} + *value from this random number generator's sequence + */ + public long nextLong() { + return (long) ((2d * nextDouble() - 1d) * Long.MAX_VALUE); + } + + /** + * Returns the next pseudorandom, uniformly distributed + * {@code boolean} value from this random number generator's + * sequence. + *

        + * The default implementation returns + *

        +     * nextDouble() <= 0.5
        +     * 

        + * + * @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. + *

        + * The default implementation returns + *

        +     * (float) nextDouble() 
        +     * 

        + * + * @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. + *

        + * This method provides the underlying source of random data used by the + * other methods.

        + * + * @return the next pseudorandom, uniformly distributed + * {@code double} value between {@code 0.0} and + * {@code 1.0} from this random number generator's sequence + */ + public abstract double nextDouble(); + + /** + * Returns the next pseudorandom, Gaussian ("normally") distributed + * {@code double} value with mean {@code 0.0} and standard + * deviation {@code 1.0} from this random number generator's sequence. + *

        + * The default implementation uses the Polar Method + * due to G.E.P. Box, M.E. Muller and G. Marsaglia, as described in + * D. Knuth, The Art of Computer Programming, 3.4.1C.

        + *

        + * The algorithm generates a pair of independent random values. One of + * these is cached for reuse, so the full algorithm is not executed on each + * activation. Implementations that do not override this method should + * make sure to call {@link #clear} to clear the cached value in the + * implementation of {@link #setSeed(long)}.

        + * + * @return the next pseudorandom, Gaussian ("normally") distributed + * {@code double} value with mean {@code 0.0} and + * standard deviation {@code 1.0} from this random number + * generator's sequence + */ + public double nextGaussian() { + if (!Double.isNaN(cachedNormalDeviate)) { + double dev = cachedNormalDeviate; + cachedNormalDeviate = Double.NaN; + return dev; + } + double v1 = 0; + double v2 = 0; + double s = 1; + while (s >=1 ) { + v1 = 2 * nextDouble() - 1; + v2 = 2 * nextDouble() - 1; + s = v1 * v1 + v2 * v2; + } + if (s != 0) { + s = FastMath.sqrt(-2 * FastMath.log(s) / s); + } + cachedNormalDeviate = v2 * s; + return v1 * s; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractWell.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractWell.java new file mode 100644 index 000000000..37945fc9c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/AbstractWell.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.random; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** This abstract class implements the WELL class of pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time plus the + * system identity hash code of this instance as the seed.

        + * @param k number of bits in the pool (not necessarily a multiple of 32) + * @param m1 first parameter of the algorithm + * @param m2 second parameter of the algorithm + * @param m3 third parameter of the algorithm + */ + protected AbstractWell(final int k, final int m1, final int m2, final int m3) { + this(k, m1, m2, m3, null); + } + + /** Creates a new random number generator using a single int seed. + * @param k number of bits in the pool (not necessarily a multiple of 32) + * @param m1 first parameter of the algorithm + * @param m2 second parameter of the algorithm + * @param m3 third parameter of the algorithm + * @param seed the initial seed (32 bits integer) + */ + protected AbstractWell(final int k, final int m1, final int m2, final int m3, final int seed) { + this(k, m1, m2, m3, new int[] { seed }); + } + + /** Creates a new random number generator using an int array seed. + * @param k number of bits in the pool (not necessarily a multiple of 32) + * @param m1 first parameter of the algorithm + * @param m2 second parameter of the algorithm + * @param m3 third parameter of the algorithm + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + protected AbstractWell(final int k, final int m1, final int m2, final int m3, final int[] seed) { + + // the bits pool contains k bits, k = r w - p where r is the number + // of w bits blocks, w is the block size (always 32 in the original paper) + // and p is the number of unused bits in the last block + final int w = 32; + final int r = (k + w - 1) / w; + this.v = new int[r]; + this.index = 0; + + // precompute indirection index tables. These tables are used for optimizing access + // they allow saving computations like "(j + r - 2) % r" with costly modulo operations + iRm1 = new int[r]; + iRm2 = new int[r]; + i1 = new int[r]; + i2 = new int[r]; + i3 = new int[r]; + for (int j = 0; j < r; ++j) { + iRm1[j] = (j + r - 1) % r; + iRm2[j] = (j + r - 2) % r; + i1[j] = (j + m1) % r; + i2[j] = (j + m2) % r; + i3[j] = (j + m3) % r; + } + + // initialize the pool content + setSeed(seed); + + } + + /** Creates a new random number generator using a single long seed. + * @param k number of bits in the pool (not necessarily a multiple of 32) + * @param m1 first parameter of the algorithm + * @param m2 second parameter of the algorithm + * @param m3 third parameter of the algorithm + * @param seed the initial seed (64 bits integer) + */ + protected AbstractWell(final int k, final int m1, final int m2, final int m3, final long seed) { + this(k, m1, m2, m3, new int[] { (int) (seed >>> 32), (int) (seed & 0xffffffffl) }); + } + + /** Reinitialize the generator as if just built with the given int seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (32 bits integer) + */ + @Override + public void setSeed(final int seed) { + setSeed(new int[] { seed }); + } + + /** Reinitialize the generator as if just built with the given int array seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (32 bits integers array). If null + * the seed of the generator will be the system time plus the system identity + * hash code of the instance. + */ + @Override + public void setSeed(final int[] seed) { + if (seed == null) { + setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + return; + } + + System.arraycopy(seed, 0, v, 0, FastMath.min(seed.length, v.length)); + + if (seed.length < v.length) { + for (int i = seed.length; i < v.length; ++i) { + final long l = v[i - seed.length]; + v[i] = (int) ((1812433253l * (l ^ (l >> 30)) + i) & 0xffffffffL); + } + } + + index = 0; + clear(); // Clear normal deviate cache + } + + /** Reinitialize the generator as if just built with the given long seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (64 bits integer) + */ + @Override + public void setSeed(final long seed) { + setSeed(new int[] { (int) (seed >>> 32), (int) (seed & 0xffffffffl) }); + } + + /** {@inheritDoc} */ + @Override + protected abstract int next(final int bits); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/BitsStreamGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/BitsStreamGenerator.java new file mode 100644 index 000000000..fdb80b724 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/BitsStreamGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** Base class for random number generators that generates bits streams. + * + * @since 2.0 + */ +public abstract class BitsStreamGenerator + implements RandomGenerator, + Serializable { + /** Serializable version identifier */ + private static final long serialVersionUID = 20130104L; + /** Next gaussian. */ + private double nextGaussian; + + /** + * Creates a new random number generator. + */ + public BitsStreamGenerator() { + nextGaussian = Double.NaN; + } + + /** {@inheritDoc} */ + public abstract void setSeed(int seed); + + /** {@inheritDoc} */ + public abstract void setSeed(int[] seed); + + /** {@inheritDoc} */ + public abstract void setSeed(long seed); + + /** Generate next pseudorandom number. + *

        This method is the core generation algorithm. It is used by all the + * public generation methods for the various primitive types {@link + * #nextBoolean()}, {@link #nextBytes(byte[])}, {@link #nextDouble()}, + * {@link #nextFloat()}, {@link #nextGaussian()}, {@link #nextInt()}, + * {@link #next(int)} and {@link #nextLong()}.

        + * @param bits number of random bits to produce + * @return random bits generated + */ + protected abstract int next(int bits); + + /** {@inheritDoc} */ + public boolean nextBoolean() { + return next(1) != 0; + } + + /** {@inheritDoc} */ + public double nextDouble() { + final long high = ((long) next(26)) << 26; + final int low = next(26); + return (high | low) * 0x1.0p-52d; + } + + /** {@inheritDoc} */ + public float nextFloat() { + return next(23) * 0x1.0p-23f; + } + + /** {@inheritDoc} */ + public double nextGaussian() { + + final double random; + if (Double.isNaN(nextGaussian)) { + // generate a new pair of gaussian numbers + final double x = nextDouble(); + final double y = nextDouble(); + final double alpha = 2 * FastMath.PI * x; + final double r = FastMath.sqrt(-2 * FastMath.log(y)); + random = r * FastMath.cos(alpha); + nextGaussian = r * FastMath.sin(alpha); + } else { + // use the second element of the pair already generated + random = nextGaussian; + nextGaussian = Double.NaN; + } + + return random; + + } + + /** {@inheritDoc} */ + public int nextInt() { + return next(32); + } + + /** + * {@inheritDoc} + *

        This default implementation is copied from Apache Harmony + * java.util.Random (r929253).

        + * + *

        Implementation notes:

          + *
        • If n is a power of 2, this method returns + * {@code (int) ((n * (long) next(31)) >> 31)}.
        • + * + *
        • If n is not a power of 2, what is returned is {@code next(31) % n} + * with {@code next(31)} values rejected (i.e. regenerated) until a + * value that is larger than the remainder of {@code Integer.MAX_VALUE / n} + * is generated. Rejection of this initial segment is necessary to ensure + * a uniform distribution.

        + */ + public int nextInt(int n) throws IllegalArgumentException { + if (n > 0) { + if ((n & -n) == n) { + return (int) ((n * (long) next(31)) >> 31); + } + int bits; + int val; + do { + bits = next(31); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); + } + + /** {@inheritDoc} */ + public long nextLong() { + final long high = ((long) next(32)) << 32; + final long low = ((long) next(32)) & 0xffffffffL; + return high | low; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code long} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed {@code long} + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + public long nextLong(long n) throws IllegalArgumentException { + if (n > 0) { + long bits; + long val; + do { + bits = ((long) next(31)) << 32; + bits |= ((long) next(32)) & 0xffffffffL; + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); + } + + /** + * Clears the cache used by the default implementation of + * {@link #nextGaussian}. + */ + public void clear() { + nextGaussian = Double.NaN; + } + + /** + * Generates random bytes and places them into a user-supplied array. + * + *

        + * The array is filled with bytes extracted from random integers. + * This implies that the number of random bytes generated may be larger than + * the length of the byte array. + *

        + * + * @param bytes Array in which to put the generated bytes. Cannot be {@code null}. + */ + public void nextBytes(byte[] bytes) { + nextBytesFill(bytes, 0, bytes.length); + } + + /** + * Generates random bytes and places them into a user-supplied array. + * + *

        + * The array is filled with bytes extracted from random integers. + * This implies that the number of random bytes generated may be larger than + * the length of the byte array. + *

        + * + * @param bytes Array in which to put the generated bytes. Cannot be {@code null}. + * @param start Index at which to start inserting the generated bytes. + * @param len Number of bytes to insert. + * @throws OutOfRangeException if {@code start < 0} or {@code start >= bytes.length}. + * @throws OutOfRangeException if {@code len < 0} or {@code len > bytes.length - start}. + */ + public void nextBytes(byte[] bytes, + int start, + int len) { + if (start < 0 || + start >= bytes.length) { + throw new OutOfRangeException(start, 0, bytes.length); + } + if (len < 0 || + len > bytes.length - start) { + throw new OutOfRangeException(len, 0, bytes.length - start); + } + + nextBytesFill(bytes, start, len); + } + + /** + * Generates random bytes and places them into a user-supplied array. + * + *

        + * The array is filled with bytes extracted from random integers. + * This implies that the number of random bytes generated may be larger than + * the length of the byte array. + *

        + * + * @param bytes Array in which to put the generated bytes. Cannot be {@code null}. + * @param start Index at which to start inserting the generated bytes. + * @param len Number of bytes to insert. + */ + private void nextBytesFill(byte[] bytes, + int start, + int len) { + int index = start; // Index of first insertion. + + // Index of first insertion plus multiple 4 part of length (i.e. length + // with two least significant bits unset). + final int indexLoopLimit = index + (len & 0x7ffffffc); + + // Start filling in the byte array, 4 bytes at a time. + while (index < indexLoopLimit) { + final int random = next(32); + bytes[index++] = (byte) random; + bytes[index++] = (byte) (random >>> 8); + bytes[index++] = (byte) (random >>> 16); + bytes[index++] = (byte) (random >>> 24); + } + + final int indexLimit = start + len; // Index of last insertion + 1. + + // Fill in the remaining bytes. + if (index < indexLimit) { + int random = next(32); + while (true) { + bytes[index++] = (byte) random; + if (index < indexLimit) { + random >>>= 8; + } else { + break; + } + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.java new file mode 100644 index 000000000..7027b1cac --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RectangularCholeskyDecomposition; + +/** + * A {@link RandomVectorGenerator} that generates vectors with with + * correlated components. + *

        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.

        + *

        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 + * Multivariate Normal Distribution. 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}.

        + *

        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 C = UT.U + * where C is the covariance matrix and U + * is an upper-triangular matrix, we compute C = B.BT + * where B is a rectangular matrix having + * more rows than columns. The number of columns of B is + * the rank of the covariance matrix, and it is the dimension of the + * uncorrelated random vector that is needed to compute the component + * of the correlated vector. This class handles this situation + * automatically.

        + * + * @since 1.2 + */ + +public class CorrelatedRandomVectorGenerator + implements RandomVectorGenerator { + /** Mean vector. */ + private final double[] mean; + /** Underlying generator. */ + private final NormalizedRandomGenerator generator; + /** Storage for the normalized vector. */ + private final double[] normalized; + /** Root of the covariance matrix. */ + private final RealMatrix root; + + /** + * Builds a correlated random vector generator from its mean + * vector and covariance matrix. + * + * @param mean Expected mean values for all components. + * @param covariance Covariance matrix. + * @param small Diagonal elements threshold under which column are + * considered to be dependent on previous ones and are discarded + * @param generator underlying generator for uncorrelated normalized + * components. + * @throws NonPositiveDefiniteMatrixException + * if the covariance matrix is not strictly positive definite. + * @throws DimensionMismatchException if the mean and covariance + * arrays dimensions do not match. + */ + public CorrelatedRandomVectorGenerator(double[] mean, + RealMatrix covariance, double small, + NormalizedRandomGenerator generator) { + int order = covariance.getRowDimension(); + if (mean.length != order) { + throw new DimensionMismatchException(mean.length, order); + } + this.mean = mean.clone(); + + final RectangularCholeskyDecomposition decomposition = + new RectangularCholeskyDecomposition(covariance, small); + root = decomposition.getRootMatrix(); + + this.generator = generator; + normalized = new double[decomposition.getRank()]; + + } + + /** + * Builds a null mean random correlated vector generator from its + * covariance matrix. + * + * @param covariance Covariance matrix. + * @param small Diagonal elements threshold under which column are + * considered to be dependent on previous ones and are discarded. + * @param generator Underlying generator for uncorrelated normalized + * components. + * @throws NonPositiveDefiniteMatrixException + * if the covariance matrix is not strictly positive definite. + */ + public CorrelatedRandomVectorGenerator(RealMatrix covariance, double small, + NormalizedRandomGenerator generator) { + int order = covariance.getRowDimension(); + mean = new double[order]; + for (int i = 0; i < order; ++i) { + mean[i] = 0; + } + + final RectangularCholeskyDecomposition decomposition = + new RectangularCholeskyDecomposition(covariance, small); + root = decomposition.getRootMatrix(); + + this.generator = generator; + normalized = new double[decomposition.getRank()]; + + } + + /** Get the underlying normalized components generator. + * @return underlying uncorrelated components generator + */ + public NormalizedRandomGenerator getGenerator() { + return generator; + } + + /** Get the rank of the covariance matrix. + * The rank is the number of independent rows in the covariance + * matrix, it is also the number of columns of the root matrix. + * @return rank of the square matrix. + * @see #getRootMatrix() + */ + public int getRank() { + return normalized.length; + } + + /** Get the root of the covariance matrix. + * The root is the rectangular matrix B such that + * the covariance matrix is equal to B.BT + * @return root of the square matrix + * @see #getRank() + */ + public RealMatrix getRootMatrix() { + return root; + } + + /** Generate a correlated random vector. + * @return a random vector as an array of double. The returned array + * is created at each call, the caller can do what it wants with it. + */ + public double[] nextVector() { + + // generate uncorrelated vector + for (int i = 0; i < normalized.length; ++i) { + normalized[i] = generator.nextNormalizedDouble(); + } + + // compute correlated vector + double[] correlated = new double[mean.length]; + for (int i = 0; i < correlated.length; ++i) { + correlated[i] = mean[i]; + for (int j = 0; j < root.getColumnDimension(); ++j) { + correlated[i] += root.getEntry(i, j) * normalized[j]; + } + } + + return correlated; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/EmpiricalDistribution.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/EmpiricalDistribution.java new file mode 100644 index 000000000..4878569b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/EmpiricalDistribution.java @@ -0,0 +1,853 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.random; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.distribution.AbstractRealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ConstantRealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.distribution.RealDistribution; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.StatisticalSummary; +import com.fr.third.org.apache.commons.math3.stat.descriptive.SummaryStatistics; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + *

        Represents an + * empirical probability distribution -- a probability distribution derived + * from observed data without making any assumptions about the functional form + * of the population distribution that the data come from.

        + * + *

        An EmpiricalDistribution maintains data structures, called + * distribution digests, that describe empirical distributions and + * support the following operations:

          + *
        • loading the distribution from a file of observed data values
        • + *
        • dividing the input data into "bin ranges" and reporting bin frequency + * counts (data for histogram)
        • + *
        • reporting univariate statistics describing the full set of data values + * as well as the observations within each bin
        • + *
        • generating random values from the distribution
        • + *
        + * Applications can use EmpiricalDistribution 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.

        + * + *

        The implementation uses what amounts to the + * + * Variable Kernel Method with Gaussian smoothing:

        + * Digesting the input file + *

        1. Pass the file once to compute min and max.
        2. + *
        3. Divide the range from min-max into binCount "bins."
        4. + *
        5. Pass the data file again, computing bin counts and univariate + * statistics (mean, std dev.) for each of the bins
        6. + *
        7. Divide the interval (0,1) into subintervals associated with the bins, + * with the length of a bin's subinterval proportional to its count.
        + * Generating random values from the distribution
          + *
        1. Generate a uniformly distributed value in (0,1)
        2. + *
        3. Select the subinterval to which the value belongs. + *
        4. Generate a random Gaussian value with mean = mean of the associated + * bin and std dev = std dev of associated bin.

        + * + *

        EmpiricalDistribution implements the {@link RealDistribution} interface + * as follows. Given x within the range of values in the dataset, let B + * be the bin containing x and let K be the within-bin kernel for B. Let P(B-) + * be the sum of the probabilities of the bins below B and let K(B) be the + * mass of B under K (i.e., the integral of the kernel density over B). Then + * set P(X < x) = P(B-) + P(B) * K(x) / K(B) where K(x) is the kernel distribution + * evaluated at x. This results in a cdf that matches the grouped frequency + * distribution at the bin endpoints and interpolates within bins using + * within-bin kernels.

        + * + *USAGE NOTES:
          + *
        • The binCount 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.
        • + *
        • The input file must be a plain text file containing one valid numeric + * entry per line.
        • + *

        + * + */ +public class EmpiricalDistribution extends AbstractRealDistribution { + + /** Default bin count */ + public static final int DEFAULT_BIN_COUNT = 1000; + + /** Character set for file input */ + private static final String FILE_CHARSET = "US-ASCII"; + + /** Serializable version identifier */ + private static final long serialVersionUID = 5729073523949762654L; + + /** RandomDataGenerator instance to use in repeated calls to getNext() */ + protected final RandomDataGenerator randomData; + + /** List of SummaryStatistics objects characterizing the bins */ + private final List binStats; + + /** Sample statistics */ + private SummaryStatistics sampleStats = null; + + /** Max loaded value */ + private double max = Double.NEGATIVE_INFINITY; + + /** Min loaded value */ + private double min = Double.POSITIVE_INFINITY; + + /** Grid size */ + private double delta = 0d; + + /** number of bins */ + private final int binCount; + + /** is the distribution loaded? */ + private boolean loaded = false; + + /** upper bounds of subintervals in (0,1) "belonging" to the bins */ + private double[] upperBounds = null; + + /** + * Creates a new EmpiricalDistribution with the default bin count. + */ + public EmpiricalDistribution() { + this(DEFAULT_BIN_COUNT); + } + + /** + * Creates a new EmpiricalDistribution with the specified bin count. + * + * @param binCount number of bins. Must be strictly positive. + * @throws NotStrictlyPositiveException if {@code binCount <= 0}. + */ + public EmpiricalDistribution(int binCount) { + this(binCount, new RandomDataGenerator()); + } + + /** + * Creates a new EmpiricalDistribution with the specified bin count using the + * provided {@link RandomGenerator} as the source of random data. + * + * @param binCount number of bins. Must be strictly positive. + * @param generator random data generator (may be null, resulting in default JDK generator) + * @throws NotStrictlyPositiveException if {@code binCount <= 0}. + * @since 3.0 + */ + public EmpiricalDistribution(int binCount, RandomGenerator generator) { + this(binCount, new RandomDataGenerator(generator)); + } + + /** + * Creates a new EmpiricalDistribution with default bin count using the + * provided {@link RandomGenerator} as the source of random data. + * + * @param generator random data generator (may be null, resulting in default JDK generator) + * @since 3.0 + */ + public EmpiricalDistribution(RandomGenerator generator) { + this(DEFAULT_BIN_COUNT, generator); + } + + /** + * Creates a new EmpiricalDistribution with the specified bin count using the + * provided {@link RandomDataImpl} instance as the source of random data. + * + * @param binCount number of bins + * @param randomData random data generator (may be null, resulting in default JDK generator) + * @since 3.0 + * @deprecated As of 3.1. Please use {@link #EmpiricalDistribution(int,RandomGenerator)} instead. + */ + @Deprecated + public EmpiricalDistribution(int binCount, RandomDataImpl randomData) { + this(binCount, randomData.getDelegate()); + } + + /** + * Creates a new EmpiricalDistribution with default bin count using the + * provided {@link RandomDataImpl} as the source of random data. + * + * @param randomData random data generator (may be null, resulting in default JDK generator) + * @since 3.0 + * @deprecated As of 3.1. Please use {@link #EmpiricalDistribution(RandomGenerator)} instead. + */ + @Deprecated + public EmpiricalDistribution(RandomDataImpl randomData) { + this(DEFAULT_BIN_COUNT, randomData); + } + + /** + * Private constructor to allow lazy initialisation of the RNG contained + * in the {@link #randomData} instance variable. + * + * @param binCount number of bins. Must be strictly positive. + * @param randomData Random data generator. + * @throws NotStrictlyPositiveException if {@code binCount <= 0}. + */ + private EmpiricalDistribution(int binCount, + RandomDataGenerator randomData) { + super(randomData.getRandomGenerator()); + if (binCount <= 0) { + throw new NotStrictlyPositiveException(binCount); + } + this.binCount = binCount; + this.randomData = randomData; + binStats = new ArrayList(); + } + + /** + * Computes the empirical distribution from the provided + * array of numbers. + * + * @param in the input data array + * @exception NullArgumentException if in is null + */ + public void load(double[] in) throws NullArgumentException { + DataAdapter da = new ArrayDataAdapter(in); + try { + da.computeStats(); + // new adapter for the second pass + fillBinStats(new ArrayDataAdapter(in)); + } catch (IOException ex) { + // Can't happen + throw new MathInternalError(); + } + loaded = true; + + } + + /** + * Computes the empirical distribution using data read from a URL. + * + *

        The input file must be an ASCII text file containing one + * valid numeric entry per line.

        + * + * @param url url of the input file + * + * @throws IOException if an IO error occurs + * @throws NullArgumentException if url is null + * @throws ZeroException if URL contains no data + */ + public void load(URL url) throws IOException, NullArgumentException, ZeroException { + MathUtils.checkNotNull(url); + Charset charset = Charset.forName(FILE_CHARSET); + BufferedReader in = + new BufferedReader(new InputStreamReader(url.openStream(), charset)); + try { + DataAdapter da = new StreamDataAdapter(in); + da.computeStats(); + if (sampleStats.getN() == 0) { + throw new ZeroException(LocalizedFormats.URL_CONTAINS_NO_DATA, url); + } + // new adapter for the second pass + in = new BufferedReader(new InputStreamReader(url.openStream(), charset)); + fillBinStats(new StreamDataAdapter(in)); + loaded = true; + } finally { + try { + in.close(); + } catch (IOException ex) { //NOPMD + // ignore + } + } + } + + /** + * Computes the empirical distribution from the input file. + * + *

        The input file must be an ASCII text file containing one + * valid numeric entry per line.

        + * + * @param file the input file + * @throws IOException if an IO error occurs + * @throws NullArgumentException if file is null + */ + public void load(File file) throws IOException, NullArgumentException { + MathUtils.checkNotNull(file); + Charset charset = Charset.forName(FILE_CHARSET); + InputStream is = new FileInputStream(file); + BufferedReader in = new BufferedReader(new InputStreamReader(is, charset)); + try { + DataAdapter da = new StreamDataAdapter(in); + da.computeStats(); + // new adapter for second pass + is = new FileInputStream(file); + in = new BufferedReader(new InputStreamReader(is, charset)); + fillBinStats(new StreamDataAdapter(in)); + loaded = true; + } finally { + try { + in.close(); + } catch (IOException ex) { //NOPMD + // ignore + } + } + } + + /** + * Provides methods for computing sampleStats and + * beanStats 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; + + } + + /** + * DataAdapter for data provided through some input stream + */ + private class StreamDataAdapter extends DataAdapter{ + + /** Input stream providing access to the data */ + private BufferedReader inputStream; + + /** + * Create a StreamDataAdapter from a BufferedReader + * + * @param in BufferedReader input stream + */ + StreamDataAdapter(BufferedReader in){ + super(); + inputStream = in; + } + + /** {@inheritDoc} */ + @Override + public void computeBinStats() throws IOException { + String str = null; + double val = 0.0d; + while ((str = inputStream.readLine()) != null) { + val = Double.parseDouble(str); + SummaryStatistics stats = binStats.get(findBin(val)); + stats.addValue(val); + } + + inputStream.close(); + inputStream = null; + } + + /** {@inheritDoc} */ + @Override + public void computeStats() throws IOException { + String str = null; + double val = 0.0; + sampleStats = new SummaryStatistics(); + while ((str = inputStream.readLine()) != null) { + val = Double.parseDouble(str); + sampleStats.addValue(val); + } + inputStream.close(); + inputStream = null; + } + } + + /** + * DataAdapter for data provided as array of doubles. + */ + private class ArrayDataAdapter extends DataAdapter { + + /** Array of input data values */ + private double[] inputArray; + + /** + * Construct an ArrayDataAdapter from a double[] array + * + * @param in double[] array holding the data + * @throws NullArgumentException if in is null + */ + ArrayDataAdapter(double[] in) throws NullArgumentException { + super(); + MathUtils.checkNotNull(in); + inputArray = in; + } + + /** {@inheritDoc} */ + @Override + public void computeStats() throws IOException { + sampleStats = new SummaryStatistics(); + for (int i = 0; i < inputArray.length; i++) { + sampleStats.addValue(inputArray[i]); + } + } + + /** {@inheritDoc} */ + @Override + public void computeBinStats() throws IOException { + for (int i = 0; i < inputArray.length; i++) { + SummaryStatistics stats = + binStats.get(findBin(inputArray[i])); + stats.addValue(inputArray[i]); + } + } + } + + /** + * Fills binStats array (second pass through data file). + * + * @param da object providing access to the data + * @throws IOException if an IO error occurs + */ + private void fillBinStats(final DataAdapter da) + throws IOException { + // Set up grid + min = sampleStats.getMin(); + max = sampleStats.getMax(); + delta = (max - min)/((double) binCount); + + // Initialize binStats ArrayList + if (!binStats.isEmpty()) { + binStats.clear(); + } + for (int i = 0; i < binCount; i++) { + SummaryStatistics stats = new SummaryStatistics(); + binStats.add(i,stats); + } + + // Filling data in binStats Array + da.computeBinStats(); + + // Assign upperBounds based on bin counts + upperBounds = new double[binCount]; + upperBounds[0] = + ((double) binStats.get(0).getN()) / (double) sampleStats.getN(); + for (int i = 1; i < binCount-1; i++) { + upperBounds[i] = upperBounds[i-1] + + ((double) binStats.get(i).getN()) / (double) sampleStats.getN(); + } + upperBounds[binCount-1] = 1.0d; + } + + /** + * Returns the index of the bin to which the given value belongs + * + * @param value the value whose bin we are trying to find + * @return the index of the bin containing the value + */ + private int findBin(double value) { + return FastMath.min( + FastMath.max((int) FastMath.ceil((value - min) / delta) - 1, 0), + binCount - 1); + } + + /** + * Generates a random value from this distribution. + * Preconditions:
          + *
        • the distribution must be loaded before invoking this method
        + * @return the random value. + * @throws MathIllegalStateException if the distribution has not been loaded + */ + public double getNextValue() throws MathIllegalStateException { + + if (!loaded) { + throw new MathIllegalStateException(LocalizedFormats.DISTRIBUTION_NOT_LOADED); + } + + return sample(); + } + + /** + * Returns a {@link StatisticalSummary} describing this distribution. + * Preconditions:
          + *
        • the distribution must be loaded before invoking this method
        + * + * @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 getBinStats() { + return binStats; + } + + /** + *

        Returns a fresh copy of the array of upper bounds for the bins. + * Bins are:
        + * [min,upperBounds[0]],(upperBounds[0],upperBounds[1]],..., + * (upperBounds[binCount-2], upperBounds[binCount-1] = max].

        + * + *

        Note: In versions 1.0-2.0 of commons-math, this method + * incorrectly returned the array of probability generator upper + * bounds now returned by {@link #getGeneratorUpperBounds()}.

        + * + * @return array of bin upper bounds + * @since 2.1 + */ + public double[] getUpperBounds() { + double[] binUpperBounds = new double[binCount]; + for (int i = 0; i < binCount - 1; i++) { + binUpperBounds[i] = min + delta * (i + 1); + } + binUpperBounds[binCount - 1] = max; + return binUpperBounds; + } + + /** + *

        Returns a fresh copy of the array of upper bounds of the subintervals + * of [0,1] used in generating data from the empirical distribution. + * Subintervals correspond to bins with lengths proportional to bin counts.

        + * + * Preconditions:
          + *
        • the distribution must be loaded before invoking this method
        + * + *

        In versions 1.0-2.0 of commons-math, this array was (incorrectly) returned + * by {@link #getUpperBounds()}.

        + * + * @since 2.1 + * @return array of upper bounds of subintervals used in data generation + * @throws NullPointerException unless a {@code load} method has been + * called beforehand. + */ + public double[] getGeneratorUpperBounds() { + int len = upperBounds.length; + double[] out = new double[len]; + System.arraycopy(upperBounds, 0, out, 0, len); + return out; + } + + /** + * Property indicating whether or not the distribution has been loaded. + * + * @return true if the distribution has been loaded + */ + public boolean isLoaded() { + return loaded; + } + + /** + * Reseeds the random number generator used by {@link #getNextValue()}. + * + * @param seed random generator seed + * @since 3.0 + */ + public void reSeed(long seed) { + randomData.reSeed(seed); + } + + // Distribution methods --------------------------- + + /** + * {@inheritDoc} + * @since 3.1 + */ + @Override + public double probability(double x) { + return 0; + } + + /** + * {@inheritDoc} + * + *

        Returns the kernel density normalized so that its integral over each bin + * equals the bin mass.

        + * + *

        Algorithm description:

          + *
        1. Find the bin B that x belongs to.
        2. + *
        3. Compute K(B) = the mass of B with respect to the within-bin kernel (i.e., the + * integral of the kernel density over B).
        4. + *
        5. Return k(x) * P(B) / K(B), where k is the within-bin kernel density + * and P(B) is the mass of B.

        + * @since 3.1 + */ + public double density(double x) { + if (x < min || x > max) { + return 0d; + } + final int binIndex = findBin(x); + final RealDistribution kernel = getKernel(binStats.get(binIndex)); + return kernel.density(x) * pB(binIndex) / kB(binIndex); + } + + /** + * {@inheritDoc} + * + *

        Algorithm description:

          + *
        1. Find the bin B that x belongs to.
        2. + *
        3. Compute P(B) = the mass of B and P(B-) = the combined mass of the bins below B.
        4. + *
        5. Compute K(B) = the probability mass of B with respect to the within-bin kernel + * and K(B-) = the kernel distribution evaluated at the lower endpoint of B
        6. + *
        7. Return P(B-) + P(B) * [K(x) - K(B-)] / K(B) where + * K(x) is the within-bin kernel distribution function evaluated at x.
        + * If K is a constant distribution, we return P(B-) + P(B) (counting the full + * mass of B).

        + * + * @since 3.1 + */ + public double cumulativeProbability(double x) { + if (x < min) { + return 0d; + } else if (x >= max) { + return 1d; + } + final int binIndex = findBin(x); + final double pBminus = pBminus(binIndex); + final double pB = pB(binIndex); + final RealDistribution kernel = k(x); + if (kernel instanceof ConstantRealDistribution) { + if (x < kernel.getNumericalMean()) { + return pBminus; + } else { + return pBminus + pB; + } + } + final double[] binBounds = getUpperBounds(); + final double kB = kB(binIndex); + final double lower = binIndex == 0 ? min : binBounds[binIndex - 1]; + final double withinBinCum = + (kernel.cumulativeProbability(x) - kernel.cumulativeProbability(lower)) / kB; + return pBminus + pB * withinBinCum; + } + + /** + * {@inheritDoc} + * + *

        Algorithm description:

          + *
        1. Find the smallest i such that the sum of the masses of the bins + * through i is at least p.
        2. + *
        3. + * Let K be the within-bin kernel distribution for bin i.
          + * Let K(B) be the mass of B under K.
          + * Let K(B-) be K evaluated at the lower endpoint of B (the combined + * mass of the bins below B under K).
          + * Let P(B) be the probability of bin i.
          + * Let P(B-) be the sum of the bin masses below bin i.
          + * Let pCrit = p - P(B-)
          + *
        4. Return the inverse of K evaluated at
          + * K(B-) + pCrit * K(B) / P(B)
        5. + *

        + * + * @since 3.1 + */ + @Override + public double inverseCumulativeProbability(final double p) throws OutOfRangeException { + if (p < 0.0 || p > 1.0) { + throw new OutOfRangeException(p, 0, 1); + } + + if (p == 0.0) { + return getSupportLowerBound(); + } + + if (p == 1.0) { + return getSupportUpperBound(); + } + + int i = 0; + while (cumBinP(i) < p) { + i++; + } + + final RealDistribution kernel = getKernel(binStats.get(i)); + final double kB = kB(i); + final double[] binBounds = getUpperBounds(); + final double lower = i == 0 ? min : binBounds[i - 1]; + final double kBminus = kernel.cumulativeProbability(lower); + final double pB = pB(i); + final double pBminus = pBminus(i); + final double pCrit = p - pBminus; + if (pCrit <= 0) { + return lower; + } + return kernel.inverseCumulativeProbability(kBminus + pCrit * kB / pB); + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public double getNumericalMean() { + return sampleStats.getMean(); + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public double getNumericalVariance() { + return sampleStats.getVariance(); + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public double getSupportLowerBound() { + return min; + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public double getSupportUpperBound() { + return max; + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public boolean isSupportLowerBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public boolean isSupportUpperBoundInclusive() { + return true; + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + public boolean isSupportConnected() { + return true; + } + + /** + * {@inheritDoc} + * @since 3.1 + */ + @Override + public void reseedRandomGenerator(long seed) { + randomData.reSeed(seed); + } + + /** + * The probability of bin i. + * + * @param i the index of the bin + * @return the probability that selection begins in bin i + */ + private double pB(int i) { + return i == 0 ? upperBounds[0] : + upperBounds[i] - upperBounds[i - 1]; + } + + /** + * The combined probability of the bins up to but not including bin i. + * + * @param i the index of the bin + * @return the probability that selection begins in a bin below bin i. + */ + private double pBminus(int i) { + return i == 0 ? 0 : upperBounds[i - 1]; + } + + /** + * Mass of bin i under the within-bin kernel of the bin. + * + * @param i index of the bin + * @return the difference in the within-bin kernel cdf between the + * upper and lower endpoints of bin i + */ + @SuppressWarnings("deprecation") + private double kB(int i) { + final double[] binBounds = getUpperBounds(); + final RealDistribution kernel = getKernel(binStats.get(i)); + return i == 0 ? kernel.cumulativeProbability(min, binBounds[0]) : + kernel.cumulativeProbability(binBounds[i - 1], binBounds[i]); + } + + /** + * The within-bin kernel of the bin that x belongs to. + * + * @param x the value to locate within a bin + * @return the within-bin kernel of the bin containing x + */ + private RealDistribution k(double x) { + final int binIndex = findBin(x); + return getKernel(binStats.get(binIndex)); + } + + /** + * The combined probability of the bins up to and including binIndex. + * + * @param binIndex maximum bin index + * @return sum of the probabilities of bins through binIndex + */ + private double cumBinP(int binIndex) { + return upperBounds[binIndex]; + } + + /** + * The within-bin smoothing kernel. Returns a Gaussian distribution + * parameterized by {@code bStats}, unless the bin contains only one + * observation, in which case a constant distribution is returned. + * + * @param bStats summary statistics for the bin + * @return within-bin kernel parameterized by bStats + */ + protected RealDistribution getKernel(SummaryStatistics bStats) { + if (bStats.getN() == 1 || bStats.getVariance() == 0) { + return new ConstantRealDistribution(bStats.getMean()); + } else { + return new NormalDistribution(randomData.getRandomGenerator(), + bStats.getMean(), bStats.getStandardDeviation(), + NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/GaussianRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/GaussianRandomGenerator.java new file mode 100644 index 000000000..8c7b59e48 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/GaussianRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +/** + * This class is a gaussian normalized random generator for scalars. + *

        This class is a simple wrapper around the {@link + * RandomGenerator#nextGaussian} method.

        + * @since 1.2 + */ + +public class GaussianRandomGenerator implements NormalizedRandomGenerator { + + /** Underlying generator. */ + private final RandomGenerator generator; + + /** Create a new generator. + * @param generator underlying random generator to use + */ + public GaussianRandomGenerator(final RandomGenerator generator) { + this.generator = generator; + } + + /** Generate a random scalar with null mean and unit standard deviation. + * @return a random scalar with null mean and unit standard deviation + */ + public double nextNormalizedDouble() { + return generator.nextGaussian(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/HaltonSequenceGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/HaltonSequenceGenerator.java new file mode 100644 index 000000000..82b0f1686 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/HaltonSequenceGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of a Halton sequence. + *

        + * A Halton sequence is a low-discrepancy sequence generating points in the interval [0, 1] according to + *

        + *   H(n) = d_0 / b + d_1 / b^2 .... d_j / b^j+1
        + *
        + *   with
        + *
        + *   n = d_j * b^j-1 + ... d_1 * b + d_0 * b^0
        + * 
        + * For higher dimensions, subsequent prime numbers are used as base, e.g. { 2, 3, 5 } for a Halton sequence in R^3. + *

        + * Halton sequences are known to suffer from linear correlation for larger prime numbers, thus the individual digits + * are usually scrambled. This implementation already comes with support for up to 40 dimensions with optimal weight + * numbers from + * H. Chi: Scrambled quasirandom sequences and their applications. + *

        + * The generator supports two modes: + *

          + *
        • sequential generation of points: {@link #nextVector()}
        • + *
        • random access to the i-th point in the sequence: {@link #skipTo(int)}
        • + *
        + * + * @see Halton sequence (Wikipedia) + * @see + * On the Halton sequence and its scramblings + * @since 3.3 + */ +public class HaltonSequenceGenerator implements RandomVectorGenerator { + + /** The first 40 primes. */ + private static final int[] PRIMES = new int[] { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, + 149, 151, 157, 163, 167, 173 + }; + + /** The optimal weights used for scrambling of the first 40 dimension. */ + private static final int[] WEIGHTS = new int[] { + 1, 2, 3, 3, 8, 11, 12, 14, 7, 18, 12, 13, 17, 18, 29, 14, 18, 43, 41, + 44, 40, 30, 47, 65, 71, 28, 40, 60, 79, 89, 56, 50, 52, 61, 108, 56, + 66, 63, 60, 66 + }; + + /** Space dimension. */ + private final int dimension; + + /** The current index in the sequence. */ + private int count = 0; + + /** The base numbers for each component. */ + private final int[] base; + + /** The scrambling weights for each component. */ + private final int[] weight; + + /** + * Construct a new Halton sequence generator for the given space dimension. + * + * @param dimension the space dimension + * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 40] + */ + public HaltonSequenceGenerator(final int dimension) throws OutOfRangeException { + this(dimension, PRIMES, WEIGHTS); + } + + /** + * Construct a new Halton sequence generator with the given base numbers and weights for each dimension. + * The length of the bases array defines the space dimension and is required to be > 0. + * + * @param dimension the space dimension + * @param bases the base number for each dimension, entries should be (pairwise) prime, may not be null + * @param weights the weights used during scrambling, may be null in which case no scrambling will be performed + * @throws NullArgumentException if base is null + * @throws OutOfRangeException if the space dimension is outside the range [1, len], where + * len refers to the length of the bases array + * @throws DimensionMismatchException if weights is non-null and the length of the input arrays differ + */ + public HaltonSequenceGenerator(final int dimension, final int[] bases, final int[] weights) + throws NullArgumentException, OutOfRangeException, DimensionMismatchException { + + MathUtils.checkNotNull(bases); + + if (dimension < 1 || dimension > bases.length) { + throw new OutOfRangeException(dimension, 1, PRIMES.length); + } + + if (weights != null && weights.length != bases.length) { + throw new DimensionMismatchException(weights.length, bases.length); + } + + this.dimension = dimension; + this.base = bases.clone(); + this.weight = weights == null ? null : weights.clone(); + count = 0; + } + + /** {@inheritDoc} */ + public double[] nextVector() { + final double[] v = new double[dimension]; + for (int i = 0; i < dimension; i++) { + int index = count; + double f = 1.0 / base[i]; + + int j = 0; + while (index > 0) { + final int digit = scramble(i, j, base[i], index % base[i]); + v[i] += f * digit; + index /= base[i]; // floor( index / base ) + f /= base[i]; + } + } + count++; + return v; + } + + /** + * Performs scrambling of digit {@code d_j} according to the formula: + *
        +     *   ( weight_i * d_j ) mod base
        +     * 
        + * Implementations can override this method to do a different scrambling. + * + * @param i the dimension index + * @param j the digit index + * @param b the base for this dimension + * @param digit the j-th digit + * @return the scrambled digit + */ + protected int scramble(final int i, final int j, final int b, final int digit) { + return weight != null ? (weight[i] * digit) % b : digit; + } + + /** + * Skip to the i-th point in the Halton sequence. + *

        + * This operation can be performed in O(1). + * + * @param index the index in the sequence to skip to + * @return the i-th point in the Halton sequence + * @throws NotPositiveException if index < 0 + */ + public double[] skipTo(final int index) throws NotPositiveException { + count = index; + return nextVector(); + } + + /** + * Returns the index i of the next point in the Halton sequence that will be returned + * by calling {@link #nextVector()}. + * + * @return the index of the next point + */ + public int getNextIndex() { + return count; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ISAACRandom.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ISAACRandom.java new file mode 100644 index 000000000..a6cc11774 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ISAACRandom.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.random; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * + * ISAAC: a fast cryptographic pseudo-random number generator + *
        + * ISAAC (Indirection, Shift, Accumulate, Add, and Count) generates 32-bit + * random numbers. + * ISAAC has been designed to be cryptographically secure and is inspired + * by RC4. + * Cycles are guaranteed to be at least 240 values long, and they + * are 28295 values long on average. + * The results are uniformly distributed, unbiased, and unpredictable unless + * you know the seed. + *
        + * This code is based (with minor changes and improvements) on the original + * implementation of the algorithm by Bob Jenkins. + *
        + * + * @since 3.0 + */ +public class ISAACRandom extends BitsStreamGenerator implements Serializable { + /** Serializable version identifier */ + private static final long serialVersionUID = 7288197941165002400L; + /** Log of size of rsl[] and mem[] */ + private static final int SIZE_L = 8; + /** Size of rsl[] and mem[] */ + private static final int SIZE = 1 << SIZE_L; + /** Half-size of rsl[] and mem[] */ + private static final int H_SIZE = SIZE >> 1; + /** For pseudo-random lookup */ + private static final int MASK = SIZE - 1 << 2; + /** The golden ratio */ + private static final int GLD_RATIO = 0x9e3779b9; + /** The results given to the user */ + private final int[] rsl = new int[SIZE]; + /** The internal state */ + private final int[] mem = new int[SIZE]; + /** Count through the results in rsl[] */ + private int count; + /** Accumulator */ + private int isaacA; + /** The last result */ + private int isaacB; + /** Counter, guarantees cycle is at least 2^40 */ + private int isaacC; + /** Service variable. */ + private final int[] arr = new int[8]; + /** Service variable. */ + private int isaacX; + /** Service variable. */ + private int isaacI; + /** Service variable. */ + private int isaacJ; + + + /** + * Creates a new ISAAC random number generator. + *
        + * The instance is initialized using a combination of the + * current time and system hash code of the instance as the seed. + */ + public ISAACRandom() { + setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + } + + /** + * Creates a new ISAAC random number generator using a single long seed. + * + * @param seed Initial seed. + */ + public ISAACRandom(long seed) { + setSeed(seed); + } + + /** + * Creates a new ISAAC random number generator using an int array seed. + * + * @param seed Initial seed. If {@code null}, the seed will be related + * to the current time. + */ + public ISAACRandom(int[] seed) { + setSeed(seed); + } + + /** {@inheritDoc} */ + @Override + public void setSeed(int seed) { + setSeed(new int[]{seed}); + } + + /** {@inheritDoc} */ + @Override + public void setSeed(long seed) { + setSeed(new int[]{(int) (seed >>> 32), (int) (seed & 0xffffffffL)}); + } + + /** {@inheritDoc} */ + @Override + public void setSeed(int[] seed) { + if (seed == null) { + setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + return; + } + final int seedLen = seed.length; + final int rslLen = rsl.length; + System.arraycopy(seed, 0, rsl, 0, FastMath.min(seedLen, rslLen)); + if (seedLen < rslLen) { + for (int j = seedLen; j < rslLen; j++) { + long k = rsl[j - seedLen]; + rsl[j] = (int) (0x6c078965L * (k ^ k >> 30) + j & 0xffffffffL); + } + } + initState(); + } + + /** {@inheritDoc} */ + @Override + protected int next(int bits) { + if (count < 0) { + isaac(); + count = SIZE - 1; + } + return rsl[count--] >>> 32 - bits; + } + + /** Generate 256 results */ + private void isaac() { + isaacI = 0; + isaacJ = H_SIZE; + isaacB += ++isaacC; + while (isaacI < H_SIZE) { + isaac2(); + } + isaacJ = 0; + while (isaacJ < H_SIZE) { + isaac2(); + } + } + + /** Intermediate internal loop. */ + private void isaac2() { + isaacX = mem[isaacI]; + isaacA ^= isaacA << 13; + isaacA += mem[isaacJ++]; + isaac3(); + isaacX = mem[isaacI]; + isaacA ^= isaacA >>> 6; + isaacA += mem[isaacJ++]; + isaac3(); + isaacX = mem[isaacI]; + isaacA ^= isaacA << 2; + isaacA += mem[isaacJ++]; + isaac3(); + isaacX = mem[isaacI]; + isaacA ^= isaacA >>> 16; + isaacA += mem[isaacJ++]; + isaac3(); + } + + /** Lowest level internal loop. */ + private void isaac3() { + mem[isaacI] = mem[(isaacX & MASK) >> 2] + isaacA + isaacB; + isaacB = mem[(mem[isaacI] >> SIZE_L & MASK) >> 2] + isaacX; + rsl[isaacI++] = isaacB; + } + + /** Initialize, or reinitialize, this instance of rand. */ + private void initState() { + isaacA = 0; + isaacB = 0; + isaacC = 0; + for (int j = 0; j < arr.length; j++) { + arr[j] = GLD_RATIO; + } + for (int j = 0; j < 4; j++) { + shuffle(); + } + // fill in mem[] with messy stuff + for (int j = 0; j < SIZE; j += 8) { + arr[0] += rsl[j]; + arr[1] += rsl[j + 1]; + arr[2] += rsl[j + 2]; + arr[3] += rsl[j + 3]; + arr[4] += rsl[j + 4]; + arr[5] += rsl[j + 5]; + arr[6] += rsl[j + 6]; + arr[7] += rsl[j + 7]; + shuffle(); + setState(j); + } + // second pass makes all of seed affect all of mem + for (int j = 0; j < SIZE; j += 8) { + arr[0] += mem[j]; + arr[1] += mem[j + 1]; + arr[2] += mem[j + 2]; + arr[3] += mem[j + 3]; + arr[4] += mem[j + 4]; + arr[5] += mem[j + 5]; + arr[6] += mem[j + 6]; + arr[7] += mem[j + 7]; + shuffle(); + setState(j); + } + isaac(); + count = SIZE - 1; + clear(); + } + + /** Shuffle array. */ + private void shuffle() { + arr[0] ^= arr[1] << 11; + arr[3] += arr[0]; + arr[1] += arr[2]; + arr[1] ^= arr[2] >>> 2; + arr[4] += arr[1]; + arr[2] += arr[3]; + arr[2] ^= arr[3] << 8; + arr[5] += arr[2]; + arr[3] += arr[4]; + arr[3] ^= arr[4] >>> 16; + arr[6] += arr[3]; + arr[4] += arr[5]; + arr[4] ^= arr[5] << 10; + arr[7] += arr[4]; + arr[5] += arr[6]; + arr[5] ^= arr[6] >>> 4; + arr[0] += arr[5]; + arr[6] += arr[7]; + arr[6] ^= arr[7] << 8; + arr[1] += arr[6]; + arr[7] += arr[0]; + arr[7] ^= arr[0] >>> 9; + arr[2] += arr[7]; + arr[0] += arr[1]; + } + + /** Set the state by copying the internal arrays. + * + * @param start First index into {@link #mem} array. + */ + private void setState(int start) { + mem[start] = arr[0]; + mem[start + 1] = arr[1]; + mem[start + 2] = arr[2]; + mem[start + 3] = arr[3]; + mem[start + 4] = arr[4]; + mem[start + 5] = arr[5]; + mem[start + 6] = arr[6]; + mem[start + 7] = arr[7]; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/JDKRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/JDKRandomGenerator.java new file mode 100644 index 000000000..a5336e5c3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/JDKRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import java.util.Random; + +/** + * Extension of java.util.Random to implement + * {@link RandomGenerator}. + * + * @since 1.1 + */ +public class JDKRandomGenerator extends Random implements RandomGenerator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -7745277476784028798L; + + /** + * Create a new JDKRandomGenerator with a default seed. + */ + public JDKRandomGenerator() { + super(); + } + + /** + * Create a new JDKRandomGenerator with the given seed. + * + * @param seed initial seed + * @since 3.6 + */ + public JDKRandomGenerator(int seed) { + setSeed(seed); + } + + /** {@inheritDoc} */ + public void setSeed(int seed) { + setSeed((long) seed); + } + + /** {@inheritDoc} */ + public void setSeed(int[] seed) { + setSeed(RandomGeneratorFactory.convertToLong(seed)); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/MersenneTwister.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/MersenneTwister.java new file mode 100644 index 000000000..89d1de7a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/MersenneTwister.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.random; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** This class implements a powerful pseudo-random number generator + * developed by Makoto Matsumoto and Takuji Nishimura during + * 1996-1997. + + *

        This generator features an extremely long period + * (219937-1) and 623-dimensional equidistribution up to 32 + * bits accuracy. The home page for this generator is located at + * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html.

        + + *

        This generator is described in a paper by Makoto Matsumoto and + * Takuji Nishimura in 1998: Mersenne + * Twister: A 623-Dimensionally Equidistributed Uniform Pseudo-Random + * Number Generator, ACM Transactions on Modeling and Computer + * Simulation, Vol. 8, No. 1, January 1998, pp 3--30

        + + *

        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:

        + + * + * + + * + + * + *
        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. + *
        3. 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.
        4. + *
        5. The names of its contributors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission.
        6. + *
        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.
        + + * @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. + *

        The instance is initialized using the current time plus the + * system identity hash code of this instance as the seed.

        + */ + public MersenneTwister() { + mt = new int[N]; + setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public MersenneTwister(int seed) { + mt = new int[N]; + setSeed(seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public MersenneTwister(int[] seed) { + mt = new int[N]; + setSeed(seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public MersenneTwister(long seed) { + mt = new int[N]; + setSeed(seed); + } + + /** Reinitialize the generator as if just built with the given int seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (32 bits integer) + */ + @Override + public void setSeed(int seed) { + // we use a long masked by 0xffffffffL as a poor man unsigned int + long longMT = seed; + // NB: unlike original C code, we are working with java longs, the cast below makes masking unnecessary + mt[0]= (int) longMT; + for (mti = 1; mti < N; ++mti) { + // See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. + // initializer from the 2002-01-09 C version by Makoto Matsumoto + longMT = (1812433253l * (longMT ^ (longMT >> 30)) + mti) & 0xffffffffL; + mt[mti]= (int) longMT; + } + + clear(); // Clear normal deviate cache + } + + /** Reinitialize the generator as if just built with the given int array seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be the current system time plus the + * system identity hash code of this instance + */ + @Override + public void setSeed(int[] seed) { + + if (seed == null) { + setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + return; + } + + setSeed(19650218); + int i = 1; + int j = 0; + + for (int k = FastMath.max(N, seed.length); k != 0; k--) { + long l0 = (mt[i] & 0x7fffffffl) | ((mt[i] < 0) ? 0x80000000l : 0x0l); + long l1 = (mt[i-1] & 0x7fffffffl) | ((mt[i-1] < 0) ? 0x80000000l : 0x0l); + long l = (l0 ^ ((l1 ^ (l1 >> 30)) * 1664525l)) + seed[j] + j; // non linear + mt[i] = (int) (l & 0xffffffffl); + i++; j++; + if (i >= N) { + mt[0] = mt[N - 1]; + i = 1; + } + if (j >= seed.length) { + j = 0; + } + } + + for (int k = N - 1; k != 0; k--) { + long l0 = (mt[i] & 0x7fffffffl) | ((mt[i] < 0) ? 0x80000000l : 0x0l); + long l1 = (mt[i-1] & 0x7fffffffl) | ((mt[i-1] < 0) ? 0x80000000l : 0x0l); + long l = (l0 ^ ((l1 ^ (l1 >> 30)) * 1566083941l)) - i; // non linear + mt[i] = (int) (l & 0xffffffffL); + i++; + if (i >= N) { + mt[0] = mt[N - 1]; + i = 1; + } + } + + mt[0] = 0x80000000; // MSB is 1; assuring non-zero initial array + + clear(); // Clear normal deviate cache + + } + + /** Reinitialize the generator as if just built with the given long seed. + *

        The state of the generator is exactly the same as a new + * generator built with the same seed.

        + * @param seed the initial seed (64 bits integer) + */ + @Override + public void setSeed(long seed) { + setSeed(new int[] { (int) (seed >>> 32), (int) (seed & 0xffffffffl) }); + } + + /** Generate next pseudorandom number. + *

        This method is the core generation algorithm. It is used by all the + * public generation methods for the various primitive types {@link + * #nextBoolean()}, {@link #nextBytes(byte[])}, {@link #nextDouble()}, + * {@link #nextFloat()}, {@link #nextGaussian()}, {@link #nextInt()}, + * {@link #next(int)} and {@link #nextLong()}.

        + * @param bits number of random bits to produce + * @return random bits generated + */ + @Override + protected int next(int bits) { + + int y; + + if (mti >= N) { // generate N words at one time + int mtNext = mt[0]; + for (int k = 0; k < N - M; ++k) { + int mtCurr = mtNext; + mtNext = mt[k + 1]; + y = (mtCurr & 0x80000000) | (mtNext & 0x7fffffff); + mt[k] = mt[k + M] ^ (y >>> 1) ^ MAG01[y & 0x1]; + } + for (int k = N - M; k < N - 1; ++k) { + int mtCurr = mtNext; + mtNext = mt[k + 1]; + y = (mtCurr & 0x80000000) | (mtNext & 0x7fffffff); + mt[k] = mt[k + (M - N)] ^ (y >>> 1) ^ MAG01[y & 0x1]; + } + y = (mtNext & 0x80000000) | (mt[0] & 0x7fffffff); + mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ MAG01[y & 0x1]; + + mti = 0; + } + + y = mt[mti++]; + + // tempering + y ^= y >>> 11; + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= y >>> 18; + + return y >>> (32 - bits); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/NormalizedRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/NormalizedRandomGenerator.java new file mode 100644 index 000000000..8f20492a7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/NormalizedRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +/** + * This interface represent a normalized random generator for + * scalars. + * Normalized generator provide null mean and unit standard deviation scalars. + * @since 1.2 + */ +public interface NormalizedRandomGenerator { + + /** Generate a random scalar with null mean and unit standard deviation. + *

        This method does not specify the shape of the + * distribution, it is the implementing class that provides it. The + * only contract here is to generate numbers with null mean and unit + * standard deviation.

        + * @return a random scalar with null mean and unit standard deviation + */ + double nextNormalizedDouble(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomAdaptor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomAdaptor.java new file mode 100644 index 000000000..d974eaa93 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomAdaptor.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 com.fr.third.org.apache.commons.math3.random; + +import java.util.Random; + +/** + * Extension of java.util.Random wrapping a + * {@link RandomGenerator}. + * + * @since 1.1 + */ +public class RandomAdaptor extends Random implements RandomGenerator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 2306581345647615033L; + + /** Wrapped randomGenerator instance */ + private final RandomGenerator randomGenerator; + + /** + * Prevent instantiation without a generator argument + */ + @SuppressWarnings("unused") + private RandomAdaptor() { randomGenerator = null; } + + /** + * Construct a RandomAdaptor wrapping the supplied RandomGenerator. + * + * @param randomGenerator the wrapped generator + */ + public RandomAdaptor(RandomGenerator randomGenerator) { + this.randomGenerator = randomGenerator; + } + + /** + * Factory method to create a Random using the supplied + * RandomGenerator. + * + * @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 + * boolean value from this random number generator's + * sequence. + * + * @return the next pseudorandom, uniformly distributed + * boolean 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 + * double value between 0.0 and + * 1.0 from this random number generator's sequence. + * + * @return the next pseudorandom, uniformly distributed + * double value between 0.0 and + * 1.0 from this random number generator's sequence + */ + @Override + public double nextDouble() { + return randomGenerator.nextDouble(); + } + + /** + * Returns the next pseudorandom, uniformly distributed float + * value between 0.0 and 1.0 from this random + * number generator's sequence. + * + * @return the next pseudorandom, uniformly distributed float + * value between 0.0 and 1.0 from this + * random number generator's sequence + */ + @Override + public float nextFloat() { + return randomGenerator.nextFloat(); + } + + /** + * Returns the next pseudorandom, Gaussian ("normally") distributed + * double value with mean 0.0 and standard + * deviation 1.0 from this random number generator's sequence. + * + * @return the next pseudorandom, Gaussian ("normally") distributed + * double value with mean 0.0 and + * standard deviation 1.0 from this random number + * generator's sequence + */ + @Override + public double nextGaussian() { + return randomGenerator.nextGaussian(); + } + + /** + * Returns the next pseudorandom, uniformly distributed int + * value from this random number generator's sequence. + * All 232 possible {@code int} values + * should be produced with (approximately) equal probability. + * + * @return the next pseudorandom, uniformly distributed int + * value from this random number generator's sequence + */ + @Override + public int nextInt() { + return randomGenerator.nextInt(); + } + + /** + * Returns a pseudorandom, uniformly distributed {@code int} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed {@code int} + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + @Override + public int nextInt(int n) { + return randomGenerator.nextInt(n); + } + + /** + * Returns the next pseudorandom, uniformly distributed long + * value from this random number generator's sequence. All + * 264 possible {@code long} values + * should be produced with (approximately) equal probability. + * + * @return the next pseudorandom, uniformly distributed long + *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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomData.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomData.java new file mode 100644 index 000000000..93674af4e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomData.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.random; +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; + +/** + * Random data generation utilities. + * @deprecated to be removed in 4.0. Use {@link RandomDataGenerator} directly + */ +@Deprecated +public interface RandomData { + /** + * Generates a random string of hex characters of length {@code len}. + *

        + * The generated string will be random, but not cryptographically + * secure. To generate cryptographically secure strings, use + * {@link #nextSecureHexString(int)}. + *

        + * + * @param len the length of the string to be generated + * @return a random string of hex characters of length {@code len} + * @throws NotStrictlyPositiveException + * if {@code len <= 0} + */ + String nextHexString(int len) throws NotStrictlyPositiveException; + + /** + * Generates a uniformly distributed random integer between {@code lower} + * and {@code upper} (endpoints included). + *

        + * The generated integer will be random, but not cryptographically secure. + * To generate cryptographically secure integer sequences, use + * {@link #nextSecureInt(int, int)}. + *

        + * + * @param lower lower bound for generated integer + * @param upper upper bound for generated integer + * @return a random integer greater than or equal to {@code lower} + * and less than or equal to {@code upper} + * @throws NumberIsTooLargeException if {@code lower >= upper} + */ + int nextInt(int lower, int upper) throws NumberIsTooLargeException; + + /** + * Generates a uniformly distributed random long integer between + * {@code lower} and {@code upper} (endpoints included). + *

        + * The generated long integer values will be random, but not + * cryptographically secure. To generate cryptographically secure sequences + * of longs, use {@link #nextSecureLong(long, long)}. + *

        + * + * @param lower lower bound for generated long integer + * @param upper upper bound for generated long integer + * @return a random long integer greater than or equal to {@code lower} and + * less than or equal to {@code upper} + * @throws NumberIsTooLargeException if {@code lower >= upper} + */ + long nextLong(long lower, long upper) throws NumberIsTooLargeException; + + /** + * Generates a random string of hex characters from a secure random + * sequence. + *

        + * If cryptographic security is not required, use + * {@link #nextHexString(int)}. + *

        + * + * @param len the length of the string to be generated + * @return a random string of hex characters of length {@code len} + * @throws NotStrictlyPositiveException if {@code len <= 0} + */ + String nextSecureHexString(int len) throws NotStrictlyPositiveException; + + /** + * Generates a uniformly distributed random integer between {@code lower} + * and {@code upper} (endpoints included) from a secure random sequence. + *

        + * Sequences of integers generated using this method will be + * cryptographically secure. If cryptographic security is not required, + * {@link #nextInt(int, int)} should be used instead of this method.

        + *

        + * Definition: + * + * Secure Random Sequence

        + * + * @param lower lower bound for generated integer + * @param upper upper bound for generated integer + * @return a random integer greater than or equal to {@code lower} and less + * than or equal to {@code upper}. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + int nextSecureInt(int lower, int upper) throws NumberIsTooLargeException; + + /** + * Generates a uniformly distributed random long integer between + * {@code lower} and {@code upper} (endpoints included) from a secure random + * sequence. + *

        + * Sequences of long values generated using this method will be + * cryptographically secure. If cryptographic security is not required, + * {@link #nextLong(long, long)} should be used instead of this method.

        + *

        + * Definition: + * + * Secure Random Sequence

        + * + * @param lower lower bound for generated integer + * @param upper upper bound for generated integer + * @return a random long integer greater than or equal to {@code lower} and + * less than or equal to {@code upper}. + * @throws NumberIsTooLargeException if {@code lower >= upper}. + */ + long nextSecureLong(long lower, long upper) throws NumberIsTooLargeException; + + /** + * Generates a random value from the Poisson distribution with the given + * mean. + *

        + * Definition: + * + * Poisson Distribution

        + * + * @param mean the mean of the Poisson distribution + * @return a random value following the specified Poisson distribution + * @throws NotStrictlyPositiveException if {@code mean <= 0}. + */ + long nextPoisson(double mean) throws NotStrictlyPositiveException; + + /** + * Generates a random value from the Normal (or Gaussian) distribution with + * specified mean and standard deviation. + *

        + * Definition: + * + * Normal Distribution

        + * + * @param mu the mean of the distribution + * @param sigma the standard deviation of the distribution + * @return a random value following the specified Gaussian distribution + * @throws NotStrictlyPositiveException if {@code sigma <= 0}. + */ + double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException; + + /** + * Generates a random value from the exponential distribution + * with specified mean. + *

        + * Definition: + * + * Exponential Distribution

        + * + * @param mean the mean of the distribution + * @return a random value following the specified exponential distribution + * @throws NotStrictlyPositiveException if {@code mean <= 0}. + */ + double nextExponential(double mean) throws NotStrictlyPositiveException; + + /** + * Generates a uniformly distributed random value from the open interval + * {@code (lower, upper)} (i.e., endpoints excluded). + *

        + * Definition: + * + * Uniform Distribution {@code lower} and {@code upper - lower} are the + * + * location and scale parameters, respectively.

        + * + * @param lower the exclusive lower bound of the support + * @param upper the exclusive upper bound of the support + * @return a uniformly distributed random value between lower and upper + * (exclusive) + * @throws NumberIsTooLargeException if {@code lower >= upper} + * @throws NotFiniteNumberException if one of the bounds is infinite + * @throws NotANumberException if one of the bounds is NaN + */ + double nextUniform(double lower, double upper) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException; + + /** + * Generates a uniformly distributed random value from the interval + * {@code (lower, upper)} or the interval {@code [lower, upper)}. The lower + * bound is thus optionally included, while the upper bound is always + * excluded. + *

        + * Definition: + * + * Uniform Distribution {@code lower} and {@code upper - lower} are the + * + * location and scale parameters, respectively.

        + * + * @param lower the lower bound of the support + * @param upper the exclusive upper bound of the support + * @param lowerInclusive {@code true} if the lower bound is inclusive + * @return uniformly distributed random value in the {@code (lower, upper)} + * interval, if {@code lowerInclusive} is {@code false}, or in the + * {@code [lower, upper)} interval, if {@code lowerInclusive} is + * {@code true} + * @throws NumberIsTooLargeException if {@code lower >= upper} + * @throws NotFiniteNumberException if one of the bounds is infinite + * @throws NotANumberException if one of the bounds is NaN + */ + double nextUniform(double lower, double upper, boolean lowerInclusive) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException; + + /** + * Generates an integer array of length {@code k} whose entries are selected + * randomly, without repetition, from the integers {@code 0, ..., n - 1} + * (inclusive). + *

        + * Generated arrays represent permutations of {@code n} taken {@code k} at a + * time.

        + * + * @param n the domain of the permutation + * @param k the size of the permutation + * @return a random {@code k}-permutation of {@code n}, as an array of + * integers + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws NotStrictlyPositiveException if {@code k <= 0}. + */ + int[] nextPermutation(int n, int k) + throws NumberIsTooLargeException, NotStrictlyPositiveException; + + /** + * Returns an array of {@code k} objects selected randomly from the + * Collection {@code c}. + *

        + * Sampling from {@code c} is without replacement; but if {@code c} contains + * identical objects, the sample may include repeats. If all elements of + * {@code c} are distinct, the resulting object array represents a + * + * Simple Random Sample of size {@code k} from the elements of + * {@code c}.

        + * + * @param c the collection to be sampled + * @param k the size of the sample + * @return a random sample of {@code k} elements from {@code c} + * @throws NumberIsTooLargeException if {@code k > c.size()}. + * @throws NotStrictlyPositiveException if {@code k <= 0}. + */ + Object[] nextSample(Collection c, int k) + throws NumberIsTooLargeException, NotStrictlyPositiveException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataGenerator.java new file mode 100644 index 000000000..b540e3e5b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataGenerator.java @@ -0,0 +1,782 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.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 com.fr.third.org.apache.commons.math3.distribution.BetaDistribution; +import com.fr.third.org.apache.commons.math3.distribution.BinomialDistribution; +import com.fr.third.org.apache.commons.math3.distribution.CauchyDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ChiSquaredDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ExponentialDistribution; +import com.fr.third.org.apache.commons.math3.distribution.FDistribution; +import com.fr.third.org.apache.commons.math3.distribution.GammaDistribution; +import com.fr.third.org.apache.commons.math3.distribution.HypergeometricDistribution; +import com.fr.third.org.apache.commons.math3.distribution.PascalDistribution; +import com.fr.third.org.apache.commons.math3.distribution.PoissonDistribution; +import com.fr.third.org.apache.commons.math3.distribution.TDistribution; +import com.fr.third.org.apache.commons.math3.distribution.UniformIntegerDistribution; +import com.fr.third.org.apache.commons.math3.distribution.WeibullDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ZipfDistribution; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * 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 nextSecureXxx methods. If no + * RandomGenerator is provided in the constructor, the default is + * to use a {@link Well19937c} generator. To plug in a different + * implementation, either implement RandomGenerator directly or + * extend {@link AbstractRandomGenerator}. + *

        + * Supports reseeding the underlying pseudo-random number generator (PRNG). The + * SecurityProvider and Algorithm used by the + * SecureRandom instance can also be reset. + *

        + *

        + * For details on the default PRNGs, see {@link java.util.Random} and + * {@link java.security.SecureRandom}. + *

        + *

        + * Usage Notes: + *

          + *
        • + * Instance variables are used to maintain RandomGenerator and + * SecureRandom instances used in data generation. Therefore, to + * generate a random sequence of values or strings, you should use just + * one RandomDataImpl instance repeatedly.
        • + *
        • + * 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.
        • + *
        • + * When a new RandomDataImpl is created, the underlying random + * number generators are not initialized. If you do not + * explicitly seed the default non-secure generator, it is seeded with the + * current time in milliseconds plus the system identity hash code on first use. + * The same holds for the secure generator. If you provide a RandomGenerator + * to the constructor, however, this generator is not reseeded by the constructor + * nor is it reseeded on first use.
        • + *
        • + * The reSeed and reSeedSecure methods delegate to the + * corresponding methods on the underlying RandomGenerator and + * SecureRandom instances. Therefore, reSeed(long) + * 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 not + * reinitialize the secure random number generator (so secure sequences started + * with calls to reseedSecure(long) won't be identical).
        • + *
        • + * This implementation is not synchronized. The underlying RandomGenerator + * or SecureRandom instances are not protected by synchronization and + * are not guaranteed to be thread-safe. Therefore, if an instance of this class + * is concurrently utilized by multiple threads, it is the responsibility of + * client code to synchronize access to seeding and data generation methods. + *
        • + *
        + *

        + * @since 3.1 + */ +public class RandomDataGenerator implements RandomData, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -626730818244969716L; + + /** underlying random number generator */ + private RandomGenerator rand = null; + + /** underlying secure random number generator */ + private RandomGenerator secRand = null; + + /** + * Construct a RandomDataGenerator, using a default random generator as the source + * of randomness. + * + *

        The default generator is a {@link Well19937c} seeded + * with {@code System.currentTimeMillis() + System.identityHashCode(this))}. + * The generator is initialized and seeded on first use.

        + */ + public RandomDataGenerator() { + } + + /** + * Construct a RandomDataGenerator using the supplied {@link RandomGenerator} as + * the source of (non-secure) random data. + * + * @param rand the source of (non-secure) random data + * (may be null, resulting in the default generator) + */ + public RandomDataGenerator(RandomGenerator rand) { + this.rand = rand; + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: hex strings are generated using a + * 2-step process. + *

          + *
        1. {@code len / 2 + 1} binary bytes are generated using the underlying + * Random
        2. + *
        3. Each binary byte is translated into 2 hex digits
        4. + *
        + *

        + * + * @param len the desired string length. + * @return the random string. + * @throws NotStrictlyPositiveException if {@code len <= 0}. + */ + public String nextHexString(int len) throws NotStrictlyPositiveException { + if (len <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len); + } + + // Get a random number generator + RandomGenerator ran = getRandomGenerator(); + + // Initialize output buffer + StringBuilder outBuffer = new StringBuilder(); + + // Get int(len/2)+1 random bytes + byte[] randomBytes = new byte[(len / 2) + 1]; + ran.nextBytes(randomBytes); + + // Convert each byte to 2 hex digits + for (int i = 0; i < randomBytes.length; i++) { + Integer c = Integer.valueOf(randomBytes[i]); + + /* + * Add 128 to byte value to make interval 0-255 before doing hex + * conversion. This guarantees <= 2 hex digits from toHexString() + * toHexString would otherwise add 2^32 to negative arguments. + */ + String hex = Integer.toHexString(c.intValue() + 128); + + // Make sure we add 2 hex digits for each byte + if (hex.length() == 1) { + hex = "0" + hex; + } + outBuffer.append(hex); + } + return outBuffer.toString().substring(0, len); + } + + /** {@inheritDoc} */ + public int nextInt(final int lower, final int upper) throws NumberIsTooLargeException { + return new UniformIntegerDistribution(getRandomGenerator(), lower, upper).sample(); + } + + /** {@inheritDoc} */ + public long nextLong(final long lower, final long upper) throws NumberIsTooLargeException { + if (lower >= upper) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + lower, upper, false); + } + final long max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive long (larger than 2^63); as it covers + // more than half the long range, we use directly a simple rejection method + final RandomGenerator rng = getRandomGenerator(); + while (true) { + final long r = rng.nextLong(); + if (r >= lower && r <= upper) { + return r; + } + } + } else if (max < Integer.MAX_VALUE){ + // we can shift the range and generate directly a positive int + return lower + getRandomGenerator().nextInt((int) max); + } else { + // we can shift the range and generate directly a positive long + return lower + nextLong(getRandomGenerator(), max); + } + } + + /** + * Returns a pseudorandom, uniformly distributed {@code long} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param rng random generator to use + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed {@code long} + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + private static long nextLong(final RandomGenerator rng, final long n) throws IllegalArgumentException { + if (n > 0) { + final byte[] byteArray = new byte[8]; + long bits; + long val; + do { + rng.nextBytes(byteArray); + bits = 0; + for (final byte b : byteArray) { + bits = (bits << 8) | (((long) b) & 0xffL); + } + bits &= 0x7fffffffffffffffL; + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: hex strings are generated in + * 40-byte segments using a 3-step process. + *

          + *
        1. + * 20 random bytes are generated using the underlying + * SecureRandom.
        2. + *
        3. + * SHA-1 hash is applied to yield a 20-byte binary digest.
        4. + *
        5. + * Each byte of the binary digest is converted to 2 hex digits.
        6. + *
        + *

        + * @throws NotStrictlyPositiveException if {@code len <= 0} + */ + public String nextSecureHexString(int len) throws NotStrictlyPositiveException { + if (len <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len); + } + + // Get SecureRandom and setup Digest provider + final RandomGenerator secRan = getSecRan(); + MessageDigest alg = null; + try { + alg = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + // this should never happen + throw new MathInternalError(ex); + } + alg.reset(); + + // Compute number of iterations required (40 bytes each) + int numIter = (len / 40) + 1; + + StringBuilder outBuffer = new StringBuilder(); + for (int iter = 1; iter < numIter + 1; iter++) { + byte[] randomBytes = new byte[40]; + secRan.nextBytes(randomBytes); + alg.update(randomBytes); + + // Compute hash -- will create 20-byte binary hash + byte[] hash = alg.digest(); + + // Loop over the hash, converting each byte to 2 hex digits + for (int i = 0; i < hash.length; i++) { + Integer c = Integer.valueOf(hash[i]); + + /* + * Add 128 to byte value to make interval 0-255 This guarantees + * <= 2 hex digits from toHexString() toHexString would + * otherwise add 2^32 to negative arguments + */ + String hex = Integer.toHexString(c.intValue() + 128); + + // Keep strings uniform length -- guarantees 40 bytes + if (hex.length() == 1) { + hex = "0" + hex; + } + outBuffer.append(hex); + } + } + return outBuffer.toString().substring(0, len); + } + + /** {@inheritDoc} */ + public int nextSecureInt(final int lower, final int upper) throws NumberIsTooLargeException { + return new UniformIntegerDistribution(getSecRan(), lower, upper).sample(); + } + + /** {@inheritDoc} */ + public long nextSecureLong(final long lower, final long upper) throws NumberIsTooLargeException { + if (lower >= upper) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + lower, upper, false); + } + final RandomGenerator rng = getSecRan(); + final long max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive long (larger than 2^63); as it covers + // more than half the long range, we use directly a simple rejection method + while (true) { + final long r = rng.nextLong(); + if (r >= lower && r <= upper) { + return r; + } + } + } else if (max < Integer.MAX_VALUE){ + // we can shift the range and generate directly a positive int + return lower + rng.nextInt((int) max); + } else { + // we can shift the range and generate directly a positive long + return lower + nextLong(rng, max); + } + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: + *

        • For small means, uses simulation of a Poisson process + * using Uniform deviates, as described + * here. + * The Poisson process (and hence value returned) is bounded by 1000 * mean.
        • + * + *
        • For large means, uses the rejection algorithm described in
          + * Devroye, Luc. (1981).The Computer Generation of Poisson Random Variables + * Computing vol. 26 pp. 197-207.

        + * @throws NotStrictlyPositiveException if {@code len <= 0} + */ + public long nextPoisson(double mean) throws NotStrictlyPositiveException { + return new PoissonDistribution(getRandomGenerator(), mean, + PoissonDistribution.DEFAULT_EPSILON, + PoissonDistribution.DEFAULT_MAX_ITERATIONS).sample(); + } + + /** {@inheritDoc} */ + public double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException { + if (sigma <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.STANDARD_DEVIATION, sigma); + } + return sigma * getRandomGenerator().nextGaussian() + mu; + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: Uses the Algorithm SA (Ahrens) + * from p. 876 in: + * [1]: Ahrens, J. H. and Dieter, U. (1972). Computer methods for + * sampling from the exponential and normal distributions. + * Communications of the ACM, 15, 873-882. + *

        + */ + public double nextExponential(double mean) throws NotStrictlyPositiveException { + return new ExponentialDistribution(getRandomGenerator(), mean, + ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + *

        Generates a random value from the + * {@link GammaDistribution Gamma Distribution}.

        + * + *

        This implementation uses the following algorithms:

        + * + *

        For 0 < shape < 1:
        + * Ahrens, J. H. and Dieter, U., Computer methods for + * sampling from gamma, beta, Poisson and binomial distributions. + * Computing, 12, 223-246, 1974.

        + * + *

        For shape >= 1:
        + * Marsaglia and Tsang, A Simple Method for Generating + * Gamma Variables. ACM Transactions on Mathematical Software, + * Volume 26 Issue 3, September, 2000.

        + * + * @param shape the median of the Gamma distribution + * @param scale the scale parameter of the Gamma distribution + * @return random value sampled from the Gamma(shape, scale) distribution + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + */ + public double nextGamma(double shape, double scale) throws NotStrictlyPositiveException { + return new GammaDistribution(getRandomGenerator(),shape, scale, + GammaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link HypergeometricDistribution Hypergeometric Distribution}. + * + * @param populationSize the population size of the Hypergeometric distribution + * @param numberOfSuccesses number of successes in the population of the Hypergeometric distribution + * @param sampleSize the sample size of the Hypergeometric distribution + * @return random value sampled from the Hypergeometric(numberOfSuccesses, sampleSize) distribution + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, + * or {@code sampleSize > populationSize}. + * @throws NotStrictlyPositiveException if {@code populationSize <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + */ + public int nextHypergeometric(int populationSize, int numberOfSuccesses, int sampleSize) throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException { + return new HypergeometricDistribution(getRandomGenerator(),populationSize, + numberOfSuccesses, sampleSize).sample(); + } + + /** + * Generates a random value from the {@link PascalDistribution Pascal Distribution}. + * + * @param r the number of successes of the Pascal distribution + * @param p the probability of success of the Pascal distribution + * @return random value sampled from the Pascal(r, p) distribution + * @throws NotStrictlyPositiveException if the number of successes is not positive + * @throws OutOfRangeException if the probability of success is not in the + * range {@code [0, 1]}. + */ + public int nextPascal(int r, double p) throws NotStrictlyPositiveException, OutOfRangeException { + return new PascalDistribution(getRandomGenerator(), r, p).sample(); + } + + /** + * Generates a random value from the {@link TDistribution T Distribution}. + * + * @param df the degrees of freedom of the T distribution + * @return random value from the T(df) distribution + * @throws NotStrictlyPositiveException if {@code df <= 0} + */ + public double nextT(double df) throws NotStrictlyPositiveException { + return new TDistribution(getRandomGenerator(), df, + TDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link WeibullDistribution Weibull Distribution}. + * + * @param shape the shape parameter of the Weibull distribution + * @param scale the scale parameter of the Weibull distribution + * @return random value sampled from the Weibull(shape, size) distribution + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + */ + public double nextWeibull(double shape, double scale) throws NotStrictlyPositiveException { + return new WeibullDistribution(getRandomGenerator(), shape, scale, + WeibullDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link ZipfDistribution Zipf Distribution}. + * + * @param numberOfElements the number of elements of the ZipfDistribution + * @param exponent the exponent of the ZipfDistribution + * @return random value sampled from the Zipf(numberOfElements, exponent) distribution + * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} + * or {@code exponent <= 0}. + */ + public int nextZipf(int numberOfElements, double exponent) throws NotStrictlyPositiveException { + return new ZipfDistribution(getRandomGenerator(), numberOfElements, exponent).sample(); + } + + /** + * Generates a random value from the {@link BetaDistribution Beta Distribution}. + * + * @param alpha first distribution shape parameter + * @param beta second distribution shape parameter + * @return random value sampled from the beta(alpha, beta) distribution + */ + public double nextBeta(double alpha, double beta) { + return new BetaDistribution(getRandomGenerator(), alpha, beta, + BetaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link BinomialDistribution Binomial Distribution}. + * + * @param numberOfTrials number of trials of the Binomial distribution + * @param probabilityOfSuccess probability of success of the Binomial distribution + * @return random value sampled from the Binomial(numberOfTrials, probabilityOfSuccess) distribution + */ + public int nextBinomial(int numberOfTrials, double probabilityOfSuccess) { + return new BinomialDistribution(getRandomGenerator(), numberOfTrials, probabilityOfSuccess).sample(); + } + + /** + * Generates a random value from the {@link CauchyDistribution Cauchy Distribution}. + * + * @param median the median of the Cauchy distribution + * @param scale the scale parameter of the Cauchy distribution + * @return random value sampled from the Cauchy(median, scale) distribution + */ + public double nextCauchy(double median, double scale) { + return new CauchyDistribution(getRandomGenerator(), median, scale, + CauchyDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link ChiSquaredDistribution ChiSquare Distribution}. + * + * @param df the degrees of freedom of the ChiSquare distribution + * @return random value sampled from the ChiSquare(df) distribution + */ + public double nextChiSquare(double df) { + return new ChiSquaredDistribution(getRandomGenerator(), df, + ChiSquaredDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * Generates a random value from the {@link FDistribution F Distribution}. + * + * @param numeratorDf the numerator degrees of freedom of the F distribution + * @param denominatorDf the denominator degrees of freedom of the F distribution + * @return random value sampled from the F(numeratorDf, denominatorDf) distribution + * @throws NotStrictlyPositiveException if + * {@code numeratorDf <= 0} or {@code denominatorDf <= 0}. + */ + public double nextF(double numeratorDf, double denominatorDf) throws NotStrictlyPositiveException { + return new FDistribution(getRandomGenerator(), numeratorDf, denominatorDf, + FDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample(); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: scales the output of + * Random.nextDouble(), but rejects 0 values (i.e., will generate another + * random double if Random.nextDouble() returns 0). This is necessary to + * provide a symmetric output interval (both endpoints excluded). + *

        + * @throws NumberIsTooLargeException if {@code lower >= upper} + * @throws NotFiniteNumberException if one of the bounds is infinite + * @throws NotANumberException if one of the bounds is NaN + */ + public double nextUniform(double lower, double upper) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException { + return nextUniform(lower, upper, false); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: if the lower bound is excluded, + * scales the output of Random.nextDouble(), but rejects 0 values (i.e., + * will generate another random double if Random.nextDouble() returns 0). + * This is necessary to provide a symmetric output interval (both + * endpoints excluded). + *

        + * + * @throws NumberIsTooLargeException if {@code lower >= upper} + * @throws NotFiniteNumberException if one of the bounds is infinite + * @throws NotANumberException if one of the bounds is NaN + */ + public double nextUniform(double lower, double upper, boolean lowerInclusive) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException { + + if (lower >= upper) { + throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, + lower, upper, false); + } + + if (Double.isInfinite(lower)) { + throw new NotFiniteNumberException(LocalizedFormats.INFINITE_BOUND, lower); + } + if (Double.isInfinite(upper)) { + throw new NotFiniteNumberException(LocalizedFormats.INFINITE_BOUND, upper); + } + + if (Double.isNaN(lower) || Double.isNaN(upper)) { + throw new NotANumberException(); + } + + final RandomGenerator generator = getRandomGenerator(); + + // ensure nextDouble() isn't 0.0 + double u = generator.nextDouble(); + while (!lowerInclusive && u <= 0.0) { + u = generator.nextDouble(); + } + + return u * upper + (1.0 - u) * lower; + } + + /** + * {@inheritDoc} + * + * This method calls {@link MathArrays#shuffle(int[],RandomGenerator) + * MathArrays.shuffle} in order to create a random shuffle of the set + * of natural numbers {@code { 0, 1, ..., n - 1 }}. + * + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws NotStrictlyPositiveException if {@code k <= 0}. + */ + public int[] nextPermutation(int n, int k) + throws NumberIsTooLargeException, NotStrictlyPositiveException { + if (k > n) { + throw new NumberIsTooLargeException(LocalizedFormats.PERMUTATION_EXCEEDS_N, + k, n, true); + } + if (k <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.PERMUTATION_SIZE, + k); + } + + int[] index = MathArrays.natural(n); + MathArrays.shuffle(index, getRandomGenerator()); + + // Return a new array containing the first "k" entries of "index". + return MathArrays.copyOf(index, k); + } + + /** + * {@inheritDoc} + * + * This method calls {@link #nextPermutation(int,int) nextPermutation(c.size(), k)} + * in order to sample the collection. + */ + public Object[] nextSample(Collection c, int k) throws NumberIsTooLargeException, NotStrictlyPositiveException { + + int len = c.size(); + if (k > len) { + throw new NumberIsTooLargeException(LocalizedFormats.SAMPLE_SIZE_EXCEEDS_COLLECTION_SIZE, + k, len, true); + } + if (k <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, k); + } + + Object[] objects = c.toArray(); + int[] index = nextPermutation(len, k); + Object[] result = new Object[k]; + for (int i = 0; i < k; i++) { + result[i] = objects[index[i]]; + } + return result; + } + + + + /** + * Reseeds the random number generator with the supplied seed. + *

        + * Will create and initialize if null. + *

        + * + * @param seed the seed value to use + */ + public void reSeed(long seed) { + getRandomGenerator().setSeed(seed); + } + + /** + * Reseeds the secure random number generator with the current time in + * milliseconds. + *

        + * Will create and initialize if null. + *

        + */ + public void reSeedSecure() { + getSecRan().setSeed(System.currentTimeMillis()); + } + + /** + * Reseeds the secure random number generator with the supplied seed. + *

        + * Will create and initialize if null. + *

        + * + * @param seed the seed value to use + */ + public void reSeedSecure(long seed) { + getSecRan().setSeed(seed); + } + + /** + * Reseeds the random number generator with + * {@code System.currentTimeMillis() + System.identityHashCode(this))}. + */ + public void reSeed() { + getRandomGenerator().setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + } + + /** + * Sets the PRNG algorithm for the underlying SecureRandom instance using + * the Security Provider API. The Security Provider API is defined in + * Java Cryptography Architecture API Specification & Reference. + *

        + * USAGE NOTE: This method carries significant + * overhead and may take several seconds to execute. + *

        + * + * @param algorithm the name of the PRNG algorithm + * @param provider the name of the provider + * @throws NoSuchAlgorithmException if the specified algorithm is not available + * @throws NoSuchProviderException if the specified provider is not installed + */ + public void setSecureAlgorithm(String algorithm, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + secRand = RandomGeneratorFactory.createRandomGenerator(SecureRandom.getInstance(algorithm, provider)); + } + + /** + * Returns the RandomGenerator used to generate non-secure random data. + *

        + * Creates and initializes a default generator if null. Uses a {@link Well19937c} + * generator with {@code System.currentTimeMillis() + System.identityHashCode(this))} + * as the default seed. + *

        + * + * @return the Random used to generate random data + * @since 3.2 + */ + public RandomGenerator getRandomGenerator() { + if (rand == null) { + initRan(); + } + return rand; + } + + /** + * Sets the default generator to a {@link Well19937c} generator seeded with + * {@code System.currentTimeMillis() + System.identityHashCode(this))}. + */ + private void initRan() { + rand = new Well19937c(System.currentTimeMillis() + System.identityHashCode(this)); + } + + /** + * Returns the SecureRandom used to generate secure random data. + *

        + * Creates and initializes if null. Uses + * {@code System.currentTimeMillis() + System.identityHashCode(this)} as the default seed. + *

        + * + * @return the SecureRandom used to generate secure random data, wrapped in a + * {@link RandomGenerator}. + */ + private RandomGenerator getSecRan() { + if (secRand == null) { + secRand = RandomGeneratorFactory.createRandomGenerator(new SecureRandom()); + secRand.setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + } + return secRand; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataImpl.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataImpl.java new file mode 100644 index 000000000..99c063aa0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomDataImpl.java @@ -0,0 +1,596 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.random; + +import java.io.Serializable; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.distribution.BetaDistribution; +import com.fr.third.org.apache.commons.math3.distribution.BinomialDistribution; +import com.fr.third.org.apache.commons.math3.distribution.CauchyDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ChiSquaredDistribution; +import com.fr.third.org.apache.commons.math3.distribution.FDistribution; +import com.fr.third.org.apache.commons.math3.distribution.GammaDistribution; +import com.fr.third.org.apache.commons.math3.distribution.HypergeometricDistribution; +import com.fr.third.org.apache.commons.math3.distribution.IntegerDistribution; +import com.fr.third.org.apache.commons.math3.distribution.PascalDistribution; +import com.fr.third.org.apache.commons.math3.distribution.RealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.TDistribution; +import com.fr.third.org.apache.commons.math3.distribution.WeibullDistribution; +import com.fr.third.org.apache.commons.math3.distribution.ZipfDistribution; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Generates random deviates and other random data using a {@link RandomGenerator} + * instance to generate non-secure data and a {@link java.security.SecureRandom} + * instance to provide data for the nextSecureXxx methods. If no + * RandomGenerator is provided in the constructor, the default is + * to use a {@link Well19937c} generator. To plug in a different + * implementation, either implement RandomGenerator directly or + * extend {@link AbstractRandomGenerator}. + *

        + * Supports reseeding the underlying pseudo-random number generator (PRNG). The + * SecurityProvider and Algorithm used by the + * SecureRandom instance can also be reset. + *

        + *

        + * For details on the default PRNGs, see {@link java.util.Random} and + * {@link java.security.SecureRandom}. + *

        + *

        + * Usage Notes: + *

          + *
        • + * Instance variables are used to maintain RandomGenerator and + * SecureRandom instances used in data generation. Therefore, to + * generate a random sequence of values or strings, you should use just + * one RandomDataGenerator instance repeatedly.
        • + *
        • + * 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.
        • + *
        • + * When a new RandomDataGenerator is created, the underlying random + * number generators are not initialized. If you do not + * explicitly seed the default non-secure generator, it is seeded with the + * current time in milliseconds plus the system identity hash code on first use. + * The same holds for the secure generator. If you provide a RandomGenerator + * to the constructor, however, this generator is not reseeded by the constructor + * nor is it reseeded on first use.
        • + *
        • + * The reSeed and reSeedSecure methods delegate to the + * corresponding methods on the underlying RandomGenerator and + * SecureRandom instances. Therefore, reSeed(long) + * 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 not + * reinitialize the secure random number generator (so secure sequences started + * with calls to reseedSecure(long) won't be identical).
        • + *
        • + * This implementation is not synchronized. The underlying RandomGenerator + * or SecureRandom instances are not protected by synchronization and + * are not guaranteed to be thread-safe. Therefore, if an instance of this class + * is concurrently utilized by multiple threads, it is the responsibility of + * client code to synchronize access to seeding and data generation methods. + *
        • + *
        + *

        + * @deprecated to be removed in 4.0. Use {@link RandomDataGenerator} instead + */ +@Deprecated +public class RandomDataImpl implements RandomData, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -626730818244969716L; + + /** RandomDataGenerator delegate */ + private final RandomDataGenerator delegate; + + /** + * Construct a RandomDataImpl, using a default random generator as the source + * of randomness. + * + *

        The default generator is a {@link Well19937c} seeded + * with {@code System.currentTimeMillis() + System.identityHashCode(this))}. + * The generator is initialized and seeded on first use.

        + */ + public RandomDataImpl() { + delegate = new RandomDataGenerator(); + } + + /** + * Construct a RandomDataImpl using the supplied {@link RandomGenerator} as + * the source of (non-secure) random data. + * + * @param rand the source of (non-secure) random data + * (may be null, resulting in the default generator) + * @since 1.1 + */ + public RandomDataImpl(RandomGenerator rand) { + delegate = new RandomDataGenerator(rand); + } + + /** + * @return the delegate object. + * @deprecated To be removed in 4.0. + */ + @Deprecated + RandomDataGenerator getDelegate() { + return delegate; + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: hex strings are generated using a + * 2-step process. + *

          + *
        1. {@code len / 2 + 1} binary bytes are generated using the underlying + * Random
        2. + *
        3. Each binary byte is translated into 2 hex digits
        4. + *
        + *

        + * + * @param len the desired string length. + * @return the random string. + * @throws NotStrictlyPositiveException if {@code len <= 0}. + */ + public String nextHexString(int len) throws NotStrictlyPositiveException { + return delegate.nextHexString(len); + } + + /** {@inheritDoc} */ + public int nextInt(int lower, int upper) throws NumberIsTooLargeException { + return delegate.nextInt(lower, upper); + } + + /** {@inheritDoc} */ + public long nextLong(long lower, long upper) throws NumberIsTooLargeException { + return delegate.nextLong(lower, upper); + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: hex strings are generated in + * 40-byte segments using a 3-step process. + *

          + *
        1. + * 20 random bytes are generated using the underlying + * SecureRandom.
        2. + *
        3. + * SHA-1 hash is applied to yield a 20-byte binary digest.
        4. + *
        5. + * Each byte of the binary digest is converted to 2 hex digits.
        6. + *
        + *

        + */ + public String nextSecureHexString(int len) throws NotStrictlyPositiveException { + return delegate.nextSecureHexString(len); + } + + /** {@inheritDoc} */ + public int nextSecureInt(int lower, int upper) throws NumberIsTooLargeException { + return delegate.nextSecureInt(lower, upper); + } + + /** {@inheritDoc} */ + public long nextSecureLong(long lower, long upper) throws NumberIsTooLargeException { + return delegate.nextSecureLong(lower,upper); + } + + /** + * {@inheritDoc} + *

        + * Algorithm Description: + *

        • For small means, uses simulation of a Poisson process + * using Uniform deviates, as described + * here. + * The Poisson process (and hence value returned) is bounded by 1000 * mean.
        • + * + *
        • For large means, uses the rejection algorithm described in
          + * Devroye, Luc. (1981).The Computer Generation of Poisson Random Variables + * Computing vol. 26 pp. 197-207.

        + */ + public long nextPoisson(double mean) throws NotStrictlyPositiveException { + return delegate.nextPoisson(mean); + } + + /** {@inheritDoc} */ + public double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException { + return delegate.nextGaussian(mu,sigma); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: Uses the Algorithm SA (Ahrens) + * from p. 876 in: + * [1]: Ahrens, J. H. and Dieter, U. (1972). Computer methods for + * sampling from the exponential and normal distributions. + * Communications of the ACM, 15, 873-882. + *

        + */ + public double nextExponential(double mean) throws NotStrictlyPositiveException { + return delegate.nextExponential(mean); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: scales the output of + * Random.nextDouble(), but rejects 0 values (i.e., will generate another + * random double if Random.nextDouble() returns 0). This is necessary to + * provide a symmetric output interval (both endpoints excluded). + *

        + */ + public double nextUniform(double lower, double upper) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException { + return delegate.nextUniform(lower, upper); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: if the lower bound is excluded, + * scales the output of Random.nextDouble(), but rejects 0 values (i.e., + * will generate another random double if Random.nextDouble() returns 0). + * This is necessary to provide a symmetric output interval (both + * endpoints excluded). + *

        + * @since 3.0 + */ + public double nextUniform(double lower, double upper, boolean lowerInclusive) + throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException { + return delegate.nextUniform(lower, upper, lowerInclusive); + } + + /** + * Generates a random value from the {@link BetaDistribution Beta Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param alpha first distribution shape parameter + * @param beta second distribution shape parameter + * @return random value sampled from the beta(alpha, beta) distribution + * @since 2.2 + */ + public double nextBeta(double alpha, double beta) { + return delegate.nextBeta(alpha, beta); + } + + /** + * Generates a random value from the {@link BinomialDistribution Binomial Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param numberOfTrials number of trials of the Binomial distribution + * @param probabilityOfSuccess probability of success of the Binomial distribution + * @return random value sampled from the Binomial(numberOfTrials, probabilityOfSuccess) distribution + * @since 2.2 + */ + public int nextBinomial(int numberOfTrials, double probabilityOfSuccess) { + return delegate.nextBinomial(numberOfTrials, probabilityOfSuccess); + } + + /** + * Generates a random value from the {@link CauchyDistribution Cauchy Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param median the median of the Cauchy distribution + * @param scale the scale parameter of the Cauchy distribution + * @return random value sampled from the Cauchy(median, scale) distribution + * @since 2.2 + */ + public double nextCauchy(double median, double scale) { + return delegate.nextCauchy(median, scale); + } + + /** + * Generates a random value from the {@link ChiSquaredDistribution ChiSquare Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param df the degrees of freedom of the ChiSquare distribution + * @return random value sampled from the ChiSquare(df) distribution + * @since 2.2 + */ + public double nextChiSquare(double df) { + return delegate.nextChiSquare(df); + } + + /** + * Generates a random value from the {@link FDistribution F Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param numeratorDf the numerator degrees of freedom of the F distribution + * @param denominatorDf the denominator degrees of freedom of the F distribution + * @return random value sampled from the F(numeratorDf, denominatorDf) distribution + * @throws NotStrictlyPositiveException if + * {@code numeratorDf <= 0} or {@code denominatorDf <= 0}. + * @since 2.2 + */ + public double nextF(double numeratorDf, double denominatorDf) throws NotStrictlyPositiveException { + return delegate.nextF(numeratorDf, denominatorDf); + } + + /** + *

        Generates a random value from the + * {@link GammaDistribution Gamma Distribution}.

        + * + *

        This implementation uses the following algorithms:

        + * + *

        For 0 < shape < 1:
        + * Ahrens, J. H. and Dieter, U., Computer methods for + * sampling from gamma, beta, Poisson and binomial distributions. + * Computing, 12, 223-246, 1974.

        + * + *

        For shape >= 1:
        + * Marsaglia and Tsang, A Simple Method for Generating + * Gamma Variables. ACM Transactions on Mathematical Software, + * Volume 26 Issue 3, September, 2000.

        + * + * @param shape the median of the Gamma distribution + * @param scale the scale parameter of the Gamma distribution + * @return random value sampled from the Gamma(shape, scale) distribution + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + * @since 2.2 + */ + public double nextGamma(double shape, double scale) throws NotStrictlyPositiveException { + return delegate.nextGamma(shape, scale); + } + + /** + * Generates a random value from the {@link HypergeometricDistribution Hypergeometric Distribution}. + * This implementation uses {@link #nextInversionDeviate(IntegerDistribution) inversion} + * to generate random values. + * + * @param populationSize the population size of the Hypergeometric distribution + * @param numberOfSuccesses number of successes in the population of the Hypergeometric distribution + * @param sampleSize the sample size of the Hypergeometric distribution + * @return random value sampled from the Hypergeometric(numberOfSuccesses, sampleSize) distribution + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, + * or {@code sampleSize > populationSize}. + * @throws NotStrictlyPositiveException if {@code populationSize <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @since 2.2 + */ + public int nextHypergeometric(int populationSize, int numberOfSuccesses, int sampleSize) + throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException { + return delegate.nextHypergeometric(populationSize, numberOfSuccesses, sampleSize); + } + + /** + * Generates a random value from the {@link PascalDistribution Pascal Distribution}. + * This implementation uses {@link #nextInversionDeviate(IntegerDistribution) inversion} + * to generate random values. + * + * @param r the number of successes of the Pascal distribution + * @param p the probability of success of the Pascal distribution + * @return random value sampled from the Pascal(r, p) distribution + * @since 2.2 + * @throws NotStrictlyPositiveException if the number of successes is not positive + * @throws OutOfRangeException if the probability of success is not in the + * range {@code [0, 1]}. + */ + public int nextPascal(int r, double p) + throws NotStrictlyPositiveException, OutOfRangeException { + return delegate.nextPascal(r, p); + } + + /** + * Generates a random value from the {@link TDistribution T Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param df the degrees of freedom of the T distribution + * @return random value from the T(df) distribution + * @since 2.2 + * @throws NotStrictlyPositiveException if {@code df <= 0} + */ + public double nextT(double df) throws NotStrictlyPositiveException { + return delegate.nextT(df); + } + + /** + * Generates a random value from the {@link WeibullDistribution Weibull Distribution}. + * This implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} + * to generate random values. + * + * @param shape the shape parameter of the Weibull distribution + * @param scale the scale parameter of the Weibull distribution + * @return random value sampled from the Weibull(shape, size) distribution + * @since 2.2 + * @throws NotStrictlyPositiveException if {@code shape <= 0} or + * {@code scale <= 0}. + */ + public double nextWeibull(double shape, double scale) throws NotStrictlyPositiveException { + return delegate.nextWeibull(shape, scale); + } + + /** + * Generates a random value from the {@link ZipfDistribution Zipf Distribution}. + * This implementation uses {@link #nextInversionDeviate(IntegerDistribution) inversion} + * to generate random values. + * + * @param numberOfElements the number of elements of the ZipfDistribution + * @param exponent the exponent of the ZipfDistribution + * @return random value sampled from the Zipf(numberOfElements, exponent) distribution + * @since 2.2 + * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} + * or {@code exponent <= 0}. + */ + public int nextZipf(int numberOfElements, double exponent) throws NotStrictlyPositiveException { + return delegate.nextZipf(numberOfElements, exponent); + } + + + /** + * Reseeds the random number generator with the supplied seed. + *

        + * Will create and initialize if null. + *

        + * + * @param seed + * the seed value to use + */ + public void reSeed(long seed) { + delegate.reSeed(seed); + } + + /** + * Reseeds the secure random number generator with the current time in + * milliseconds. + *

        + * Will create and initialize if null. + *

        + */ + public void reSeedSecure() { + delegate.reSeedSecure(); + } + + /** + * Reseeds the secure random number generator with the supplied seed. + *

        + * Will create and initialize if null. + *

        + * + * @param seed + * the seed value to use + */ + public void reSeedSecure(long seed) { + delegate.reSeedSecure(seed); + } + + /** + * Reseeds the random number generator with + * {@code System.currentTimeMillis() + System.identityHashCode(this))}. + */ + public void reSeed() { + delegate.reSeed(); + } + + /** + * Sets the PRNG algorithm for the underlying SecureRandom instance using + * the Security Provider API. The Security Provider API is defined in + * Java Cryptography Architecture API Specification & Reference. + *

        + * USAGE NOTE: This method carries significant + * overhead and may take several seconds to execute. + *

        + * + * @param algorithm + * the name of the PRNG algorithm + * @param provider + * the name of the provider + * @throws NoSuchAlgorithmException + * if the specified algorithm is not available + * @throws NoSuchProviderException + * if the specified provider is not installed + */ + public void setSecureAlgorithm(String algorithm, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + delegate.setSecureAlgorithm(algorithm, provider); + } + + /** + * {@inheritDoc} + * + *

        + * Uses a 2-cycle permutation shuffle. The shuffling process is described + * here. + *

        + */ + public int[] nextPermutation(int n, int k) + throws NotStrictlyPositiveException, NumberIsTooLargeException { + return delegate.nextPermutation(n, k); + } + + /** + * {@inheritDoc} + * + *

        + * Algorithm Description: Uses a 2-cycle permutation + * shuffle to generate a random permutation of c.size() 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 + * here + *

        + */ + public Object[] nextSample(Collection c, int k) + throws NotStrictlyPositiveException, NumberIsTooLargeException { + return delegate.nextSample(c, k); + } + + /** + * Generate a random deviate from the given distribution using the + * inversion method. + * + * @param distribution Continuous distribution to generate a random value from + * @return a random value sampled from the given distribution + * @throws MathIllegalArgumentException if the underlynig distribution throws one + * @since 2.2 + * @deprecated use the distribution's sample() method + */ + @Deprecated + public double nextInversionDeviate(RealDistribution distribution) + throws MathIllegalArgumentException { + return distribution.inverseCumulativeProbability(nextUniform(0, 1)); + + } + + /** + * Generate a random deviate from the given distribution using the + * inversion method. + * + * @param distribution Integer distribution to generate a random value from + * @return a random value sampled from the given distribution + * @throws MathIllegalArgumentException if the underlynig distribution throws one + * @since 2.2 + * @deprecated use the distribution's sample() method + */ + @Deprecated + public int nextInversionDeviate(IntegerDistribution distribution) + throws MathIllegalArgumentException { + return distribution.inverseCumulativeProbability(nextUniform(0, 1)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGenerator.java new file mode 100644 index 000000000..862409349 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + + +/** + * Interface extracted from java.util.Random. This interface is + * implemented by {@link AbstractRandomGenerator}. + * + * @since 1.1 + */ +public interface RandomGenerator { + + /** + * Sets the seed of the underlying random number generator using an + * int seed. + *

        Sequences of values generated starting with the same seeds + * should be identical. + *

        + * @param seed the seed value + */ + void setSeed(int seed); + + /** + * Sets the seed of the underlying random number generator using an + * int array seed. + *

        Sequences of values generated starting with the same seeds + * should be identical. + *

        + * @param seed the seed value + */ + void setSeed(int[] seed); + + /** + * Sets the seed of the underlying random number generator using a + * long seed. + *

        Sequences of values generated starting with the same seeds + * should be identical. + *

        + * @param seed the seed value + */ + void setSeed(long seed); + + /** + * Generates random bytes and places them into a user-supplied + * byte array. The number of random bytes produced is equal to + * the length of the byte array. + * + * @param bytes the non-null byte array in which to put the + * random bytes + */ + void nextBytes(byte[] bytes); + + /** + * Returns the next pseudorandom, uniformly distributed int + * value from this random number generator's sequence. + * All 232 possible {@code int} values + * should be produced with (approximately) equal probability. + * + * @return the next pseudorandom, uniformly distributed int + * value from this random number generator's sequence + */ + int nextInt(); + + /** + * Returns a pseudorandom, uniformly distributed {@code int} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed {@code int} + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + int nextInt(int n); + + /** + * Returns the next pseudorandom, uniformly distributed long + * value from this random number generator's sequence. All + * 264 possible {@code long} values + * should be produced with (approximately) equal probability. + * + * @return the next pseudorandom, uniformly distributed long + *value from this random number generator's sequence + */ + long nextLong(); + + /** + * Returns the next pseudorandom, uniformly distributed + * boolean value from this random number generator's + * sequence. + * + * @return the next pseudorandom, uniformly distributed + * boolean value from this random number generator's + * sequence + */ + boolean nextBoolean(); + + /** + * Returns the next pseudorandom, uniformly distributed float + * value between 0.0 and 1.0 from this random + * number generator's sequence. + * + * @return the next pseudorandom, uniformly distributed float + * value between 0.0 and 1.0 from this + * random number generator's sequence + */ + float nextFloat(); + + /** + * Returns the next pseudorandom, uniformly distributed + * double value between 0.0 and + * 1.0 from this random number generator's sequence. + * + * @return the next pseudorandom, uniformly distributed + * double value between 0.0 and + * 1.0 from this random number generator's sequence + */ + double nextDouble(); + + /** + * Returns the next pseudorandom, Gaussian ("normally") distributed + * double value with mean 0.0 and standard + * deviation 1.0 from this random number generator's sequence. + * + * @return the next pseudorandom, Gaussian ("normally") distributed + * double value with mean 0.0 and + * standard deviation 1.0 from this random number + * generator's sequence + */ + double nextGaussian(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGeneratorFactory.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGeneratorFactory.java new file mode 100644 index 000000000..da5a14a94 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomGeneratorFactory.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 com.fr.third.org.apache.commons.math3.random; + +import java.util.Random; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Utilities for creating {@link RandomGenerator} instances. + * + * @since 3.3 + */ +public class RandomGeneratorFactory { + /** + * Class contains only static methods. + */ + private RandomGeneratorFactory() {} + + /** + * Creates a {@link RandomDataGenerator} instance that wraps a + * {@link Random} instance. + * + * @param rng JDK {@link Random} instance that will generate the + * the random data. + * @return the given RNG, wrapped in a {@link RandomGenerator}. + */ + public static RandomGenerator createRandomGenerator(final Random rng) { + return new RandomGenerator() { + /** {@inheritDoc} */ + public void setSeed(int seed) { + rng.setSeed((long) seed); + } + + /** {@inheritDoc} */ + public void setSeed(int[] seed) { + rng.setSeed(convertToLong(seed)); + } + + /** {@inheritDoc} */ + public void setSeed(long seed) { + rng.setSeed(seed); + } + + /** {@inheritDoc} */ + public void nextBytes(byte[] bytes) { + rng.nextBytes(bytes); + } + + /** {@inheritDoc} */ + public int nextInt() { + return rng.nextInt(); + } + + /** {@inheritDoc} */ + public int nextInt(int n) { + if (n <= 0) { + throw new NotStrictlyPositiveException(n); + } + return rng.nextInt(n); + } + + /** {@inheritDoc} */ + public long nextLong() { + return rng.nextLong(); + } + + /** {@inheritDoc} */ + public boolean nextBoolean() { + return rng.nextBoolean(); + } + + /** {@inheritDoc} */ + public float nextFloat() { + return rng.nextFloat(); + } + + /** {@inheritDoc} */ + public double nextDouble() { + return rng.nextDouble(); + } + + /** {@inheritDoc} */ + public double nextGaussian() { + return rng.nextGaussian(); + } + }; + } + + /** + * Converts seed from one representation to another. + * + * @param seed Original seed. + * @return the converted seed. + */ + public static long convertToLong(int[] seed) { + // The following number is the largest prime that fits + // in 32 bits (i.e. 2^32 - 5). + final long prime = 4294967291l; + + long combined = 0l; + for (int s : seed) { + combined = combined * prime + s; + } + + return combined; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomVectorGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomVectorGenerator.java new file mode 100644 index 000000000..1bcfcb28f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/RandomVectorGenerator.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 com.fr.third.org.apache.commons.math3.random; + + +/** This interface represents a random generator for whole vectors. + * + * @since 1.2 + * + */ + +public interface RandomVectorGenerator { + + /** Generate a random vector. + * @return a random vector as an array of double. + */ + double[] nextVector(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SobolSequenceGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SobolSequenceGenerator.java new file mode 100644 index 000000000..fdbf33950 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SobolSequenceGenerator.java @@ -0,0 +1,325 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.random; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.MathParseException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implementation of a Sobol sequence. + *

        + * A Sobol sequence is a low-discrepancy sequence with the property that for all values of N, + * its subsequence (x1, ... xN) has a low discrepancy. It can be used to generate pseudo-random + * points in a space S, which are equi-distributed. + *

        + * The implementation already comes with support for up to 1000 dimensions with direction numbers + * calculated from Stephen Joe and Frances Kuo. + *

        + * The generator supports two modes: + *

          + *
        • sequential generation of points: {@link #nextVector()}
        • + *
        • random access to the i-th point in the sequence: {@link #skipTo(int)}
        • + *
        + * + * @see Sobol sequence (Wikipedia) + * @see Sobol sequence direction numbers + * + * @since 3.3 + */ +public class SobolSequenceGenerator implements RandomVectorGenerator { + + /** The number of bits to use. */ + private static final int BITS = 52; + + /** The scaling factor. */ + private static final double SCALE = FastMath.pow(2, BITS); + + /** The maximum supported space dimension. */ + private static final int MAX_DIMENSION = 1000; + + /** The resource containing the direction numbers. */ + private static final String RESOURCE_NAME = "/assets/org/apache/commons/math3/random/new-joe-kuo-6.1000"; + + /** Character set for file input. */ + private static final String FILE_CHARSET = "US-ASCII"; + + /** Space dimension. */ + private final int dimension; + + /** The current index in the sequence. */ + private int count = 0; + + /** The direction vector for each component. */ + private final long[][] direction; + + /** The current state. */ + private final long[] x; + + /** + * Construct a new Sobol sequence generator for the given space dimension. + * + * @param dimension the space dimension + * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 1000] + */ + public SobolSequenceGenerator(final int dimension) throws OutOfRangeException { + if (dimension < 1 || dimension > MAX_DIMENSION) { + throw new OutOfRangeException(dimension, 1, MAX_DIMENSION); + } + + // initialize the other dimensions with direction numbers from a resource + final InputStream is = getClass().getResourceAsStream(RESOURCE_NAME); + if (is == null) { + throw new MathInternalError(); + } + + this.dimension = dimension; + + // init data structures + direction = new long[dimension][BITS + 1]; + x = new long[dimension]; + + try { + initFromStream(is); + } catch (IOException e) { + // the internal resource file could not be read -> should not happen + throw new MathInternalError(); + } catch (MathParseException e) { + // the internal resource file could not be parsed -> should not happen + throw new MathInternalError(); + } finally { + try { + is.close(); + } catch (IOException e) { // NOPMD + // ignore + } + } + } + + /** + * Construct a new Sobol sequence generator for the given space dimension with + * direction vectors loaded from the given stream. + *

        + * The expected format is identical to the files available from + * Stephen Joe and Frances Kuo. + * The first line will be ignored as it is assumed to contain only the column headers. + * The columns are: + *

          + *
        • d: the dimension
        • + *
        • s: the degree of the primitive polynomial
        • + *
        • a: the number representing the coefficients
        • + *
        • m: the list of initial direction numbers
        • + *
        + * Example: + *
        +     * d       s       a       m_i
        +     * 2       1       0       1
        +     * 3       2       1       1 3
        +     * 
        + *

        + * The input stream must be an ASCII text containing one valid direction vector per line. + * + * @param dimension the space dimension + * @param is the stream to read the direction vectors from + * @throws NotStrictlyPositiveException if the space dimension is < 1 + * @throws OutOfRangeException if the space dimension is outside the range [1, max], where + * max refers to the maximum dimension found in the input stream + * @throws MathParseException if the content in the stream could not be parsed successfully + * @throws IOException if an error occurs while reading from the input stream + */ + public SobolSequenceGenerator(final int dimension, final InputStream is) + throws NotStrictlyPositiveException, MathParseException, IOException { + + if (dimension < 1) { + throw new NotStrictlyPositiveException(dimension); + } + + this.dimension = dimension; + + // init data structures + direction = new long[dimension][BITS + 1]; + x = new long[dimension]; + + // initialize the other dimensions with direction numbers from the stream + int lastDimension = initFromStream(is); + if (lastDimension < dimension) { + throw new OutOfRangeException(dimension, 1, lastDimension); + } + } + + /** + * Load the direction vector for each dimension from the given stream. + *

        + * The input stream must be an ASCII text containing one + * valid direction vector per line. + * + * @param is the input stream to read the direction vector from + * @return the last dimension that has been read from the input stream + * @throws IOException if the stream could not be read + * @throws MathParseException if the content could not be parsed successfully + */ + private int initFromStream(final InputStream is) throws MathParseException, IOException { + + // special case: dimension 1 -> use unit initialization + for (int i = 1; i <= BITS; i++) { + direction[0][i] = 1l << (BITS - i); + } + + final Charset charset = Charset.forName(FILE_CHARSET); + final BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset)); + int dim = -1; + + try { + // ignore first line + reader.readLine(); + + int lineNumber = 2; + int index = 1; + String line = null; + while ( (line = reader.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line, " "); + try { + dim = Integer.parseInt(st.nextToken()); + if (dim >= 2 && dim <= dimension) { // we have found the right dimension + final int s = Integer.parseInt(st.nextToken()); + final int a = Integer.parseInt(st.nextToken()); + final int[] m = new int[s + 1]; + for (int i = 1; i <= s; i++) { + m[i] = Integer.parseInt(st.nextToken()); + } + initDirectionVector(index++, a, m); + } + + if (dim > dimension) { + return dim; + } + } catch (NoSuchElementException e) { + throw new MathParseException(line, lineNumber); + } catch (NumberFormatException e) { + throw new MathParseException(line, lineNumber); + } + lineNumber++; + } + } finally { + reader.close(); + } + + return dim; + } + + /** + * Calculate the direction numbers from the given polynomial. + * + * @param d the dimension, zero-based + * @param a the coefficients of the primitive polynomial + * @param m the initial direction numbers + */ + private void initDirectionVector(final int d, final int a, final int[] m) { + final int s = m.length - 1; + for (int i = 1; i <= s; i++) { + direction[d][i] = ((long) m[i]) << (BITS - i); + } + for (int i = s + 1; i <= BITS; i++) { + direction[d][i] = direction[d][i - s] ^ (direction[d][i - s] >> s); + for (int k = 1; k <= s - 1; k++) { + direction[d][i] ^= ((a >> (s - 1 - k)) & 1) * direction[d][i - k]; + } + } + } + + /** {@inheritDoc} */ + public double[] nextVector() { + final double[] v = new double[dimension]; + if (count == 0) { + count++; + return v; + } + + // find the index c of the rightmost 0 + int c = 1; + int value = count - 1; + while ((value & 1) == 1) { + value >>= 1; + c++; + } + + for (int i = 0; i < dimension; i++) { + x[i] ^= direction[i][c]; + v[i] = (double) x[i] / SCALE; + } + count++; + return v; + } + + /** + * Skip to the i-th point in the Sobol sequence. + *

        + * This operation can be performed in O(1). + * + * @param index the index in the sequence to skip to + * @return the i-th point in the Sobol sequence + * @throws NotPositiveException if index < 0 + */ + public double[] skipTo(final int index) throws NotPositiveException { + if (index == 0) { + // reset x vector + Arrays.fill(x, 0); + } else { + final int i = index - 1; + final long grayCode = i ^ (i >> 1); // compute the gray code of i = i XOR floor(i / 2) + for (int j = 0; j < dimension; j++) { + long result = 0; + for (int k = 1; k <= BITS; k++) { + final long shift = grayCode >> (k - 1); + if (shift == 0) { + // stop, as all remaining bits will be zero + break; + } + // the k-th bit of i + final long ik = shift & 1; + result ^= ik * direction[j][k]; + } + x[j] = result; + } + } + count = index; + return nextVector(); + } + + /** + * Returns the index i of the next point in the Sobol sequence that will be returned + * by calling {@link #nextVector()}. + * + * @return the index of the next point + */ + public int getNextIndex() { + return count; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/StableRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/StableRandomGenerator.java new file mode 100644 index 000000000..798c9ba4a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/StableRandomGenerator.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + *

        This class provides a stable normalized random generator. It samples from a stable + * distribution with location parameter 0 and scale 1.

        + * + *

        The implementation uses the Chambers-Mallows-Stuck method as described in + * Handbook of computational statistics: concepts and methods by + * James E. Gentle, Wolfgang Härdle, Yuichi Mori.

        + * + * @since 3.0 + */ +public class StableRandomGenerator implements NormalizedRandomGenerator { + /** Underlying generator. */ + private final RandomGenerator generator; + + /** stability parameter */ + private final double alpha; + + /** skewness parameter */ + private final double beta; + + /** cache of expression value used in generation */ + private final double zeta; + + /** + * Create a new generator. + * + * @param generator underlying random generator to use + * @param alpha Stability parameter. Must be in range (0, 2] + * @param beta Skewness parameter. Must be in range [-1, 1] + * @throws NullArgumentException if generator is null + * @throws OutOfRangeException if {@code alpha <= 0} or {@code alpha > 2} + * or {@code beta < -1} or {@code beta > 1} + */ + public StableRandomGenerator(final RandomGenerator generator, + final double alpha, final double beta) + throws NullArgumentException, OutOfRangeException { + if (generator == null) { + throw new NullArgumentException(); + } + + if (!(alpha > 0d && alpha <= 2d)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_LEFT, + alpha, 0, 2); + } + + if (!(beta >= -1d && beta <= 1d)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_SIMPLE, + beta, -1, 1); + } + + this.generator = generator; + this.alpha = alpha; + this.beta = beta; + if (alpha < 2d && beta != 0d) { + zeta = beta * FastMath.tan(FastMath.PI * alpha / 2); + } else { + zeta = 0d; + } + } + + /** + * Generate a random scalar with zero location and unit scale. + * + * @return a random scalar with zero location and unit scale + */ + public double nextNormalizedDouble() { + // we need 2 uniform random numbers to calculate omega and phi + double omega = -FastMath.log(generator.nextDouble()); + double phi = FastMath.PI * (generator.nextDouble() - 0.5); + + // Normal distribution case (Box-Muller algorithm) + if (alpha == 2d) { + return FastMath.sqrt(2d * omega) * FastMath.sin(phi); + } + + double x; + // when beta = 0, zeta is zero as well + // Thus we can exclude it from the formula + if (beta == 0d) { + // Cauchy distribution case + if (alpha == 1d) { + x = FastMath.tan(phi); + } else { + x = FastMath.pow(omega * FastMath.cos((1 - alpha) * phi), + 1d / alpha - 1d) * + FastMath.sin(alpha * phi) / + FastMath.pow(FastMath.cos(phi), 1d / alpha); + } + } else { + // Generic stable distribution + double cosPhi = FastMath.cos(phi); + // to avoid rounding errors around alpha = 1 + if (FastMath.abs(alpha - 1d) > 1e-8) { + double alphaPhi = alpha * phi; + double invAlphaPhi = phi - alphaPhi; + x = (FastMath.sin(alphaPhi) + zeta * FastMath.cos(alphaPhi)) / cosPhi * + (FastMath.cos(invAlphaPhi) + zeta * FastMath.sin(invAlphaPhi)) / + FastMath.pow(omega * cosPhi, (1 - alpha) / alpha); + } else { + double betaPhi = FastMath.PI / 2 + beta * phi; + x = 2d / FastMath.PI * (betaPhi * FastMath.tan(phi) - beta * + FastMath.log(FastMath.PI / 2d * omega * cosPhi / betaPhi)); + + if (alpha != 1d) { + x += beta * FastMath.tan(FastMath.PI * alpha / 2); + } + } + } + return x; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SynchronizedRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SynchronizedRandomGenerator.java new file mode 100644 index 000000000..bfb70d012 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/SynchronizedRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +/** + * Any {@link RandomGenerator} implementation can be thread-safe if it + * is used through an instance of this class. + * This is achieved by enclosing calls to the methods of the actual + * generator inside the overridden {@code synchronized} methods of this + * class. + * + * @since 3.1 + */ +public class SynchronizedRandomGenerator implements RandomGenerator { + /** Object to which all calls will be delegated. */ + private final RandomGenerator wrapped; + + /** + * Creates a synchronized wrapper for the given {@code RandomGenerator} + * instance. + * + * @param rng Generator whose methods will be called through + * their corresponding overridden synchronized version. + * To ensure thread-safety, the wrapped generator must + * not be used directly. + */ + public SynchronizedRandomGenerator(RandomGenerator rng) { + wrapped = rng; + } + + /** + * {@inheritDoc} + */ + public synchronized void setSeed(int seed) { + wrapped.setSeed(seed); + } + + /** + * {@inheritDoc} + */ + public synchronized void setSeed(int[] seed) { + wrapped.setSeed(seed); + } + + /** + * {@inheritDoc} + */ + public synchronized void setSeed(long seed) { + wrapped.setSeed(seed); + } + + /** + * {@inheritDoc} + */ + public synchronized void nextBytes(byte[] bytes) { + wrapped.nextBytes(bytes); + } + + /** + * {@inheritDoc} + */ + public synchronized int nextInt() { + return wrapped.nextInt(); + } + + /** + * {@inheritDoc} + */ + public synchronized int nextInt(int n) { + return wrapped.nextInt(n); + } + + /** + * {@inheritDoc} + */ + public synchronized long nextLong() { + return wrapped.nextLong(); + } + + /** + * {@inheritDoc} + */ + public synchronized boolean nextBoolean() { + return wrapped.nextBoolean(); + } + + /** + * {@inheritDoc} + */ + public synchronized float nextFloat() { + return wrapped.nextFloat(); + } + + /** + * {@inheritDoc} + */ + public synchronized double nextDouble() { + return wrapped.nextDouble(); + } + + /** + * {@inheritDoc} + */ + public synchronized double nextGaussian() { + return wrapped.nextGaussian(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.java new file mode 100644 index 000000000..bd91b12b6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.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. + * + * @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. + *

        Build an uncorrelated random vector generator from + * its mean and standard deviation vectors.

        + * @param mean expected mean values for each component + * @param standardDeviation standard deviation for each component + * @param generator underlying generator for uncorrelated normalized + * components + */ + public UncorrelatedRandomVectorGenerator(double[] mean, + double[] standardDeviation, + NormalizedRandomGenerator generator) { + if (mean.length != standardDeviation.length) { + throw new DimensionMismatchException(mean.length, standardDeviation.length); + } + this.mean = mean.clone(); + this.standardDeviation = standardDeviation.clone(); + this.generator = generator; + } + + /** Simple constructor. + *

        Build a null mean random and unit standard deviation + * uncorrelated vector generator

        + * @param dimension dimension of the vectors to generate + * @param generator underlying generator for uncorrelated normalized + * components + */ + public UncorrelatedRandomVectorGenerator(int dimension, + NormalizedRandomGenerator generator) { + mean = new double[dimension]; + standardDeviation = new double[dimension]; + Arrays.fill(standardDeviation, 1.0); + this.generator = generator; + } + + /** Generate an uncorrelated random vector. + * @return a random vector as a newly built array of double + */ + public double[] nextVector() { + + double[] random = new double[mean.length]; + for (int i = 0; i < random.length; ++i) { + random[i] = mean[i] + standardDeviation[i] * generator.nextNormalizedDouble(); + } + + return random; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UniformRandomGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UniformRandomGenerator.java new file mode 100644 index 000000000..9cc819a36 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UniformRandomGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * This class implements a normalized uniform random generator. + *

        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 + * [-√3, +√3].

        + * + * @since 1.2 + * + */ + +public class UniformRandomGenerator implements NormalizedRandomGenerator { + + /** Square root of three. */ + private static final double SQRT3 = FastMath.sqrt(3.0); + + /** Underlying generator. */ + private final RandomGenerator generator; + + /** Create a new generator. + * @param generator underlying random generator to use + */ + public UniformRandomGenerator(RandomGenerator generator) { + this.generator = generator; + } + + /** Generate a random scalar with null mean and unit standard deviation. + *

        The number generated is uniformly distributed between -&sqrt;(3) + * and +&sqrt;(3).

        + * @return a random scalar with null mean and unit standard deviation + */ + public double nextNormalizedDouble() { + return SQRT3 * (2 * generator.nextDouble() - 1.0); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.java new file mode 100644 index 000000000..516261d6c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.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 com.fr.third.org.apache.commons.math3.random; + +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + * Generate random vectors isotropically located on the surface of a sphere. + * + * @since 2.1 + */ + +public class UnitSphereRandomVectorGenerator + implements RandomVectorGenerator { + /** + * RNG used for generating the individual components of the vectors. + */ + private final RandomGenerator rand; + /** + * Space dimension. + */ + private final int dimension; + + /** + * @param dimension Space dimension. + * @param rand RNG for the individual components of the vectors. + */ + public UnitSphereRandomVectorGenerator(final int dimension, + final RandomGenerator rand) { + this.dimension = dimension; + this.rand = rand; + } + /** + * Create an object that will use a default RNG ({@link MersenneTwister}), + * in order to generate the individual components. + * + * @param dimension Space dimension. + */ + public UnitSphereRandomVectorGenerator(final int dimension) { + this(dimension, new MersenneTwister()); + } + + /** {@inheritDoc} */ + public double[] nextVector() { + final double[] v = new double[dimension]; + + // See http://mathworld.wolfram.com/SpherePointPicking.html for example. + // Pick a point by choosing a standard Gaussian for each element, and then + // normalizing to unit length. + double normSq = 0; + for (int i = 0; i < dimension; i++) { + final double comp = rand.nextGaussian(); + v[i] = comp; + normSq += comp * comp; + } + + final double f = 1 / FastMath.sqrt(normSq); + for (int i = 0; i < dimension; i++) { + v[i] *= f; + } + + return v; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ValueServer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ValueServer.java new file mode 100644 index 000000000..8b6efcf0f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/ValueServer.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 com.fr.third.org.apache.commons.math3.random; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Generates values for use in simulation applications. + *

        + * How values are generated is determined by the mode + * property.

        + *

        + * Supported mode values are:

          + *
        • DIGEST_MODE -- uses an empirical distribution
        • + *
        • REPLAY_MODE -- replays data from valuesFileURL
        • + *
        • UNIFORM_MODE -- generates uniformly distributed random values with + * mean = mu
        • + *
        • EXPONENTIAL_MODE -- generates exponentially distributed random values + * with mean = mu
        • + *
        • GAUSSIAN_MODE -- generates Gaussian distributed random values with + * mean = mu and + * standard deviation = sigma
        • + *
        • CONSTANT_MODE -- returns mu every time.

        + * + * + */ +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 RandomDataGenerator randomData; + + // Data generation modes ====================================== + + /** Creates new ValueServer */ + public ValueServer() { + randomData = new RandomDataGenerator(); + } + + /** + * Construct a ValueServer instance using a RandomDataImpl as its source + * of random data. + * + * @param randomData the RandomDataImpl instance used to source random data + * @since 3.0 + * @deprecated use {@link #ValueServer(RandomGenerator)} + */ + @Deprecated + public ValueServer(RandomDataImpl randomData) { + this.randomData = randomData.getDelegate(); + } + + /** + * Construct a ValueServer instance using a RandomGenerator as its source + * of random data. + * + * @since 3.1 + * @param generator source of random data + */ + public ValueServer(RandomGenerator generator) { + this.randomData = new RandomDataGenerator(generator); + } + + /** + * Returns the next generated value, generated according + * to the mode value (see MODE constants). + * + * @return generated value + * @throws IOException in REPLAY_MODE if a file I/O error occurs + * @throws MathIllegalStateException if mode is not recognized + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + public double getNext() throws IOException, MathIllegalStateException, MathIllegalArgumentException { + switch (mode) { + case DIGEST_MODE: return getNextDigest(); + case REPLAY_MODE: return getNextReplay(); + case UNIFORM_MODE: return getNextUniform(); + case EXPONENTIAL_MODE: return getNextExponential(); + case GAUSSIAN_MODE: return getNextGaussian(); + case CONSTANT_MODE: return mu; + default: throw new MathIllegalStateException( + LocalizedFormats.UNKNOWN_MODE, + mode, + "DIGEST_MODE", DIGEST_MODE, "REPLAY_MODE", REPLAY_MODE, + "UNIFORM_MODE", UNIFORM_MODE, "EXPONENTIAL_MODE", EXPONENTIAL_MODE, + "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE", CONSTANT_MODE); + } + } + + /** + * Fills the input array with values generated using getNext() repeatedly. + * + * @param values array to be filled + * @throws IOException in REPLAY_MODE if a file I/O error occurs + * @throws MathIllegalStateException if mode is not recognized + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + public void fill(double[] values) + throws IOException, MathIllegalStateException, MathIllegalArgumentException { + for (int i = 0; i < values.length; i++) { + values[i] = getNext(); + } + } + + /** + * Returns an array of length length with values generated + * using getNext() repeatedly. + * + * @param length length of output array + * @return array of generated values + * @throws IOException in REPLAY_MODE if a file I/O error occurs + * @throws MathIllegalStateException if mode is not recognized + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + public double[] fill(int length) + throws IOException, MathIllegalStateException, MathIllegalArgumentException { + double[] out = new double[length]; + for (int i = 0; i < length; i++) { + out[i] = getNext(); + } + return out; + } + + /** + * Computes the empirical distribution using values from the file + * in valuesFileURL, using the default number of bins. + *

        + * valuesFileURL must exist and be + * readable by *this at runtime.

        + *

        + * This method must be called before using getNext() + * with mode = DIGEST_MODE

        + * + * @throws IOException if an I/O error occurs reading the input file + * @throws NullArgumentException if the {@code valuesFileURL} has not been set + * @throws ZeroException if URL contains no data + */ + public void computeDistribution() throws IOException, ZeroException, NullArgumentException { + computeDistribution(EmpiricalDistribution.DEFAULT_BIN_COUNT); + } + + /** + * Computes the empirical distribution using values from the file + * in valuesFileURL and binCount bins. + *

        + * valuesFileURL must exist and be readable by this process + * at runtime.

        + *

        + * This method must be called before using getNext() + * with mode = DIGEST_MODE

        + * + * @param binCount the number of bins used in computing the empirical + * distribution + * @throws NullArgumentException if the {@code valuesFileURL} has not been set + * @throws IOException if an error occurs reading the input file + * @throws ZeroException if URL contains no data + */ + public void computeDistribution(int binCount) throws NullArgumentException, IOException, ZeroException { + empiricalDistribution = new EmpiricalDistribution(binCount, randomData.getRandomGenerator()); + empiricalDistribution.load(valuesFileURL); + mu = empiricalDistribution.getSampleStats().getMean(); + sigma = empiricalDistribution.getSampleStats().getStandardDeviation(); + } + + /** + * Returns the data generation mode. See {@link ValueServer the class javadoc} + * for description of the valid values of this property. + * + * @return Value of property mode. + */ + public int getMode() { + return mode; + } + + /** + * Sets the data generation mode. + * + * @param mode New value of the data generation mode. + */ + public void setMode(int mode) { + this.mode = mode; + } + + /** + * Returns the URL for the file used to build the empirical distribution + * when using {@link #DIGEST_MODE}. + * + * @return Values file URL. + */ + public URL getValuesFileURL() { + return valuesFileURL; + } + + /** + * Sets the {@link #getValuesFileURL() values file URL} using a string + * URL representation. + * + * @param url String representation for new valuesFileURL. + * @throws MalformedURLException if url is not well formed + */ + public void setValuesFileURL(String url) throws MalformedURLException { + this.valuesFileURL = new URL(url); + } + + /** + * Sets the the {@link #getValuesFileURL() values file URL}. + * + *

        The values file must be an ASCII text file containing one + * valid numeric entry per line.

        + * + * @param url URL of the values file. + */ + public void setValuesFileURL(URL url) { + this.valuesFileURL = url; + } + + /** + * Returns the {@link EmpiricalDistribution} used when operating in {@value #DIGEST_MODE}. + * + * @return EmpircalDistribution built by {@link #computeDistribution()} + */ + public EmpiricalDistribution getEmpiricalDistribution() { + return empiricalDistribution; + } + + /** + * Resets REPLAY_MODE file pointer to the beginning of the valuesFileURL. + * + * @throws IOException if an error occurs opening the file + * @throws NullPointerException if the {@code valuesFileURL} has not been set. + */ + public void resetReplayFile() throws IOException { + if (filePointer != null) { + try { + filePointer.close(); + filePointer = null; + } catch (IOException ex) { //NOPMD + // ignore + } + } + filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8")); + } + + /** + * Closes {@code valuesFileURL} after use in REPLAY_MODE. + * + * @throws IOException if an error occurs closing the file + */ + public void closeReplayFile() throws IOException { + if (filePointer != null) { + filePointer.close(); + filePointer = null; + } + } + + /** + * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE} + * or {@link #UNIFORM_MODE}. When operating in {@link #CONSTANT_MODE}, this is the constant + * value always returned. Calling {@link #computeDistribution()} sets this value to the + * overall mean of the values in the {@link #getValuesFileURL() values file}. + * + * @return Mean used in data generation. + */ + public double getMu() { + return mu; + } + + /** + * Sets the {@link #getMu() mean} used in data generation. Note that calling this method + * after {@link #computeDistribution()} has been called will have no effect on data + * generated in {@link #DIGEST_MODE}. + * + * @param mu new Mean value. + */ + public void setMu(double mu) { + this.mu = mu; + } + + /** + * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}. + * Calling {@link #computeDistribution()} sets this value to the overall standard + * deviation of the values in the {@link #getValuesFileURL() values file}. This + * property has no effect when the data generation mode is not + * {@link #GAUSSIAN_MODE}. + * + * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}. + */ + public double getSigma() { + return sigma; + } + + /** + * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}. + * + * @param sigma New standard deviation. + */ + public void setSigma(double sigma) { + this.sigma = sigma; + } + + /** + * Reseeds the random data generator. + * + * @param seed Value with which to reseed the {@link RandomDataImpl} + * used to generate random data. + */ + public void reSeed(long seed) { + randomData.reSeed(seed); + } + + //------------- private methods --------------------------------- + + /** + * Gets a random value in DIGEST_MODE. + *

        + * Preconditions:

          + *
        • Before this method is called, computeDistribution() + * must have completed successfully; otherwise an + * IllegalStateException will be thrown

        + * + * @return next random value from the empirical distribution digest + * @throws MathIllegalStateException if digest has not been initialized + */ + private double getNextDigest() throws MathIllegalStateException { + if ((empiricalDistribution == null) || + (empiricalDistribution.getBinStats().size() == 0)) { + throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED); + } + return empiricalDistribution.getNextValue(); + } + + /** + * Gets next sequential value from the valuesFileURL. + *

        + * Throws an IOException if the read fails.

        + *

        + * This method will open the valuesFileURL if there is no + * replay file open.

        + *

        + * The valuesFileURL 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 valuesFileURL is + * empty.

        + * + * @return next value from the replay file + * @throws IOException if there is a problem reading from the file + * @throws MathIllegalStateException if URL contains no data + * @throws NumberFormatException if an invalid numeric string is + * encountered in the file + */ + private double getNextReplay() throws IOException, MathIllegalStateException { + String str = null; + if (filePointer == null) { + resetReplayFile(); + } + if ((str = filePointer.readLine()) == null) { + // we have probably reached end of file, wrap around from EOF to BOF + closeReplayFile(); + resetReplayFile(); + if ((str = filePointer.readLine()) == null) { + throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA, + valuesFileURL); + } + } + return Double.parseDouble(str); + } + + /** + * Gets a uniformly distributed random value with mean = mu. + * + * @return random uniform value + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + private double getNextUniform() throws MathIllegalArgumentException { + return randomData.nextUniform(0, 2 * mu); + } + + /** + * Gets an exponentially distributed random value with mean = mu. + * + * @return random exponential value + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + private double getNextExponential() throws MathIllegalArgumentException { + return randomData.nextExponential(mu); + } + + /** + * Gets a Gaussian distributed random value with mean = mu + * and standard deviation = sigma. + * + * @return random Gaussian value + * @throws MathIllegalArgumentException if the underlying random generator thwrows one + */ + private double getNextGaussian() throws MathIllegalArgumentException { + return randomData.nextGaussian(mu, sigma); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well1024a.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well1024a.java new file mode 100644 index 000000000..668b8f7f7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well1024a.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL1024a pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well1024a() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well1024a(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well1024a(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well1024a(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + final int indexRm1 = iRm1[index]; + + final int v0 = v[index]; + final int vM1 = v[i1[index]]; + final int vM2 = v[i2[index]]; + final int vM3 = v[i3[index]]; + + final int z0 = v[indexRm1]; + final int z1 = v0 ^ (vM1 ^ (vM1 >>> 8)); + final int z2 = (vM2 ^ (vM2 << 19)) ^ (vM3 ^ (vM3 << 14)); + final int z3 = z1 ^ z2; + final int z4 = (z0 ^ (z0 << 11)) ^ (z1 ^ (z1 << 7)) ^ (z2 ^ (z2 << 13)); + + v[index] = z3; + v[indexRm1] = z4; + index = indexRm1; + + return z4 >>> (32 - bits); + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937a.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937a.java new file mode 100644 index 000000000..03d73de1a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937a.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 com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL19937a pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well19937a() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well19937a(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well19937a(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well19937a(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + final int indexRm1 = iRm1[index]; + final int indexRm2 = iRm2[index]; + + final int v0 = v[index]; + final int vM1 = v[i1[index]]; + final int vM2 = v[i2[index]]; + final int vM3 = v[i3[index]]; + + final int z0 = (0x80000000 & v[indexRm1]) ^ (0x7FFFFFFF & v[indexRm2]); + final int z1 = (v0 ^ (v0 << 25)) ^ (vM1 ^ (vM1 >>> 27)); + final int z2 = (vM2 >>> 9) ^ (vM3 ^ (vM3 >>> 1)); + final int z3 = z1 ^ z2; + final int z4 = z0 ^ (z1 ^ (z1 << 9)) ^ (z2 ^ (z2 << 21)) ^ (z3 ^ (z3 >>> 21)); + + v[index] = z3; + v[indexRm1] = z4; + v[indexRm2] &= 0x80000000; + index = indexRm1; + + return z4 >>> (32 - bits); + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937c.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937c.java new file mode 100644 index 000000000..40a565f22 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well19937c.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 com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL19937c pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well19937c() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well19937c(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well19937c(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well19937c(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + final int indexRm1 = iRm1[index]; + final int indexRm2 = iRm2[index]; + + final int v0 = v[index]; + final int vM1 = v[i1[index]]; + final int vM2 = v[i2[index]]; + final int vM3 = v[i3[index]]; + + final int z0 = (0x80000000 & v[indexRm1]) ^ (0x7FFFFFFF & v[indexRm2]); + final int z1 = (v0 ^ (v0 << 25)) ^ (vM1 ^ (vM1 >>> 27)); + final int z2 = (vM2 >>> 9) ^ (vM3 ^ (vM3 >>> 1)); + final int z3 = z1 ^ z2; + int z4 = z0 ^ (z1 ^ (z1 << 9)) ^ (z2 ^ (z2 << 21)) ^ (z3 ^ (z3 >>> 21)); + + v[index] = z3; + v[indexRm1] = z4; + v[indexRm2] &= 0x80000000; + index = indexRm1; + + + // add Matsumoto-Kurita tempering + // to get a maximally-equidistributed generator + z4 ^= (z4 << 7) & 0xe46e1700; + z4 ^= (z4 << 15) & 0x9b868000; + + return z4 >>> (32 - bits); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497a.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497a.java new file mode 100644 index 000000000..2c3d1b018 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497a.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 com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL44497a pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well44497a() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well44497a(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well44497a(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well44497a(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + final int indexRm1 = iRm1[index]; + final int indexRm2 = iRm2[index]; + + final int v0 = v[index]; + final int vM1 = v[i1[index]]; + final int vM2 = v[i2[index]]; + final int vM3 = v[i3[index]]; + + // the values below include the errata of the original article + final int z0 = (0xFFFF8000 & v[indexRm1]) ^ (0x00007FFF & v[indexRm2]); + final int z1 = (v0 ^ (v0 << 24)) ^ (vM1 ^ (vM1 >>> 30)); + final int z2 = (vM2 ^ (vM2 << 10)) ^ (vM3 << 26); + final int z3 = z1 ^ z2; + final int z2Prime = ((z2 << 9) ^ (z2 >>> 23)) & 0xfbffffff; + final int z2Second = ((z2 & 0x00020000) != 0) ? (z2Prime ^ 0xb729fcec) : z2Prime; + final int z4 = z0 ^ (z1 ^ (z1 >>> 20)) ^ z2Second ^ z3; + + v[index] = z3; + v[indexRm1] = z4; + v[indexRm2] &= 0xFFFF8000; + index = indexRm1; + + return z4 >>> (32 - bits); + + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497b.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497b.java new file mode 100644 index 000000000..c19c12497 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well44497b.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 com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL44497b pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well44497b() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well44497b(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well44497b(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well44497b(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + // compute raw value given by WELL44497a generator + // which is NOT maximally-equidistributed + final int indexRm1 = iRm1[index]; + final int indexRm2 = iRm2[index]; + + final int v0 = v[index]; + final int vM1 = v[i1[index]]; + final int vM2 = v[i2[index]]; + final int vM3 = v[i3[index]]; + + // the values below include the errata of the original article + final int z0 = (0xFFFF8000 & v[indexRm1]) ^ (0x00007FFF & v[indexRm2]); + final int z1 = (v0 ^ (v0 << 24)) ^ (vM1 ^ (vM1 >>> 30)); + final int z2 = (vM2 ^ (vM2 << 10)) ^ (vM3 << 26); + final int z3 = z1 ^ z2; + final int z2Prime = ((z2 << 9) ^ (z2 >>> 23)) & 0xfbffffff; + final int z2Second = ((z2 & 0x00020000) != 0) ? (z2Prime ^ 0xb729fcec) : z2Prime; + int z4 = z0 ^ (z1 ^ (z1 >>> 20)) ^ z2Second ^ z3; + + v[index] = z3; + v[indexRm1] = z4; + v[indexRm2] &= 0xFFFF8000; + index = indexRm1; + + // add Matsumoto-Kurita tempering + // to get a maximally-equidistributed generator + z4 ^= (z4 << 7) & 0x93dd1400; + z4 ^= (z4 << 15) & 0xfa118000; + + return z4 >>> (32 - bits); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well512a.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well512a.java new file mode 100644 index 000000000..0063828bb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/Well512a.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 com.fr.third.org.apache.commons.math3.random; + + +/** This class implements the WELL512a pseudo-random number generator + * from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto. + + *

        This generator is described in a paper by François Panneton, + * Pierre L'Ecuyer and Makoto Matsumoto Improved + * Long-Period Generators Based on Linear Recurrences Modulo 2 ACM + * Transactions on Mathematical Software, 32, 1 (2006). The errata for the paper + * are in wellrng-errata.txt.

        + + * @see WELL Random number generator + * @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. + *

        The instance is initialized using the current time as the + * seed.

        + */ + public Well512a() { + super(K, M1, M2, M3); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public Well512a(int seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using an int array seed. + * @param seed the initial seed (32 bits integers array), if null + * the seed of the generator will be related to the current time + */ + public Well512a(int[] seed) { + super(K, M1, M2, M3, seed); + } + + /** Creates a new random number generator using a single long seed. + * @param seed the initial seed (64 bits integer) + */ + public Well512a(long seed) { + super(K, M1, M2, M3, seed); + } + + /** {@inheritDoc} */ + @Override + protected int next(final int bits) { + + final int indexRm1 = iRm1[index]; + + final int vi = v[index]; + final int vi1 = v[i1[index]]; + final int vi2 = v[i2[index]]; + final int z0 = v[indexRm1]; + + // the values below include the errata of the original article + final int z1 = (vi ^ (vi << 16)) ^ (vi1 ^ (vi1 << 15)); + final int z2 = vi2 ^ (vi2 >>> 11); + final int z3 = z1 ^ z2; + final int z4 = (z0 ^ (z0 << 2)) ^ (z1 ^ (z1 << 18)) ^ (z2 << 28) ^ (z3 ^ ((z3 << 5) & 0xda442d24)); + + v[index] = z3; + v[indexRm1] = z4; + index = indexRm1; + + return z4 >>> (32 - bits); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/package-info.java new file mode 100644 index 000000000..570e8c2c3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/random/package-info.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. + */ +/** + * + *

        Random number and random data generators.

        + *

        Commons-math provides a few pseudo random number generators. The top level interface is RandomGenerator. + * It is implemented by three classes: + *

          + *
        • {@link com.fr.third.org.apache.commons.math3.random.JDKRandomGenerator JDKRandomGenerator} + * that extends the JDK provided generator
        • + *
        • AbstractRandomGenerator as a helper for users generators
        • + *
        • BitStreamGenerator which is an abstract class for several generators and + * which in turn is extended by: + *
            + *
          • {@link com.fr.third.org.apache.commons.math3.random.MersenneTwister MersenneTwister}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well512a Well512a}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well1024a Well1024a}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well19937a Well19937a}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well19937c Well19937c}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well44497a Well44497a}
          • + *
          • {@link com.fr.third.org.apache.commons.math3.random.Well44497b Well44497b}
          • + *
          + *
        • + *
        + *

        + * + *

        + * 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: 219937 - 1 (which is a Mersenne prime). This generator + * is described in a paper by Makoto Matsumoto and Takuji Nishimura in 1998: Mersenne Twister: + * A 623-Dimensionally Equidistributed Uniform Pseudo-Random Number Generator, 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 2512 - 1 + * to 244497 - 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 Improved Long-Period + * Generators Based on Linear Recurrences Modulo 2 ACM Transactions on Mathematical Software, + * 32, 1 (2006). The errata for the paper are in wellrng-errata.txt. + *

        + * + *

        + * 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 2n-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. + *

        + * + *

        + * 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 escape zeroland 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). + *

        + * + *

        + * 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. + *

        + * + *

        + * 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: + *

        + * + *

        + * + * + * + * + * + * + * + * + * + * + * + *
        Example of performances
        Namegeneration rate (relative to MersenneTwister)
        {@link com.fr.third.org.apache.commons.math3.random.MersenneTwister MersenneTwister}1
        {@link com.fr.third.org.apache.commons.math3.random.JDKRandomGenerator JDKRandomGenerator}between 0.96 and 1.16
        {@link com.fr.third.org.apache.commons.math3.random.Well512a Well512a}between 0.85 and 0.88
        {@link com.fr.third.org.apache.commons.math3.random.Well1024a Well1024a}between 0.63 and 0.73
        {@link com.fr.third.org.apache.commons.math3.random.Well19937a Well19937a}between 0.70 and 0.71
        {@link com.fr.third.org.apache.commons.math3.random.Well19937c Well19937c}between 0.57 and 0.71
        {@link com.fr.third.org.apache.commons.math3.random.Well44497a Well44497a}between 0.69 and 0.71
        {@link com.fr.third.org.apache.commons.math3.random.Well44497b Well44497b}between 0.65 and 0.71
        + *

        + * + *

        + * So for most simulation problems, the better generators like {@link + * com.fr.third.org.apache.commons.math3.random.Well19937c Well19937c} and {@link + * com.fr.third.org.apache.commons.math3.random.Well44497b Well44497b} are probably very good choices. + *

        + * + *

        + * Note that none of these generators are suitable for cryptography. They are devoted + * to simulation, and to generate very long series with strong properties on the series as a whole + * (equidistribution, no correlation ...). They do not attempt to create small series but with + * very strong properties of unpredictability as needed in cryptography. + *

        + * + * + */ +package com.fr.third.org.apache.commons.math3.random; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/BesselJ.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/BesselJ.java new file mode 100644 index 000000000..773cf21e0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/BesselJ.java @@ -0,0 +1,648 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.special; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * This class provides computation methods related to Bessel + * functions of the first kind. Detailed descriptions of these functions are + * available in Wikipedia, Abrabowitz and + * Stegun (Ch. 9-11), and DLMF (Ch. 10). + *

        + * This implementation is based on the rjbesl Fortran routine at + * Netlib.

        + *

        + * From the Fortran code:

        + *

        + * This program is based on a program written by David J. Sookne (2) that + * computes values of the Bessel functions J or I of real argument and integer + * order. Modifications include the restriction of the computation to the J + * Bessel function of non-negative real argument, the extension of the + * computation to arbitrary positive order, and the elimination of most + * underflow.

        + *

        + * References:

        + *
          + *
        • "A Note on Backward Recurrence Algorithms," Olver, F. W. J., and Sookne, + * D. J., Math. Comp. 26, 1972, pp 941-947.
        • + *
        • "Bessel Functions of Real Argument and Integer Order," Sookne, D. J., NBS + * Jour. of Res. B. 77B, 1973, pp 125-132.
        • + *

        + * @since 3.4 + */ +public class BesselJ + implements UnivariateFunction { + + // --------------------------------------------------------------------- + // Mathematical constants + // --------------------------------------------------------------------- + + /** -2 / pi */ + private static final double PI2 = 0.636619772367581343075535; + + /** first few significant digits of 2pi */ + private static final double TOWPI1 = 6.28125; + + /** 2pi - TWOPI1 to working precision */ + private static final double TWOPI2 = 1.935307179586476925286767e-3; + + /** TOWPI1 + TWOPI2 */ + private static final double TWOPI = TOWPI1 + TWOPI2; + + // --------------------------------------------------------------------- + // Machine-dependent parameters + // --------------------------------------------------------------------- + + /** + * 10.0^K, where K is the largest integer such that ENTEN is + * machine-representable in working precision + */ + private static final double ENTEN = 1.0e308; + + /** + * Decimal significance desired. Should be set to (INT(log_{10}(2) * (it)+1)). + * Setting NSIG lower will result in decreased accuracy while setting + * NSIG higher will increase CPU time without increasing accuracy. + * The truncation error is limited to a relative error of + * T=.5(10^(-NSIG)). + */ + private static final double ENSIG = 1.0e16; + + /** 10.0 ** (-K) for the smallest integer K such that K >= NSIG/4 */ + private static final double RTNSIG = 1.0e-4; + + /** Smallest ABS(X) such that X/4 does not underflow */ + private static final double ENMTEN = 8.90e-308; + + /** Minimum acceptable value for x */ + private static final double X_MIN = 0.0; + + /** + * Upper limit on the magnitude of x. If abs(x) = n, then at least + * n iterations of the backward recursion will be executed. The value of + * 10.0 ** 4 is used on every machine. + */ + private static final double X_MAX = 1.0e4; + + /** First 25 factorials as doubles */ + private static final double[] FACT = { + 1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0, 362880.0, + 3628800.0, 39916800.0, 479001600.0, 6227020800.0, 87178291200.0, + 1.307674368e12, 2.0922789888e13, 3.55687428096e14, 6.402373705728e15, + 1.21645100408832e17, 2.43290200817664e18, 5.109094217170944e19, + 1.12400072777760768e21, 2.585201673888497664e22, + 6.2044840173323943936e23 + }; + + /** Order of the function computed when {@link #value(double)} is used */ + private final double order; + + /** + * Create a new BesselJ with the given order. + * + * @param order order of the function computed when using {@link #value(double)}. + */ + public BesselJ(double order) { + this.order = order; + } + + /** + * Returns the value of the constructed Bessel function of the first kind, + * for the passed argument. + * + * @param x Argument + * @return Value of the Bessel function at x + * @throws MathIllegalArgumentException if {@code x} is too large relative to {@code order} + * @throws ConvergenceException if the algorithm fails to converge + */ + public double value(double x) + throws MathIllegalArgumentException, ConvergenceException { + return BesselJ.value(order, x); + } + + /** + * Returns the first Bessel function, \(J_{order}(x)\). + * + * @param order Order of the Bessel function + * @param x Argument + * @return Value of the Bessel function of the first kind, \(J_{order}(x)\) + * @throws MathIllegalArgumentException if {@code x} is too large relative to {@code order} + * @throws ConvergenceException if the algorithm fails to converge + */ + public static double value(double order, double x) + throws MathIllegalArgumentException, ConvergenceException { + final int n = (int) order; + final double alpha = order - n; + final int nb = n + 1; + final BesselJResult res = rjBesl(x, alpha, nb); + + if (res.nVals >= nb) { + return res.vals[n]; + } else if (res.nVals < 0) { + throw new MathIllegalArgumentException(LocalizedFormats.BESSEL_FUNCTION_BAD_ARGUMENT,order, x); + } else if (FastMath.abs(res.vals[res.nVals - 1]) < 1e-100) { + return res.vals[n]; // underflow; return value (will be zero) + } + throw new ConvergenceException(LocalizedFormats.BESSEL_FUNCTION_FAILED_CONVERGENCE, order, x); + } + + /** + * Encapsulates the results returned by {@link BesselJ#rjBesl(double, double, int)}. + *

        + * {@link #getVals()} returns the computed function values. + * {@link #getnVals()} is the number of values among those returned by {@link #getnVals()} + * that can be considered accurate. + *

        + *

          + *
        • nVals < 0: An argument is out of range. For example, nb <= 0, alpha + * < 0 or > 1, or x is too large. In this case, b(0) is set to zero, the + * remainder of the b-vector is not calculated, and nVals is set to + * MIN(nb,0) - 1 so that nVals != nb.
        • + *
        • nb > nVals > 0: Not all requested function values could be calculated + * accurately. This usually occurs because nb is much larger than abs(x). In + * this case, b(n) is calculated to the desired accuracy for n < nVals, but + * precision is lost for nVals < n <= nb. If b(n) does not vanish for n > + * nVals (because it is too small to be represented), and b(n)/b(nVals) = + * \(10^{-k}\), then only the first NSIG-k significant figures of b(n) can be + * trusted.

        + */ + public static class BesselJResult { + + /** Bessel function values */ + private final double[] vals; + + /** Valid value count */ + private final int nVals; + + /** + * Create a new BesselJResult with the given values and valid value count. + * + * @param b values + * @param n count of valid values + */ + public BesselJResult(double[] b, int n) { + vals = MathArrays.copyOf(b, b.length); + nVals = n; + } + + /** + * @return the computed function values + */ + public double[] getVals() { + return MathArrays.copyOf(vals, vals.length); + } + + /** + * @return the number of valid function values (normally the same as the + * length of the array returned by {@link #getnVals()}) + */ + public int getnVals() { + return nVals; + } + } + + /** + * Calculates Bessel functions \(J_{n+alpha}(x)\) for + * non-negative argument x, and non-negative order n + alpha. + *

        + * Before using the output vector, the user should check that + * nVals = nb, i.e., all orders have been calculated to the desired accuracy. + * See BesselResult class javadoc for details on return values. + *

        + * @param x non-negative real argument for which J's are to be calculated + * @param alpha fractional part of order for which J's or exponentially + * scaled J's (\(J\cdot e^{x}\)) are to be calculated. 0 <= alpha < 1.0. + * @param nb integer number of functions to be calculated, nb > 0. The first + * function calculated is of order alpha, and the last is of order + * nb - 1 + alpha. + * @return BesselJResult a vector of the functions + * \(J_{alpha}(x)\) through \(J_{nb-1+alpha}(x)\), or the corresponding exponentially + * scaled functions and an integer output variable indicating possible errors + */ + public static BesselJResult rjBesl(double x, double alpha, int nb) { + final double[] b = new double[nb]; + + int ncalc = 0; + double alpem = 0; + double alp2em = 0; + + // --------------------------------------------------------------------- + // Check for out of range arguments. + // --------------------------------------------------------------------- + final int magx = (int) x; + if ((nb > 0) && (x >= X_MIN) && (x <= X_MAX) && (alpha >= 0) && + (alpha < 1)) { + // --------------------------------------------------------------------- + // Initialize result array to zero. + // --------------------------------------------------------------------- + ncalc = nb; + for (int i = 0; i < nb; ++i) { + b[i] = 0; + } + + // --------------------------------------------------------------------- + // Branch to use 2-term ascending series for small X and asymptotic + // form for large X when NB is not too large. + // --------------------------------------------------------------------- + double tempa; + double tempb; + double tempc; + double tover; + if (x < RTNSIG) { + // --------------------------------------------------------------------- + // Two-term ascending series for small X. + // --------------------------------------------------------------------- + tempa = 1; + alpem = 1 + alpha; + double halfx = 0; + if (x > ENMTEN) { + halfx = 0.5 * x; + } + if (alpha != 0) { + tempa = FastMath.pow(halfx, alpha) / + (alpha * Gamma.gamma(alpha)); + } + tempb = 0; + if (x + 1 > 1) { + tempb = -halfx * halfx; + } + b[0] = tempa + (tempa * tempb / alpem); + if ((x != 0) && (b[0] == 0)) { + ncalc = 0; + } + if (nb != 1) { + if (x <= 0) { + for (int n = 1; n < nb; ++n) { + b[n] = 0; + } + } else { + // --------------------------------------------------------------------- + // Calculate higher order functions. + // --------------------------------------------------------------------- + tempc = halfx; + tover = tempb != 0 ? ENMTEN / tempb : 2 * ENMTEN / x; + for (int n = 1; n < nb; ++n) { + tempa /= alpem; + alpem += 1; + tempa *= tempc; + if (tempa <= tover * alpem) { + tempa = 0; + } + b[n] = tempa + (tempa * tempb / alpem); + if ((b[n] == 0) && (ncalc > n)) { + ncalc = n; + } + } + } + } + } else if ((x > 25.0) && (nb <= magx + 1)) { + // --------------------------------------------------------------------- + // Asymptotic series for X > 25 + // --------------------------------------------------------------------- + final double xc = FastMath.sqrt(PI2 / x); + final double mul = 0.125 / x; + final double xin = mul * mul; + int m = 0; + if (x >= 130.0) { + m = 4; + } else if (x >= 35.0) { + m = 8; + } else { + m = 11; + } + + final double xm = 4.0 * m; + // --------------------------------------------------------------------- + // Argument reduction for SIN and COS routines. + // --------------------------------------------------------------------- + double t = (double) ((int) ((x / TWOPI) + 0.5)); + final double z = x - t * TOWPI1 - t * TWOPI2 - (alpha + 0.5) / PI2; + double vsin = FastMath.sin(z); + double vcos = FastMath.cos(z); + double gnu = 2 * alpha; + double capq; + double capp; + double s; + double t1; + double xk; + for (int i = 1; i <= 2; i++) { + s = (xm - 1 - gnu) * (xm - 1 + gnu) * xin * 0.5; + t = (gnu - (xm - 3.0)) * (gnu + (xm - 3.0)); + capp = (s * t) / FACT[2 * m]; + t1 = (gnu - (xm + 1)) * (gnu + (xm + 1)); + capq = (s * t1) / FACT[2 * m + 1]; + xk = xm; + int k = 2 * m; + t1 = t; + + for (int j = 2; j <= m; j++) { + xk -= 4.0; + s = (xk - 1 - gnu) * (xk - 1 + gnu); + t = (gnu - (xk - 3.0)) * (gnu + (xk - 3.0)); + capp = (capp + 1 / FACT[k - 2]) * s * t * xin; + capq = (capq + 1 / FACT[k - 1]) * s * t1 * xin; + k -= 2; + t1 = t; + } + + capp += 1; + capq = (capq + 1) * ((gnu * gnu) - 1) * (0.125 / x); + b[i - 1] = xc * (capp * vcos - capq * vsin); + if (nb == 1) { + return new BesselJResult(MathArrays.copyOf(b, b.length), + ncalc); + } + t = vsin; + vsin = -vcos; + vcos = t; + gnu += 2.0; + } + + // --------------------------------------------------------------------- + // If NB > 2, compute J(X,ORDER+I) I = 2, NB-1 + // --------------------------------------------------------------------- + if (nb > 2) { + gnu = 2 * alpha + 2.0; + for (int j = 2; j < nb; ++j) { + b[j] = gnu * b[j - 1] / x - b[j - 2]; + gnu += 2.0; + } + } + } else { + // --------------------------------------------------------------------- + // Use recurrence to generate results. First initialize the + // calculation of P*S. + // --------------------------------------------------------------------- + final int nbmx = nb - magx; + int n = magx + 1; + int nstart = 0; + int nend = 0; + double en = 2 * (n + alpha); + double plast = 1; + double p = en / x; + double pold; + // --------------------------------------------------------------------- + // Calculate general significance test. + // --------------------------------------------------------------------- + double test = 2 * ENSIG; + boolean readyToInitialize = false; + if (nbmx >= 3) { + // --------------------------------------------------------------------- + // Calculate P*S until N = NB-1. Check for possible + // overflow. + // --------------------------------------------------------------------- + tover = ENTEN / ENSIG; + nstart = magx + 2; + nend = nb - 1; + en = 2 * (nstart - 1 + alpha); + double psave; + double psavel; + for (int k = nstart; k <= nend; k++) { + n = k; + en += 2.0; + pold = plast; + plast = p; + p = (en * plast / x) - pold; + if (p > tover) { + // --------------------------------------------------------------------- + // To avoid overflow, divide P*S by TOVER. Calculate + // P*S until + // ABS(P) > 1. + // --------------------------------------------------------------------- + tover = ENTEN; + p /= tover; + plast /= tover; + psave = p; + psavel = plast; + nstart = n + 1; + do { + n += 1; + en += 2.0; + pold = plast; + plast = p; + p = (en * plast / x) - pold; + } while (p <= 1); + tempb = en / x; + // --------------------------------------------------------------------- + // Calculate backward test and find NCALC, the + // highest N such that + // the test is passed. + // --------------------------------------------------------------------- + test = pold * plast * (0.5 - 0.5 / (tempb * tempb)); + test /= ENSIG; + p = plast * tover; + n -= 1; + en -= 2.0; + nend = FastMath.min(nb, n); + for (int l = nstart; l <= nend; l++) { + pold = psavel; + psavel = psave; + psave = (en * psavel / x) - pold; + if (psave * psavel > test) { + ncalc = l - 1; + readyToInitialize = true; + break; + } + } + ncalc = nend; + readyToInitialize = true; + break; + } + } + if (!readyToInitialize) { + n = nend; + en = 2 * (n + alpha); + // --------------------------------------------------------------------- + // Calculate special significance test for NBMX > 2. + // --------------------------------------------------------------------- + test = FastMath.max(test, FastMath.sqrt(plast * ENSIG) * + FastMath.sqrt(2 * p)); + } + } + // --------------------------------------------------------------------- + // Calculate P*S until significance test passes. + // --------------------------------------------------------------------- + if (!readyToInitialize) { + do { + n += 1; + en += 2.0; + pold = plast; + plast = p; + p = (en * plast / x) - pold; + } while (p < test); + } + // --------------------------------------------------------------------- + // Initialize the backward recursion and the normalization sum. + // --------------------------------------------------------------------- + n += 1; + en += 2.0; + tempb = 0; + tempa = 1 / p; + int m = (2 * n) - 4 * (n / 2); + double sum = 0; + double em = (double) (n / 2); + alpem = em - 1 + alpha; + alp2em = 2 * em + alpha; + if (m != 0) { + sum = tempa * alpem * alp2em / em; + } + nend = n - nb; + + boolean readyToNormalize = false; + boolean calculatedB0 = false; + + // --------------------------------------------------------------------- + // Recur backward via difference equation, calculating (but not + // storing) B(N), until N = NB. + // --------------------------------------------------------------------- + for (int l = 1; l <= nend; l++) { + n -= 1; + en -= 2.0; + tempc = tempb; + tempb = tempa; + tempa = (en * tempb / x) - tempc; + m = 2 - m; + if (m != 0) { + em -= 1; + alp2em = 2 * em + alpha; + if (n == 1) { + break; + } + alpem = em - 1 + alpha; + if (alpem == 0) { + alpem = 1; + } + sum = (sum + tempa * alp2em) * alpem / em; + } + } + + // --------------------------------------------------------------------- + // Store B(NB). + // --------------------------------------------------------------------- + b[n - 1] = tempa; + if (nend >= 0) { + if (nb <= 1) { + alp2em = alpha; + if (alpha + 1 == 1) { + alp2em = 1; + } + sum += b[0] * alp2em; + readyToNormalize = true; + } else { + // --------------------------------------------------------------------- + // Calculate and store B(NB-1). + // --------------------------------------------------------------------- + n -= 1; + en -= 2.0; + b[n - 1] = (en * tempa / x) - tempb; + if (n == 1) { + calculatedB0 = true; + } else { + m = 2 - m; + if (m != 0) { + em -= 1; + alp2em = 2 * em + alpha; + alpem = em - 1 + alpha; + if (alpem == 0) { + alpem = 1; + } + + sum = (sum + (b[n - 1] * alp2em)) * alpem / em; + } + } + } + } + if (!readyToNormalize && !calculatedB0) { + nend = n - 2; + if (nend != 0) { + // --------------------------------------------------------------------- + // Calculate via difference equation and store B(N), + // until N = 2. + // --------------------------------------------------------------------- + + for (int l = 1; l <= nend; l++) { + n -= 1; + en -= 2.0; + b[n - 1] = (en * b[n] / x) - b[n + 1]; + m = 2 - m; + if (m != 0) { + em -= 1; + alp2em = 2 * em + alpha; + alpem = em - 1 + alpha; + if (alpem == 0) { + alpem = 1; + } + + sum = (sum + b[n - 1] * alp2em) * alpem / em; + } + } + } + } + // --------------------------------------------------------------------- + // Calculate b[0] + // --------------------------------------------------------------------- + if (!readyToNormalize) { + if (!calculatedB0) { + b[0] = 2.0 * (alpha + 1) * b[1] / x - b[2]; + } + em -= 1; + alp2em = 2 * em + alpha; + if (alp2em == 0) { + alp2em = 1; + } + sum += b[0] * alp2em; + } + // --------------------------------------------------------------------- + // Normalize. Divide all B(N) by sum. + // --------------------------------------------------------------------- + + if (FastMath.abs(alpha) > 1e-16) { + sum *= Gamma.gamma(alpha) * FastMath.pow(x * 0.5, -alpha); + } + tempa = ENMTEN; + if (sum > 1) { + tempa *= sum; + } + + for (n = 0; n < nb; n++) { + if (FastMath.abs(b[n]) < tempa) { + b[n] = 0; + } + b[n] /= sum; + } + } + // --------------------------------------------------------------------- + // Error return -- X, NB, or ALPHA is out of range. + // --------------------------------------------------------------------- + } else { + if (b.length > 0) { + b[0] = 0; + } + ncalc = FastMath.min(nb, 0) - 1; + } + return new BesselJResult(MathArrays.copyOf(b, b.length), ncalc); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Beta.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Beta.java new file mode 100644 index 000000000..ef2e51f71 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Beta.java @@ -0,0 +1,514 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.special; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.ContinuedFraction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + *

        + * This is a utility class that provides computation methods related to the + * Beta family of functions. + *

        + *

        + * Implementation of {@link #logBeta(double, double)} is based on the + * algorithms described in + *

        + * and implemented in the + * NSWC Library of Mathematical Functions, + * available + * here. + * This library is "approved for public release", and the + * Copyright guidance + * indicates that unless otherwise stated in the code, all FORTRAN functions in + * this library are license free. Since no such notice appears in the code these + * functions can safely be ported to Commons-Math. + *

        + * + * + */ +public class Beta { + /** Maximum allowed numerical error. */ + private static final double DEFAULT_EPSILON = 1E-14; + + /** The constant value of ½log 2π. */ + private static final double HALF_LOG_TWO_PI = .9189385332046727; + + /** + *

        + * The coefficients of the series expansion of the Δ function. This function + * is defined as follows + *

        + *
        Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π,
        + *

        + * see equation (23) in Didonato and Morris (1992). The series expansion, + * which applies for x ≥ 10, reads + *

        + *
        +     *                 14
        +     *                ====
        +     *             1  \                2 n
        +     *     Δ(x) = ---  >    d  (10 / x)
        +     *             x  /      n
        +     *                ====
        +     *                n = 0
        +     * 
        +     */
        +    private static final double[] DELTA = {
        +        .833333333333333333333333333333E-01,
        +        -.277777777777777777777777752282E-04,
        +        .793650793650793650791732130419E-07,
        +        -.595238095238095232389839236182E-09,
        +        .841750841750832853294451671990E-11,
        +        -.191752691751854612334149171243E-12,
        +        .641025640510325475730918472625E-14,
        +        -.295506514125338232839867823991E-15,
        +        .179643716359402238723287696452E-16,
        +        -.139228964661627791231203060395E-17,
        +        .133802855014020915603275339093E-18,
        +        -.154246009867966094273710216533E-19,
        +        .197701992980957427278370133333E-20,
        +        -.234065664793997056856992426667E-21,
        +        .171348014966398575409015466667E-22
        +    };
        +
        +    /**
        +     * Default constructor.  Prohibit instantiation.
        +     */
        +    private Beta() {}
        +
        +    /**
        +     * Returns the
        +     * 
        +     * regularized beta function I(x, a, b).
        +     *
        +     * @param x Value.
        +     * @param a Parameter {@code a}.
        +     * @param b Parameter {@code b}.
        +     * @return the regularized beta function I(x, a, b).
        +     * @throws MaxCountExceededException
        +     * if the algorithm fails to converge.
        +     */
        +    public static double regularizedBeta(double x, double a, double b) {
        +        return regularizedBeta(x, a, b, DEFAULT_EPSILON, Integer.MAX_VALUE);
        +    }
        +
        +    /**
        +     * Returns the
        +     * 
        +     * regularized beta function I(x, a, b).
        +     *
        +     * @param x Value.
        +     * @param a Parameter {@code a}.
        +     * @param b Parameter {@code b}.
        +     * @param epsilon When the absolute value of the nth item in the
        +     * series is less than epsilon the approximation ceases to calculate
        +     * further elements in the series.
        +     * @return the regularized beta function I(x, a, b)
        +     * @throws MaxCountExceededException
        +     * if the algorithm fails to converge.
        +     */
        +    public static double regularizedBeta(double x,
        +                                         double a, double b,
        +                                         double epsilon) {
        +        return regularizedBeta(x, a, b, epsilon, Integer.MAX_VALUE);
        +    }
        +
        +    /**
        +     * Returns the regularized beta function I(x, a, b).
        +     *
        +     * @param x the value.
        +     * @param a Parameter {@code a}.
        +     * @param b Parameter {@code b}.
        +     * @param maxIterations Maximum number of "iterations" to complete.
        +     * @return the regularized beta function I(x, a, b)
        +     * @throws MaxCountExceededException
        +     * if the algorithm fails to converge.
        +     */
        +    public static double regularizedBeta(double x,
        +                                         double a, double b,
        +                                         int maxIterations) {
        +        return regularizedBeta(x, a, b, DEFAULT_EPSILON, maxIterations);
        +    }
        +
        +    /**
        +     * Returns the regularized beta function I(x, a, b).
        +     *
        +     * The implementation of this method is based on:
        +     * 
        +     *
        +     * @param x the value.
        +     * @param a Parameter {@code a}.
        +     * @param b Parameter {@code b}.
        +     * @param epsilon When the absolute value of the nth item in the
        +     * series is less than epsilon the approximation ceases to calculate
        +     * further elements in the series.
        +     * @param maxIterations Maximum number of "iterations" to complete.
        +     * @return the regularized beta function I(x, a, b)
        +     * @throws MaxCountExceededException
        +     * if the algorithm fails to converge.
        +     */
        +    public static double regularizedBeta(double x,
        +                                         final double a, final double b,
        +                                         double epsilon, int maxIterations) {
        +        double ret;
        +
        +        if (Double.isNaN(x) ||
        +            Double.isNaN(a) ||
        +            Double.isNaN(b) ||
        +            x < 0 ||
        +            x > 1 ||
        +            a <= 0 ||
        +            b <= 0) {
        +            ret = Double.NaN;
        +        } else if (x > (a + 1) / (2 + b + a) &&
        +                   1 - x <= (b + 1) / (2 + b + a)) {
        +            ret = 1 - regularizedBeta(1 - x, b, a, epsilon, maxIterations);
        +        } else {
        +            ContinuedFraction fraction = new ContinuedFraction() {
        +
        +                /** {@inheritDoc} */
        +                @Override
        +                protected double getB(int n, double x) {
        +                    double ret;
        +                    double m;
        +                    if (n % 2 == 0) { // even
        +                        m = n / 2.0;
        +                        ret = (m * (b - m) * x) /
        +                            ((a + (2 * m) - 1) * (a + (2 * m)));
        +                    } else {
        +                        m = (n - 1.0) / 2.0;
        +                        ret = -((a + m) * (a + b + m) * x) /
        +                                ((a + (2 * m)) * (a + (2 * m) + 1.0));
        +                    }
        +                    return ret;
        +                }
        +
        +                /** {@inheritDoc} */
        +                @Override
        +                protected double getA(int n, double x) {
        +                    return 1.0;
        +                }
        +            };
        +            ret = FastMath.exp((a * FastMath.log(x)) + (b * FastMath.log1p(-x)) -
        +                FastMath.log(a) - logBeta(a, b)) *
        +                1.0 / fraction.evaluate(x, epsilon, maxIterations);
        +        }
        +
        +        return ret;
        +    }
        +
        +    /**
        +     * Returns the natural logarithm of the beta function B(a, b).
        +     *
        +     * The implementation of this method is based on:
        +     * 
        +     *
        +     * @param a Parameter {@code a}.
        +     * @param b Parameter {@code b}.
        +     * @param epsilon This parameter is ignored.
        +     * @param maxIterations This parameter is ignored.
        +     * @return log(B(a, b)).
        +     * @deprecated as of version 3.1, this method is deprecated as the
        +     * computation of the beta function is no longer iterative; it will be
        +     * removed in version 4.0. Current implementation of this method
        +     * internally calls {@link #logBeta(double, double)}.
        +     */
        +    @Deprecated
        +    public static double logBeta(double a, double b,
        +                                 double epsilon,
        +                                 int maxIterations) {
        +
        +        return logBeta(a, b);
        +    }
        +
        +
        +    /**
        +     * Returns the value of log Γ(a + b) for 1 ≤ a, b ≤ 2. Based on the
        +     * NSWC Library of Mathematics Subroutines double precision
        +     * implementation, {@code DGSMLN}. In {@code BetaTest.testLogGammaSum()},
        +     * this private method is accessed through reflection.
        +     *
        +     * @param a First argument.
        +     * @param b Second argument.
        +     * @return the value of {@code log(Gamma(a + b))}.
        +     * @throws OutOfRangeException if {@code a} or {@code b} is lower than
        +     * {@code 1.0} or greater than {@code 2.0}.
        +     */
        +    private static double logGammaSum(final double a, final double b)
        +        throws OutOfRangeException {
        +
        +        if ((a < 1.0) || (a > 2.0)) {
        +            throw new OutOfRangeException(a, 1.0, 2.0);
        +        }
        +        if ((b < 1.0) || (b > 2.0)) {
        +            throw new OutOfRangeException(b, 1.0, 2.0);
        +        }
        +
        +        final double x = (a - 1.0) + (b - 1.0);
        +        if (x <= 0.5) {
        +            return Gamma.logGamma1p(1.0 + x);
        +        } else if (x <= 1.5) {
        +            return Gamma.logGamma1p(x) + FastMath.log1p(x);
        +        } else {
        +            return Gamma.logGamma1p(x - 1.0) + FastMath.log(x * (1.0 + x));
        +        }
        +    }
        +
        +    /**
        +     * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. Based on
        +     * the NSWC Library of Mathematics Subroutines double precision
        +     * implementation, {@code DLGDIV}. In
        +     * {@code BetaTest.testLogGammaMinusLogGammaSum()}, this private method is
        +     * accessed through reflection.
        +     *
        +     * @param a First argument.
        +     * @param b Second argument.
        +     * @return the value of {@code log(Gamma(b) / Gamma(a + b))}.
        +     * @throws NumberIsTooSmallException if {@code a < 0.0} or {@code b < 10.0}.
        +     */
        +    private static double logGammaMinusLogGammaSum(final double a,
        +                                                   final double b)
        +        throws NumberIsTooSmallException {
        +
        +        if (a < 0.0) {
        +            throw new NumberIsTooSmallException(a, 0.0, true);
        +        }
        +        if (b < 10.0) {
        +            throw new NumberIsTooSmallException(b, 10.0, true);
        +        }
        +
        +        /*
        +         * d = a + b - 0.5
        +         */
        +        final double d;
        +        final double w;
        +        if (a <= b) {
        +            d = b + (a - 0.5);
        +            w = deltaMinusDeltaSum(a, b);
        +        } else {
        +            d = a + (b - 0.5);
        +            w = deltaMinusDeltaSum(b, a);
        +        }
        +
        +        final double u = d * FastMath.log1p(a / b);
        +        final double v = a * (FastMath.log(b) - 1.0);
        +
        +        return u <= v ? (w - u) - v : (w - v) - u;
        +    }
        +
        +    /**
        +     * Returns the value of Δ(b) - Δ(a + b), with 0 ≤ a ≤ b and b ≥ 10. Based
        +     * on equations (26), (27) and (28) in Didonato and Morris (1992).
        +     *
        +     * @param a First argument.
        +     * @param b Second argument.
        +     * @return the value of {@code Delta(b) - Delta(a + b)}
        +     * @throws OutOfRangeException if {@code a < 0} or {@code a > b}
        +     * @throws NumberIsTooSmallException if {@code b < 10}
        +     */
        +    private static double deltaMinusDeltaSum(final double a,
        +                                             final double b)
        +        throws OutOfRangeException, NumberIsTooSmallException {
        +
        +        if ((a < 0) || (a > b)) {
        +            throw new OutOfRangeException(a, 0, b);
        +        }
        +        if (b < 10) {
        +            throw new NumberIsTooSmallException(b, 10, true);
        +        }
        +
        +        final double h = a / b;
        +        final double p = h / (1.0 + h);
        +        final double q = 1.0 / (1.0 + h);
        +        final double q2 = q * q;
        +        /*
        +         * s[i] = 1 + q + ... - q**(2 * i)
        +         */
        +        final double[] s = new double[DELTA.length];
        +        s[0] = 1.0;
        +        for (int i = 1; i < s.length; i++) {
        +            s[i] = 1.0 + (q + q2 * s[i - 1]);
        +        }
        +        /*
        +         * w = Delta(b) - Delta(a + b)
        +         */
        +        final double sqrtT = 10.0 / b;
        +        final double t = sqrtT * sqrtT;
        +        double w = DELTA[DELTA.length - 1] * s[s.length - 1];
        +        for (int i = DELTA.length - 2; i >= 0; i--) {
        +            w = t * w + DELTA[i] * s[i];
        +        }
        +        return w * p / b;
        +    }
        +
        +    /**
        +     * Returns the value of Δ(p) + Δ(q) - Δ(p + q), with p, q ≥ 10. Based on
        +     * the NSWC Library of Mathematics Subroutines double precision
        +     * implementation, {@code DBCORR}. In
        +     * {@code BetaTest.testSumDeltaMinusDeltaSum()}, this private method is
        +     * accessed through reflection.
        +     *
        +     * @param p First argument.
        +     * @param q Second argument.
        +     * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}.
        +     * @throws NumberIsTooSmallException if {@code p < 10.0} or {@code q < 10.0}.
        +     */
        +    private static double sumDeltaMinusDeltaSum(final double p,
        +                                                final double q) {
        +
        +        if (p < 10.0) {
        +            throw new NumberIsTooSmallException(p, 10.0, true);
        +        }
        +        if (q < 10.0) {
        +            throw new NumberIsTooSmallException(q, 10.0, true);
        +        }
        +
        +        final double a = FastMath.min(p, q);
        +        final double b = FastMath.max(p, q);
        +        final double sqrtT = 10.0 / a;
        +        final double t = sqrtT * sqrtT;
        +        double z = DELTA[DELTA.length - 1];
        +        for (int i = DELTA.length - 2; i >= 0; i--) {
        +            z = t * z + DELTA[i];
        +        }
        +        return z / a + deltaMinusDeltaSum(a, b);
        +    }
        +
        +    /**
        +     * Returns the value of log B(p, q) for 0 ≤ x ≤ 1 and p, q > 0. Based on the
        +     * NSWC Library of Mathematics Subroutines implementation,
        +     * {@code DBETLN}.
        +     *
        +     * @param p First argument.
        +     * @param q Second argument.
        +     * @return the value of {@code log(Beta(p, q))}, {@code NaN} if
        +     * {@code p <= 0} or {@code q <= 0}.
        +     */
        +    public static double logBeta(final double p, final double q) {
        +        if (Double.isNaN(p) || Double.isNaN(q) || (p <= 0.0) || (q <= 0.0)) {
        +            return Double.NaN;
        +        }
        +
        +        final double a = FastMath.min(p, q);
        +        final double b = FastMath.max(p, q);
        +        if (a >= 10.0) {
        +            final double w = sumDeltaMinusDeltaSum(a, b);
        +            final double h = a / b;
        +            final double c = h / (1.0 + h);
        +            final double u = -(a - 0.5) * FastMath.log(c);
        +            final double v = b * FastMath.log1p(h);
        +            if (u <= v) {
        +                return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - u) - v;
        +            } else {
        +                return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - v) - u;
        +            }
        +        } else if (a > 2.0) {
        +            if (b > 1000.0) {
        +                final int n = (int) FastMath.floor(a - 1.0);
        +                double prod = 1.0;
        +                double ared = a;
        +                for (int i = 0; i < n; i++) {
        +                    ared -= 1.0;
        +                    prod *= ared / (1.0 + ared / b);
        +                }
        +                return (FastMath.log(prod) - n * FastMath.log(b)) +
        +                        (Gamma.logGamma(ared) +
        +                         logGammaMinusLogGammaSum(ared, b));
        +            } else {
        +                double prod1 = 1.0;
        +                double ared = a;
        +                while (ared > 2.0) {
        +                    ared -= 1.0;
        +                    final double h = ared / b;
        +                    prod1 *= h / (1.0 + h);
        +                }
        +                if (b < 10.0) {
        +                    double prod2 = 1.0;
        +                    double bred = b;
        +                    while (bred > 2.0) {
        +                        bred -= 1.0;
        +                        prod2 *= bred / (ared + bred);
        +                    }
        +                    return FastMath.log(prod1) +
        +                           FastMath.log(prod2) +
        +                           (Gamma.logGamma(ared) +
        +                           (Gamma.logGamma(bred) -
        +                            logGammaSum(ared, bred)));
        +                } else {
        +                    return FastMath.log(prod1) +
        +                           Gamma.logGamma(ared) +
        +                           logGammaMinusLogGammaSum(ared, b);
        +                }
        +            }
        +        } else if (a >= 1.0) {
        +            if (b > 2.0) {
        +                if (b < 10.0) {
        +                    double prod = 1.0;
        +                    double bred = b;
        +                    while (bred > 2.0) {
        +                        bred -= 1.0;
        +                        prod *= bred / (a + bred);
        +                    }
        +                    return FastMath.log(prod) +
        +                           (Gamma.logGamma(a) +
        +                            (Gamma.logGamma(bred) -
        +                             logGammaSum(a, bred)));
        +                } else {
        +                    return Gamma.logGamma(a) +
        +                           logGammaMinusLogGammaSum(a, b);
        +                }
        +            } else {
        +                return Gamma.logGamma(a) +
        +                       Gamma.logGamma(b) -
        +                       logGammaSum(a, b);
        +            }
        +        } else {
        +            if (b >= 10.0) {
        +                return Gamma.logGamma(a) +
        +                       logGammaMinusLogGammaSum(a, b);
        +            } else {
        +                // The following command is the original NSWC implementation.
        +                // return Gamma.logGamma(a) +
        +                // (Gamma.logGamma(b) - Gamma.logGamma(a + b));
        +                // The following command turns out to be more accurate.
        +                return FastMath.log(Gamma.gamma(a) * Gamma.gamma(b) /
        +                                    Gamma.gamma(a + b));
        +            }
        +        }
        +    }
        +}
        diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Erf.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Erf.java
        new file mode 100644
        index 000000000..859ed8d9b
        --- /dev/null
        +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Erf.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 com.fr.third.org.apache.commons.math3.special;
        +
        +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException;
        +import com.fr.third.org.apache.commons.math3.util.FastMath;
        +
        +/**
        + * This is a utility class that provides computation methods related to the
        + * error functions.
        + *
        + */
        +public class Erf {
        +
        +    /**
        +     * The number {@code X_CRIT} is used by {@link #erf(double, double)} internally.
        +     * This number solves {@code erf(x)=0.5} within 1ulp.
        +     * More precisely, the current implementations of
        +     * {@link #erf(double)} and {@link #erfc(double)} satisfy:
        + * {@code erf(X_CRIT) < 0.5},
        + * {@code erf(Math.nextUp(X_CRIT) > 0.5},
        + * {@code erfc(X_CRIT) = 0.5}, and
        + * {@code erfc(Math.nextUp(X_CRIT) < 0.5} + */ + private static final double X_CRIT = 0.4769362762044697; + + /** + * Default constructor. Prohibit instantiation. + */ + private Erf() {} + + /** + * Returns the error function. + * + *

        erf(x) = 2/√π 0x e-t2dt

        + * + *

        This implementation computes erf(x) using the + * {@link Gamma#regularizedGammaP(double, double, double, int) regularized gamma function}, + * following Erf, equation (3)

        + * + *

        The value returned is always between -1 and 1 (inclusive). + * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from + * either 1 or -1 as a double, so the appropriate extreme value is returned. + *

        + * + * @param x the value. + * @return the error function erf(x) + * @throws MaxCountExceededException + * if the algorithm fails to converge. + * @see Gamma#regularizedGammaP(double, double, double, int) + */ + public static double erf(double x) { + if (FastMath.abs(x) > 40) { + return x > 0 ? 1 : -1; + } + final double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000); + return x < 0 ? -ret : ret; + } + + /** + * Returns the complementary error function. + * + *

        erfc(x) = 2/√π x e-t2dt + *
        + * = 1 - {@link #erf(double) erf(x)}

        + * + *

        This implementation computes erfc(x) using the + * {@link Gamma#regularizedGammaQ(double, double, double, int) regularized gamma function}, + * following Erf, equation (3).

        + * + *

        The value returned is always between 0 and 2 (inclusive). + * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from + * either 0 or 2 as a double, so the appropriate extreme value is returned. + *

        + * + * @param x the value + * @return the complementary error function erfc(x) + * @throws MaxCountExceededException + * if the algorithm fails to converge. + * @see Gamma#regularizedGammaQ(double, double, double, int) + * @since 2.2 + */ + public static double erfc(double x) { + if (FastMath.abs(x) > 40) { + return x > 0 ? 0 : 2; + } + final double ret = Gamma.regularizedGammaQ(0.5, x * x, 1.0e-15, 10000); + return x < 0 ? 2 - ret : ret; + } + + /** + * Returns the difference between erf(x1) and erf(x2). + * + * The implementation uses either erf(double) or erfc(double) + * depending on which provides the most precise result. + * + * @param x1 the first value + * @param x2 the second value + * @return erf(x2) - erf(x1) + */ + public static double erf(double x1, double x2) { + if(x1 > x2) { + return -erf(x2, x1); + } + + return + x1 < -X_CRIT ? + x2 < 0.0 ? + erfc(-x2) - erfc(-x1) : + erf(x2) - erf(x1) : + x2 > X_CRIT && x1 > 0.0 ? + erfc(x1) - erfc(x2) : + erf(x2) - erf(x1); + } + + /** + * Returns the inverse erf. + *

        + * This implementation is described in the paper: + * Approximating + * the erfinv function by Mike Giles, Oxford-Man Institute of Quantitative Finance, + * which was published in GPU Computing Gems, volume 2, 2010. + * The source code is available here. + *

        + * @param x the value + * @return t such that x = erf(t) + * @since 3.2 + */ + public static double erfInv(final double x) { + + // beware that the logarithm argument must be + // commputed as (1.0 - x) * (1.0 + x), + // it must NOT be simplified as 1.0 - x * x as this + // would induce rounding errors near the boundaries +/-1 + double w = - FastMath.log((1.0 - x) * (1.0 + x)); + double p; + + if (w < 6.25) { + w -= 3.125; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.0) { + w = FastMath.sqrt(w) - 3.25; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else if (!Double.isInfinite(w)) { + w = FastMath.sqrt(w) - 5.0; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } else { + // this branch does not appears in the original code, it + // was added because the previous branch does not handle + // x = +/-1 correctly. In this case, w is positive infinity + // and as the first coefficient (-2.71e-11) is negative. + // Once the first multiplication is done, p becomes negative + // infinity and remains so throughout the polynomial evaluation. + // So the branch above incorrectly returns negative infinity + // instead of the correct positive infinity. + p = Double.POSITIVE_INFINITY; + } + + return p * x; + + } + + /** + * Returns the inverse erfc. + * @param x the value + * @return t such that x = erfc(t) + * @since 3.2 + */ + public static double erfcInv(final double x) { + return erfInv(1 - x); + } + +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Gamma.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Gamma.java new file mode 100644 index 000000000..2eef871d4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/Gamma.java @@ -0,0 +1,720 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.special; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.util.ContinuedFraction; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + *

        + * This is a utility class that provides computation methods related to the + * Γ (Gamma) family of functions. + *

        + *

        + * Implementation of {@link #invGamma1pm1(double)} and + * {@link #logGamma1p(double)} is based on the algorithms described in + *

        + * and implemented in the + * NSWC Library of Mathematical Functions, + * available + * here. + * This library is "approved for public release", and the + * Copyright guidance + * indicates that unless otherwise stated in the code, all FORTRAN functions in + * this library are license free. Since no such notice appears in the code these + * functions can safely be ported to Commons-Math. + *

        + * + */ +public class Gamma { + /** + * Euler-Mascheroni constant + * @since 2.0 + */ + public static final double GAMMA = 0.577215664901532860606512090082; + + /** + * The value of the {@code g} constant in the Lanczos approximation, see + * {@link #lanczos(double)}. + * @since 3.1 + */ + public static final double LANCZOS_G = 607.0 / 128.0; + + /** Maximum allowed numerical error. */ + private static final double DEFAULT_EPSILON = 10e-15; + + /** Lanczos coefficients */ + private static final double[] LANCZOS = { + 0.99999999999999709182, + 57.156235665862923517, + -59.597960355475491248, + 14.136097974741747174, + -0.49191381609762019978, + .33994649984811888699e-4, + .46523628927048575665e-4, + -.98374475304879564677e-4, + .15808870322491248884e-3, + -.21026444172410488319e-3, + .21743961811521264320e-3, + -.16431810653676389022e-3, + .84418223983852743293e-4, + -.26190838401581408670e-4, + .36899182659531622704e-5, + }; + + /** Avoid repeated computation of log of 2 PI in logGamma */ + private static final double HALF_LOG_2_PI = 0.5 * FastMath.log(2.0 * FastMath.PI); + + /** The constant value of √(2π). */ + private static final double SQRT_TWO_PI = 2.506628274631000502; + + // limits for switching algorithm in digamma + /** C limit. */ + private static final double C_LIMIT = 49; + + /** S limit. */ + private static final double S_LIMIT = 1e-5; + + /* + * Constants for the computation of double invGamma1pm1(double). + * Copied from DGAM1 in the NSWC library. + */ + + /** The constant {@code A0} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_A0 = .611609510448141581788E-08; + + /** The constant {@code A1} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_A1 = .624730830116465516210E-08; + + /** The constant {@code B1} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B1 = .203610414066806987300E+00; + + /** The constant {@code B2} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B2 = .266205348428949217746E-01; + + /** The constant {@code B3} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B3 = .493944979382446875238E-03; + + /** The constant {@code B4} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B4 = -.851419432440314906588E-05; + + /** The constant {@code B5} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B5 = -.643045481779353022248E-05; + + /** The constant {@code B6} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B6 = .992641840672773722196E-06; + + /** The constant {@code B7} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B7 = -.607761895722825260739E-07; + + /** The constant {@code B8} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_B8 = .195755836614639731882E-09; + + /** The constant {@code P0} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P0 = .6116095104481415817861E-08; + + /** The constant {@code P1} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P1 = .6871674113067198736152E-08; + + /** The constant {@code P2} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P2 = .6820161668496170657918E-09; + + /** The constant {@code P3} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P3 = .4686843322948848031080E-10; + + /** The constant {@code P4} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P4 = .1572833027710446286995E-11; + + /** The constant {@code P5} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P5 = -.1249441572276366213222E-12; + + /** The constant {@code P6} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_P6 = .4343529937408594255178E-14; + + /** The constant {@code Q1} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_Q1 = .3056961078365221025009E+00; + + /** The constant {@code Q2} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_Q2 = .5464213086042296536016E-01; + + /** The constant {@code Q3} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_Q3 = .4956830093825887312020E-02; + + /** The constant {@code Q4} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_Q4 = .2692369466186361192876E-03; + + /** The constant {@code C} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C = -.422784335098467139393487909917598E+00; + + /** The constant {@code C0} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C0 = .577215664901532860606512090082402E+00; + + /** The constant {@code C1} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C1 = -.655878071520253881077019515145390E+00; + + /** The constant {@code C2} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C2 = -.420026350340952355290039348754298E-01; + + /** The constant {@code C3} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C3 = .166538611382291489501700795102105E+00; + + /** The constant {@code C4} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C4 = -.421977345555443367482083012891874E-01; + + /** The constant {@code C5} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C5 = -.962197152787697356211492167234820E-02; + + /** The constant {@code C6} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C6 = .721894324666309954239501034044657E-02; + + /** The constant {@code C7} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C7 = -.116516759185906511211397108401839E-02; + + /** The constant {@code C8} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C8 = -.215241674114950972815729963053648E-03; + + /** The constant {@code C9} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C9 = .128050282388116186153198626328164E-03; + + /** The constant {@code C10} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C10 = -.201348547807882386556893914210218E-04; + + /** The constant {@code C11} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C11 = -.125049348214267065734535947383309E-05; + + /** The constant {@code C12} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C12 = .113302723198169588237412962033074E-05; + + /** The constant {@code C13} defined in {@code DGAM1}. */ + private static final double INV_GAMMA1P_M1_C13 = -.205633841697760710345015413002057E-06; + + /** + * Default constructor. Prohibit instantiation. + */ + private Gamma() {} + + /** + *

        + * Returns the value of log Γ(x) for x > 0. + *

        + *

        + * For x ≤ 8, the implementation is based on the double precision + * implementation in the NSWC Library of Mathematics Subroutines, + * {@code DGAMLN}. For x > 8, the implementation is based on + *

        + * + * + * @param x Argument. + * @return the value of {@code log(Gamma(x))}, {@code Double.NaN} if + * {@code x <= 0.0}. + */ + public static double logGamma(double x) { + double ret; + + if (Double.isNaN(x) || (x <= 0.0)) { + ret = Double.NaN; + } else if (x < 0.5) { + return logGamma1p(x) - FastMath.log(x); + } else if (x <= 2.5) { + return logGamma1p((x - 0.5) - 0.5); + } else if (x <= 8.0) { + final int n = (int) FastMath.floor(x - 1.5); + double prod = 1.0; + for (int i = 1; i <= n; i++) { + prod *= x - i; + } + return logGamma1p(x - (n + 1)) + FastMath.log(prod); + } else { + double sum = lanczos(x); + double tmp = x + LANCZOS_G + .5; + ret = ((x + .5) * FastMath.log(tmp)) - tmp + + HALF_LOG_2_PI + FastMath.log(sum / x); + } + + return ret; + } + + /** + * Returns the regularized gamma function P(a, x). + * + * @param a Parameter. + * @param x Value. + * @return the regularized gamma function P(a, x). + * @throws MaxCountExceededException if the algorithm fails to converge. + */ + public static double regularizedGammaP(double a, double x) { + return regularizedGammaP(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE); + } + + /** + * Returns the regularized gamma function P(a, x). + * + * The implementation of this method is based on: + * + * + * @param a the a parameter. + * @param x the value. + * @param epsilon When the absolute value of the nth item in the + * series is less than epsilon the approximation ceases to calculate + * further elements in the series. + * @param maxIterations Maximum number of "iterations" to complete. + * @return the regularized gamma function P(a, x) + * @throws MaxCountExceededException if the algorithm fails to converge. + */ + public static double regularizedGammaP(double a, + double x, + double epsilon, + int maxIterations) { + double ret; + + if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) { + ret = Double.NaN; + } else if (x == 0.0) { + ret = 0.0; + } else if (x >= a + 1) { + // use regularizedGammaQ because it should converge faster in this + // case. + ret = 1.0 - regularizedGammaQ(a, x, epsilon, maxIterations); + } else { + // calculate series + double n = 0.0; // current element index + double an = 1.0 / a; // n-th element in the series + double sum = an; // partial sum + while (FastMath.abs(an/sum) > epsilon && + n < maxIterations && + sum < Double.POSITIVE_INFINITY) { + // compute next element in the series + n += 1.0; + an *= x / (a + n); + + // update partial sum + sum += an; + } + if (n >= maxIterations) { + throw new MaxCountExceededException(maxIterations); + } else if (Double.isInfinite(sum)) { + ret = 1.0; + } else { + ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGamma(a)) * sum; + } + } + + return ret; + } + + /** + * Returns the regularized gamma function Q(a, x) = 1 - P(a, x). + * + * @param a the a parameter. + * @param x the value. + * @return the regularized gamma function Q(a, x) + * @throws MaxCountExceededException if the algorithm fails to converge. + */ + public static double regularizedGammaQ(double a, double x) { + return regularizedGammaQ(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE); + } + + /** + * Returns the regularized gamma function Q(a, x) = 1 - P(a, x). + * + * The implementation of this method is based on: + * + * + * @param a the a parameter. + * @param x the value. + * @param epsilon When the absolute value of the nth item in the + * series is less than epsilon the approximation ceases to calculate + * further elements in the series. + * @param maxIterations Maximum number of "iterations" to complete. + * @return the regularized gamma function P(a, x) + * @throws MaxCountExceededException if the algorithm fails to converge. + */ + public static double regularizedGammaQ(final double a, + double x, + double epsilon, + int maxIterations) { + double ret; + + if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) { + ret = Double.NaN; + } else if (x == 0.0) { + ret = 1.0; + } else if (x < a + 1.0) { + // use regularizedGammaP because it should converge faster in this + // case. + ret = 1.0 - regularizedGammaP(a, x, epsilon, maxIterations); + } else { + // create continued fraction + ContinuedFraction cf = new ContinuedFraction() { + + /** {@inheritDoc} */ + @Override + protected double getA(int n, double x) { + return ((2.0 * n) + 1.0) - a + x; + } + + /** {@inheritDoc} */ + @Override + protected double getB(int n, double x) { + return n * (a - n); + } + }; + + ret = 1.0 / cf.evaluate(x, epsilon, maxIterations); + ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGamma(a)) * ret; + } + + return ret; + } + + + /** + *

        Computes the digamma function of x.

        + * + *

        This is an independently written implementation of the algorithm described in + * Jose Bernardo, Algorithm AS 103: Psi (Digamma) Function, Applied Statistics, 1976.

        + * + *

        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.

        + * + *

        Performance for large negative values of x will be quite expensive (proportional to + * |x|). Accuracy for negative values of x should be about 10^-8 absolute for results + * less than 10^5 and 10^-8 relative for results larger than that.

        + * + * @param x Argument. + * @return digamma(x) to within 10-8 relative or absolute error whichever is smaller. + * @see Digamma + * @see Bernardo's original article + * @since 2.0 + */ + public static double digamma(double x) { + if (Double.isNaN(x) || Double.isInfinite(x)) { + return x; + } + + if (x > 0 && x <= S_LIMIT) { + // use method 5 from Bernardo AS103 + // accurate to O(x) + return -GAMMA - 1 / x; + } + + if (x >= C_LIMIT) { + // use method 4 (accurate to O(1/x^8) + double inv = 1 / (x * x); + // 1 1 1 1 + // log(x) - --- - ------ + ------- - ------- + // 2 x 12 x^2 120 x^4 252 x^6 + return FastMath.log(x) - 0.5 / x - inv * ((1.0 / 12) + inv * (1.0 / 120 - inv / 252)); + } + + return digamma(x + 1) - 1 / x; + } + + /** + * Computes the trigamma function of x. + * This function is derived by taking the derivative of the implementation + * of digamma. + * + * @param x Argument. + * @return trigamma(x) to within 10-8 relative or absolute error whichever is smaller + * @see Trigamma + * @see Gamma#digamma(double) + * @since 2.0 + */ + public static double trigamma(double x) { + if (Double.isNaN(x) || Double.isInfinite(x)) { + return x; + } + + if (x > 0 && x <= S_LIMIT) { + return 1 / (x * x); + } + + if (x >= C_LIMIT) { + double inv = 1 / (x * x); + // 1 1 1 1 1 + // - + ---- + ---- - ----- + ----- + // x 2 3 5 7 + // 2 x 6 x 30 x 42 x + return 1 / x + inv / 2 + inv / x * (1.0 / 6 - inv * (1.0 / 30 + inv / 42)); + } + + return trigamma(x + 1) + 1 / (x * x); + } + + /** + *

        + * Returns the Lanczos approximation used to compute the gamma function. + * The Lanczos approximation is related to the Gamma function by the + * following equation + *

        + * {@code gamma(x) = sqrt(2 * pi) / x * (x + g + 0.5) ^ (x + 0.5) + * * exp(-x - g - 0.5) * lanczos(x)}, + *
        + * where {@code g} is the Lanczos constant. + *

        + * + * @param x Argument. + * @return The Lanczos approximation. + * @see Lanczos Approximation + * equations (1) through (5), and Paul Godfrey's + * Note on the computation + * of the convergent Lanczos complex Gamma approximation + * @since 3.1 + */ + public static double lanczos(final double x) { + double sum = 0.0; + for (int i = LANCZOS.length - 1; i > 0; --i) { + sum += LANCZOS[i] / (x + i); + } + return sum + LANCZOS[0]; + } + + /** + * Returns the value of 1 / Γ(1 + x) - 1 for -0.5 ≤ x ≤ + * 1.5. This implementation is based on the double precision + * implementation in the NSWC Library of Mathematics Subroutines, + * {@code DGAM1}. + * + * @param x Argument. + * @return The value of {@code 1.0 / Gamma(1.0 + x) - 1.0}. + * @throws NumberIsTooSmallException if {@code x < -0.5} + * @throws NumberIsTooLargeException if {@code x > 1.5} + * @since 3.1 + */ + public static double invGamma1pm1(final double x) { + + if (x < -0.5) { + throw new NumberIsTooSmallException(x, -0.5, true); + } + if (x > 1.5) { + throw new NumberIsTooLargeException(x, 1.5, true); + } + + final double ret; + final double t = x <= 0.5 ? x : (x - 0.5) - 0.5; + if (t < 0.0) { + final double a = INV_GAMMA1P_M1_A0 + t * INV_GAMMA1P_M1_A1; + double b = INV_GAMMA1P_M1_B8; + b = INV_GAMMA1P_M1_B7 + t * b; + b = INV_GAMMA1P_M1_B6 + t * b; + b = INV_GAMMA1P_M1_B5 + t * b; + b = INV_GAMMA1P_M1_B4 + t * b; + b = INV_GAMMA1P_M1_B3 + t * b; + b = INV_GAMMA1P_M1_B2 + t * b; + b = INV_GAMMA1P_M1_B1 + t * b; + b = 1.0 + t * b; + + double c = INV_GAMMA1P_M1_C13 + t * (a / b); + c = INV_GAMMA1P_M1_C12 + t * c; + c = INV_GAMMA1P_M1_C11 + t * c; + c = INV_GAMMA1P_M1_C10 + t * c; + c = INV_GAMMA1P_M1_C9 + t * c; + c = INV_GAMMA1P_M1_C8 + t * c; + c = INV_GAMMA1P_M1_C7 + t * c; + c = INV_GAMMA1P_M1_C6 + t * c; + c = INV_GAMMA1P_M1_C5 + t * c; + c = INV_GAMMA1P_M1_C4 + t * c; + c = INV_GAMMA1P_M1_C3 + t * c; + c = INV_GAMMA1P_M1_C2 + t * c; + c = INV_GAMMA1P_M1_C1 + t * c; + c = INV_GAMMA1P_M1_C + t * c; + if (x > 0.5) { + ret = t * c / x; + } else { + ret = x * ((c + 0.5) + 0.5); + } + } else { + double p = INV_GAMMA1P_M1_P6; + p = INV_GAMMA1P_M1_P5 + t * p; + p = INV_GAMMA1P_M1_P4 + t * p; + p = INV_GAMMA1P_M1_P3 + t * p; + p = INV_GAMMA1P_M1_P2 + t * p; + p = INV_GAMMA1P_M1_P1 + t * p; + p = INV_GAMMA1P_M1_P0 + t * p; + + double q = INV_GAMMA1P_M1_Q4; + q = INV_GAMMA1P_M1_Q3 + t * q; + q = INV_GAMMA1P_M1_Q2 + t * q; + q = INV_GAMMA1P_M1_Q1 + t * q; + q = 1.0 + t * q; + + double c = INV_GAMMA1P_M1_C13 + (p / q) * t; + c = INV_GAMMA1P_M1_C12 + t * c; + c = INV_GAMMA1P_M1_C11 + t * c; + c = INV_GAMMA1P_M1_C10 + t * c; + c = INV_GAMMA1P_M1_C9 + t * c; + c = INV_GAMMA1P_M1_C8 + t * c; + c = INV_GAMMA1P_M1_C7 + t * c; + c = INV_GAMMA1P_M1_C6 + t * c; + c = INV_GAMMA1P_M1_C5 + t * c; + c = INV_GAMMA1P_M1_C4 + t * c; + c = INV_GAMMA1P_M1_C3 + t * c; + c = INV_GAMMA1P_M1_C2 + t * c; + c = INV_GAMMA1P_M1_C1 + t * c; + c = INV_GAMMA1P_M1_C0 + t * c; + + if (x > 0.5) { + ret = (t / x) * ((c - 0.5) - 0.5); + } else { + ret = x * c; + } + } + + return ret; + } + + /** + * Returns the value of log Γ(1 + x) for -0.5 ≤ x ≤ 1.5. + * This implementation is based on the double precision implementation in + * the NSWC Library of Mathematics Subroutines, {@code DGMLN1}. + * + * @param x Argument. + * @return The value of {@code log(Gamma(1 + x))}. + * @throws NumberIsTooSmallException if {@code x < -0.5}. + * @throws NumberIsTooLargeException if {@code x > 1.5}. + * @since 3.1 + */ + public static double logGamma1p(final double x) + throws NumberIsTooSmallException, NumberIsTooLargeException { + + if (x < -0.5) { + throw new NumberIsTooSmallException(x, -0.5, true); + } + if (x > 1.5) { + throw new NumberIsTooLargeException(x, 1.5, true); + } + + return -FastMath.log1p(invGamma1pm1(x)); + } + + + /** + * Returns the value of Γ(x). Based on the NSWC Library of + * Mathematics Subroutines double precision implementation, + * {@code DGAMMA}. + * + * @param x Argument. + * @return the value of {@code Gamma(x)}. + * @since 3.1 + */ + public static double gamma(final double x) { + + if ((x == FastMath.rint(x)) && (x <= 0.0)) { + return Double.NaN; + } + + final double ret; + final double absX = FastMath.abs(x); + if (absX <= 20.0) { + if (x >= 1.0) { + /* + * From the recurrence relation + * Gamma(x) = (x - 1) * ... * (x - n) * Gamma(x - n), + * then + * Gamma(t) = 1 / [1 + invGamma1pm1(t - 1)], + * where t = x - n. This means that t must satisfy + * -0.5 <= t - 1 <= 1.5. + */ + double prod = 1.0; + double t = x; + while (t > 2.5) { + t -= 1.0; + prod *= t; + } + ret = prod / (1.0 + invGamma1pm1(t - 1.0)); + } else { + /* + * From the recurrence relation + * Gamma(x) = Gamma(x + n + 1) / [x * (x + 1) * ... * (x + n)] + * then + * Gamma(x + n + 1) = 1 / [1 + invGamma1pm1(x + n)], + * which requires -0.5 <= x + n <= 1.5. + */ + double prod = x; + double t = x; + while (t < -0.5) { + t += 1.0; + prod *= t; + } + ret = 1.0 / (prod * (1.0 + invGamma1pm1(t))); + } + } else { + final double y = absX + LANCZOS_G + 0.5; + final double gammaAbs = SQRT_TWO_PI / absX * + FastMath.pow(y, absX + 0.5) * + FastMath.exp(-y) * lanczos(absX); + if (x > 0.0) { + ret = gammaAbs; + } else { + /* + * From the reflection formula + * Gamma(x) * Gamma(1 - x) * sin(pi * x) = pi, + * and the recurrence relation + * Gamma(1 - x) = -x * Gamma(-x), + * it is found + * Gamma(x) = -pi / [x * sin(pi * x) * Gamma(-x)]. + */ + ret = -FastMath.PI / + (x * FastMath.sin(FastMath.PI * x) * gammaAbs); + } + } + return ret; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/package-info.java new file mode 100644 index 000000000..390a02465 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/special/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Implementations of special functions such as Beta and Gamma. + */ +package com.fr.third.org.apache.commons.math3.special; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/Frequency.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/Frequency.java new file mode 100644 index 000000000..ca053b0a6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/Frequency.java @@ -0,0 +1,686 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat; + +import java.io.Serializable; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Maintains a frequency distribution. + *

        + * 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.

        + *

        + * Integer values (int, long, Integer, Long) are not distinguished by type -- + * i.e. addValue(Long.valueOf(2)), addValue(2), addValue(2l) all have + * the same effect (similarly for arguments to getCount, etc.).

        + *

        NOTE: byte and short values will be implicitly converted to int values + * by the compiler, thus there are no explicit overloaded methods for these + * primitive types.

        + *

        + * char values are converted by addValue 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. + *

        + *

        + * Float is not coerced to Double. + * Since they are not Comparable with each other the user must do any necessary coercion. + * Float.NaN and Double.NaN are not treated specially; they may occur in input and will + * occur in output if appropriate. + * + *

        + * The values are ordered using the default (natural order), unless a + * Comparator is supplied in the constructor.

        + * + */ +public class Frequency implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -3845586908418844111L; + + /** underlying collection */ + private final SortedMap, Long> freqTable; + + /** + * Default constructor. + */ + public Frequency() { + freqTable = new TreeMap, 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, Long>((Comparator>) 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> 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. + *

        + * If other objects have already been added to this Frequency, v must + * be comparable to those that have already been added. + *

        + * + * @param v the value to add. + * @throws MathIllegalArgumentException if v is not comparable with previous entries + */ + public void addValue(Comparable v) throws MathIllegalArgumentException { + incrementValue(v, 1); + } + + /** + * Adds 1 to the frequency count for v. + * + * @param v the value to add. + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Long + */ + public void addValue(int v) throws MathIllegalArgumentException { + addValue(Long.valueOf(v)); + } + + /** + * Adds 1 to the frequency count for v. + * + * @param v the value to add. + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Long + */ + public void addValue(long v) throws MathIllegalArgumentException { + addValue(Long.valueOf(v)); + } + + /** + * Adds 1 to the frequency count for v. + * + * @param v the value to add. + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Char + */ + public void addValue(char v) throws MathIllegalArgumentException { + addValue(Character.valueOf(v)); + } + + /** + * Increments the frequency count for v. + *

        + * If other objects have already been added to this Frequency, v must + * be comparable to those that have already been added. + *

        + * + * @param v the value to add. + * @param increment the amount by which the value should be incremented + * @throws MathIllegalArgumentException if v is not comparable with previous entries + * @since 3.1 + */ + public void incrementValue(Comparable v, long increment) throws MathIllegalArgumentException { + Comparable obj = v; + if (v instanceof Integer) { + obj = Long.valueOf(((Integer) v).longValue()); + } + try { + Long count = freqTable.get(obj); + if (count == null) { + freqTable.put(obj, Long.valueOf(increment)); + } else { + freqTable.put(obj, Long.valueOf(count.longValue() + increment)); + } + } catch (ClassCastException ex) { + //TreeMap will throw ClassCastException if v is not comparable + throw new MathIllegalArgumentException( + LocalizedFormats.INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES, + v.getClass().getName()); + } + } + + /** + * Increments the frequency count for v. + *

        + * If other objects have already been added to this Frequency, v must + * be comparable to those that have already been added. + *

        + * + * @param v the value to add. + * @param increment the amount by which the value should be incremented + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Long + * @since 3.3 + */ + public void incrementValue(int v, long increment) throws MathIllegalArgumentException { + incrementValue(Long.valueOf(v), increment); + } + + /** + * Increments the frequency count for v. + *

        + * If other objects have already been added to this Frequency, v must + * be comparable to those that have already been added. + *

        + * + * @param v the value to add. + * @param increment the amount by which the value should be incremented + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Long + * @since 3.3 + */ + public void incrementValue(long v, long increment) throws MathIllegalArgumentException { + incrementValue(Long.valueOf(v), increment); + } + + /** + * Increments the frequency count for v. + *

        + * If other objects have already been added to this Frequency, v must + * be comparable to those that have already been added. + *

        + * + * @param v the value to add. + * @param increment the amount by which the value should be incremented + * @throws MathIllegalArgumentException if the table contains entries not + * comparable to Char + * @since 3.3 + */ + public void incrementValue(char v, long increment) throws MathIllegalArgumentException { + incrementValue(Character.valueOf(v), increment); + } + + /** Clears the frequency table */ + public void clear() { + freqTable.clear(); + } + + /** + * Returns an Iterator over the set of values that have been added. + *

        + * If added values are integral (i.e., integers, longs, Integers, or Longs), + * they are converted to Longs when they are added, so the objects returned + * by the Iterator will in this case be Longs.

        + * + * @return values Iterator + */ + public Iterator> valuesIterator() { + return freqTable.keySet().iterator(); + } + + /** + * Return an Iterator over the set of keys and values that have been added. + * Using the entry set to iterate is more efficient in the case where you + * need to access respective counts as well as values, since it doesn't + * require a "get" for every key...the value is provided in the Map.Entry. + *

        + * If added values are integral (i.e., integers, longs, Integers, or Longs), + * they are converted to Longs when they are added, so the values of the + * map entries returned by the Iterator will in this case be Longs.

        + * + * @return entry set Iterator + * @since 3.1 + */ + public Iterator, Long>> entrySetIterator() { + return freqTable.entrySet().iterator(); + } + + //------------------------------------------------------------------------- + + /** + * Returns the sum of all frequencies. + * + * @return the total frequency count. + */ + public long getSumFreq() { + long result = 0; + Iterator iterator = freqTable.values().iterator(); + while (iterator.hasNext()) { + result += iterator.next().longValue(); + } + return result; + } + + /** + * Returns the number of values equal to v. + * Returns 0 if the value is not comparable. + * + * @param v the value to lookup. + * @return the frequency of v. + */ + public long getCount(Comparable v) { + if (v instanceof Integer) { + return getCount(((Integer) v).longValue()); + } + long result = 0; + try { + Long count = freqTable.get(v); + if (count != null) { + result = count.longValue(); + } + } catch (ClassCastException ex) { // NOPMD + // ignore and return 0 -- ClassCastException will be thrown if value is not comparable + } + return result; + } + + /** + * Returns the number of values equal to v. + * + * @param v the value to lookup. + * @return the frequency of v. + */ + public long getCount(int v) { + return getCount(Long.valueOf(v)); + } + + /** + * Returns the number of values equal to v. + * + * @param v the value to lookup. + * @return the frequency of v. + */ + public long getCount(long v) { + return getCount(Long.valueOf(v)); + } + + /** + * Returns the number of values equal to v. + * + * @param v the value to lookup. + * @return the frequency of v. + */ + public long getCount(char v) { + return getCount(Character.valueOf(v)); + } + + /** + * Returns the number of values in the frequency table. + * + * @return the number of unique values that have been added to the frequency table. + * @see #valuesIterator() + */ + public int getUniqueCount(){ + return freqTable.keySet().size(); + } + + /** + * Returns the percentage of values that are equal to v + * (as a proportion between 0 and 1). + *

        + * Returns Double.NaN if no values have been added. + * Returns 0 if at least one value has been added, but v is not comparable + * to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public double getPct(Comparable v) { + final long sumFreq = getSumFreq(); + if (sumFreq == 0) { + return Double.NaN; + } + return (double) getCount(v) / (double) sumFreq; + } + + /** + * Returns the percentage of values that are equal to v + * (as a proportion between 0 and 1). + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public double getPct(int v) { + return getPct(Long.valueOf(v)); + } + + /** + * Returns the percentage of values that are equal to v + * (as a proportion between 0 and 1). + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public double getPct(long v) { + return getPct(Long.valueOf(v)); + } + + /** + * Returns the percentage of values that are equal to v + * (as a proportion between 0 and 1). + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public double getPct(char v) { + return getPct(Character.valueOf(v)); + } + + //----------------------------------------------------------------------------------------- + + /** + * Returns the cumulative frequency of values less than or equal to v. + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup. + * @return the proportion of values equal to v + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public long getCumFreq(Comparable v) { + if (getSumFreq() == 0) { + return 0; + } + if (v instanceof Integer) { + return getCumFreq(((Integer) v).longValue()); + } + Comparator> c = (Comparator>) 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> 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. + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public long getCumFreq(int v) { + return getCumFreq(Long.valueOf(v)); + } + + /** + * Returns the cumulative frequency of values less than or equal to v. + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public long getCumFreq(long v) { + return getCumFreq(Long.valueOf(v)); + } + + /** + * Returns the cumulative frequency of values less than or equal to v. + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values equal to v + */ + public long getCumFreq(char v) { + return getCumFreq(Character.valueOf(v)); + } + + //---------------------------------------------------------------------------------------------- + + /** + * Returns the cumulative percentage of values less than or equal to v + * (as a proportion between 0 and 1). + *

        + * Returns Double.NaN if no values have been added. + * Returns 0 if at least one value has been added, but v is not comparable + * to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values less than or equal to v + */ + public double getCumPct(Comparable v) { + final long sumFreq = getSumFreq(); + if (sumFreq == 0) { + return Double.NaN; + } + return (double) getCumFreq(v) / (double) sumFreq; + } + + /** + * Returns the cumulative percentage of values less than or equal to v + * (as a proportion between 0 and 1). + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values less than or equal to v + */ + public double getCumPct(int v) { + return getCumPct(Long.valueOf(v)); + } + + /** + * Returns the cumulative percentage of values less than or equal to v + * (as a proportion between 0 and 1). + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values less than or equal to v + */ + public double getCumPct(long v) { + return getCumPct(Long.valueOf(v)); + } + + /** + * Returns the cumulative percentage of values less than or equal to v + * (as a proportion between 0 and 1). + *

        + * Returns 0 if v is not comparable to the values set.

        + * + * @param v the value to lookup + * @return the proportion of values less than or equal to v + */ + public double getCumPct(char v) { + return getCumPct(Character.valueOf(v)); + } + + /** + * Returns the mode value(s) in comparator order. + * + * @return a list containing the value(s) which appear most often. + * @since 3.3 + */ + public List> getMode() { + long mostPopular = 0; // frequencies are always positive + + // Get the max count first, so we avoid having to recreate the List each time + for(Long l : freqTable.values()) { + long frequency = l.longValue(); + if (frequency > mostPopular) { + mostPopular = frequency; + } + } + + List> modeList = new ArrayList>(); + for (Entry, Long> ent : freqTable.entrySet()) { + long frequency = ent.getValue().longValue(); + if (frequency == mostPopular) { + modeList.add(ent.getKey()); + } + } + return modeList; + } + + //---------------------------------------------------------------------------------------------- + + /** + * Merge another Frequency object's counts into this instance. + * This Frequency's counts will be incremented (or set when not already set) + * by the counts represented by other. + * + * @param other the other {@link Frequency} object to be merged + * @throws NullArgumentException if {@code other} is null + * @since 3.1 + */ + public void merge(final Frequency other) throws NullArgumentException { + MathUtils.checkNotNull(other, LocalizedFormats.NULL_NOT_ALLOWED); + + final Iterator, Long>> iter = other.entrySetIterator(); + while (iter.hasNext()) { + final Map.Entry, Long> entry = iter.next(); + incrementValue(entry.getKey(), entry.getValue().longValue()); + } + } + + /** + * Merge a {@link Collection} of {@link Frequency} objects into this instance. + * This Frequency's counts will be incremented (or set when not already set) + * by the counts represented by each of the others. + * + * @param others the other {@link Frequency} objects to be merged + * @throws NullArgumentException if the collection is null + * @since 3.1 + */ + public void merge(final Collection others) throws NullArgumentException { + MathUtils.checkNotNull(others, LocalizedFormats.NULL_NOT_ALLOWED); + + for (final Frequency freq : others) { + merge(freq); + } + } + + //---------------------------------------------------------------------------------------------- + + /** + * A Comparator that compares comparable objects using the + * natural order. Copied from Commons Collections ComparableComparator. + * @param the type of the objects compared + */ + private static class NaturalComparator> implements Comparator>, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -3852193713161395148L; + + /** + * Compare the two {@link Comparable Comparable} arguments. + * This method is equivalent to: + *
        (({@link Comparable Comparable})o1).{@link Comparable#compareTo compareTo}(o2)
        + * + * @param o1 the first object + * @param o2 the second object + * @return result of comparison + * @throws NullPointerException when o1 is null, + * or when ((Comparable)o1).compareTo(o2) does + * @throws ClassCastException when o1 is not a {@link Comparable Comparable}, + * or when ((Comparable)o1).compareTo(o2) does + */ + @SuppressWarnings("unchecked") // cast to (T) may throw ClassCastException, see Javadoc + public int compare(Comparable o1, Comparable 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/StatUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/StatUtils.java new file mode 100644 index 000000000..a06f3fd45 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/StatUtils.java @@ -0,0 +1,887 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat; + +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; +import com.fr.third.org.apache.commons.math3.stat.descriptive.UnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.GeometricMean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Mean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Max; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Min; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Percentile; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Product; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Sum; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfLogs; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfSquares; + +/** + * StatUtils provides static methods for computing statistics based on data + * stored in double[] arrays. + * + */ +public final class StatUtils { + + /** sum */ + private static final UnivariateStatistic SUM = new Sum(); + + /** sumSq */ + private static final UnivariateStatistic SUM_OF_SQUARES = new SumOfSquares(); + + /** prod */ + private static final UnivariateStatistic PRODUCT = new Product(); + + /** sumLog */ + private static final UnivariateStatistic SUM_OF_LOGS = new SumOfLogs(); + + /** min */ + private static final UnivariateStatistic MIN = new Min(); + + /** max */ + private static final UnivariateStatistic MAX = new Max(); + + /** mean */ + private static final UnivariateStatistic MEAN = new Mean(); + + /** variance */ + private static final Variance VARIANCE = new Variance(); + + /** percentile */ + private static final Percentile PERCENTILE = new Percentile(); + + /** geometric mean */ + private static final GeometricMean GEOMETRIC_MEAN = new GeometricMean(); + + /** + * Private Constructor + */ + private StatUtils() { + } + + /** + * Returns the sum of the values in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the input array + * is null.

        + * + * @param values array of values to sum + * @return the sum of the values or Double.NaN if the array + * is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double sum(final double[] values) + throws MathIllegalArgumentException { + return SUM.evaluate(values); + } + + /** + * Returns the sum of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double sum(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return SUM.evaluate(values, begin, length); + } + + /** + * Returns the sum of the squares of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values input array + * @return the sum of the squared values or Double.NaN if the + * array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double sumSq(final double[] values) throws MathIllegalArgumentException { + return SUM_OF_SQUARES.evaluate(values); + } + + /** + * Returns the sum of the squares of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the squares of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double sumSq(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return SUM_OF_SQUARES.evaluate(values, begin, length); + } + + /** + * Returns the product of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @return the product of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double product(final double[] values) + throws MathIllegalArgumentException { + return PRODUCT.evaluate(values); + } + + /** + * Returns the product of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the product of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double product(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return PRODUCT.evaluate(values, begin, length); + } + + /** + * Returns the sum of the natural logs of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link SumOfLogs}. + *

        + * + * @param values the input array + * @return the sum of the natural logs of the values or Double.NaN if + * the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double sumLog(final double[] values) + throws MathIllegalArgumentException { + return SUM_OF_LOGS.evaluate(values); + } + + /** + * Returns the sum of the natural logs of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link SumOfLogs}. + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the natural logs of the values or Double.NaN if + * length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double sumLog(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return SUM_OF_LOGS.evaluate(values, begin, length); + } + + /** + * Returns the arithmetic mean of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link Mean} for + * details on the computing algorithm.

        + * + * @param values the input array + * @return the mean of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double mean(final double[] values) + throws MathIllegalArgumentException { + return MEAN.evaluate(values); + } + + /** + * Returns the arithmetic mean of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link Mean} for + * details on the computing algorithm.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the mean of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double mean(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return MEAN.evaluate(values, begin, length); + } + + /** + * Returns the geometric mean of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link GeometricMean} + * for details on the computing algorithm.

        + * + * @param values the input array + * @return the geometric mean of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double geometricMean(final double[] values) + throws MathIllegalArgumentException { + return GEOMETRIC_MEAN.evaluate(values); + } + + /** + * Returns the geometric mean of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link GeometricMean} + * for details on the computing algorithm.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the geometric mean of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double geometricMean(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return GEOMETRIC_MEAN.evaluate(values, begin, length); + } + + + /** + * Returns the variance of the entries in the input array, or + * Double.NaN if the array is empty. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #populationVariance(double[])} for the non-bias-corrected + * population variance.

        + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @return the variance of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double variance(final double[] values) throws MathIllegalArgumentException { + return VARIANCE.evaluate(values); + } + + /** + * Returns the variance of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #populationVariance(double[], int, int)} for the non-bias-corrected + * population variance.

        + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null or the + * array index parameters are not valid.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double variance(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return VARIANCE.evaluate(values, begin, length); + } + + /** + * Returns the variance of the entries in the specified portion of + * the input array, using the precomputed mean value. Returns + * Double.NaN if the designated subarray is empty. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #populationVariance(double[], double, int, int)} for the non-bias-corrected + * population variance.

        + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * 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.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null or the + * array index parameters are not valid.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double variance(final double[] values, final double mean, + final int begin, final int length) throws MathIllegalArgumentException { + return VARIANCE.evaluate(values, mean, begin, length); + } + + /** + * Returns the variance of the entries in the input array, using the + * precomputed mean value. Returns Double.NaN if the array + * is empty. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #populationVariance(double[], double)} for the non-bias-corrected + * population variance.

        + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * 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.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @return the variance of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double variance(final double[] values, final double mean) + throws MathIllegalArgumentException { + return VARIANCE.evaluate(values, mean); + } + + /** + * Returns the + * population variance of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * See {@link Variance} for + * details on the formula and computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @return the population variance of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double populationVariance(final double[] values) + throws MathIllegalArgumentException { + return new Variance(false).evaluate(values); + } + + /** + * Returns the + * population variance of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null or the + * array index parameters are not valid.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the population variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double populationVariance(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return new Variance(false).evaluate(values, begin, length); + } + + /** + * Returns the + * population variance of the entries in the specified portion of + * the input array, using the precomputed mean value. Returns + * Double.NaN if the designated subarray is empty. + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * 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.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null or the + * array index parameters are not valid.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the population variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double populationVariance(final double[] values, final double mean, + final int begin, final int length) throws MathIllegalArgumentException { + return new Variance(false).evaluate(values, mean, begin, length); + } + + /** + * Returns the + * population variance of the entries in the input array, using the + * precomputed mean value. Returns Double.NaN if the array + * is empty. + *

        + * See {@link Variance} for + * details on the computing algorithm.

        + *

        + * 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.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @return the population variance of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double populationVariance(final double[] values, final double mean) + throws MathIllegalArgumentException { + return new Variance(false).evaluate(values, mean); + } + + /** + * Returns the maximum of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.POSITIVE_INFINITY, + * the result is Double.POSITIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @return the maximum of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double max(final double[] values) throws MathIllegalArgumentException { + return MAX.evaluate(values); + } + + /** + * Returns the maximum of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null or + * the array index parameters are not valid.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.POSITIVE_INFINITY, + * the result is Double.POSITIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the maximum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double max(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return MAX.evaluate(values, begin, length); + } + + /** + * Returns the minimum of the entries in the input array, or + * Double.NaN if the array is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.NEGATIVE_INFINITY, + * the result is Double.NEGATIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @return the minimum of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public static double min(final double[] values) throws MathIllegalArgumentException { + return MIN.evaluate(values); + } + + /** + * Returns the minimum of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null or + * the array index parameters are not valid.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.NEGATIVE_INFINITY, + * the result is Double.NEGATIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the minimum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public static double min(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + return MIN.evaluate(values, begin, length); + } + + /** + * Returns an estimate of the pth percentile of the values + * in the values array. + *

        + *

          + *
        • Returns Double.NaN if values has length + * 0
        • + *
        • Returns (for any value of p) values[0] + * if values has length 1
        • + *
        • Throws IllegalArgumentException if values + * is null or p is not a valid quantile value (p must be greater than 0 + * and less than or equal to 100)
        • + *

        + *

        + * See {@link Percentile} for + * a description of the percentile estimation algorithm used.

        + * + * @param values input array of values + * @param p the percentile value to compute + * @return the percentile value or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if values is null + * or p is invalid + */ + public static double percentile(final double[] values, final double p) + throws MathIllegalArgumentException { + return PERCENTILE.evaluate(values,p); + } + + /** + * Returns an estimate of the pth percentile of the values + * in the values array, starting with the element in (0-based) + * position begin in the array and including length + * values. + *

        + *

          + *
        • Returns Double.NaN if length = 0
        • + *
        • Returns (for any value of p) values[begin] + * if length = 1
        • + *
        • Throws MathIllegalArgumentException if values + * is null , begin or length is invalid, or + * p is not a valid quantile value (p must be greater than 0 + * and less than or equal to 100)
        • + *

        + *

        + * See {@link Percentile} for + * a description of the percentile estimation algorithm used.

        + * + * @param values array of input values + * @param p the percentile to compute + * @param begin the first (0-based) element to include in the computation + * @param length the number of array elements to include + * @return the percentile value + * @throws MathIllegalArgumentException if the parameters are not valid or the + * input array is null + */ + public static double percentile(final double[] values, final int begin, + final int length, final double p) throws MathIllegalArgumentException { + return PERCENTILE.evaluate(values, begin, length, p); + } + + /** + * Returns the sum of the (signed) differences between corresponding elements of the + * input arrays -- i.e., sum(sample1[i] - sample2[i]). + * + * @param sample1 the first array + * @param sample2 the second array + * @return sum of paired differences + * @throws DimensionMismatchException if the arrays do not have the same + * (positive) length. + * @throws NoDataException if the sample arrays are empty. + */ + public static double sumDifference(final double[] sample1, final double[] sample2) + throws DimensionMismatchException, NoDataException { + int n = sample1.length; + if (n != sample2.length) { + throw new DimensionMismatchException(n, sample2.length); + } + if (n <= 0) { + throw new NoDataException(LocalizedFormats.INSUFFICIENT_DIMENSION); + } + double result = 0; + for (int i = 0; i < n; i++) { + result += sample1[i] - sample2[i]; + } + return result; + } + + /** + * Returns the mean of the (signed) differences between corresponding elements of the + * input arrays -- i.e., sum(sample1[i] - sample2[i]) / sample1.length. + * + * @param sample1 the first array + * @param sample2 the second array + * @return mean of paired differences + * @throws DimensionMismatchException if the arrays do not have the same + * (positive) length. + * @throws NoDataException if the sample arrays are empty. + */ + public static double meanDifference(final double[] sample1, final double[] sample2) + throws DimensionMismatchException, NoDataException{ + return sumDifference(sample1, sample2) / sample1.length; + } + + /** + * Returns the variance of the (signed) differences between corresponding elements of the + * input arrays -- i.e., var(sample1[i] - sample2[i]). + * + * @param sample1 the first array + * @param sample2 the second array + * @param meanDifference the mean difference between corresponding entries + * @see #meanDifference(double[],double[]) + * @return variance of paired differences + * @throws DimensionMismatchException if the arrays do not have the same + * length. + * @throws NumberIsTooSmallException if the arrays length is less than 2. + */ + public static double varianceDifference(final double[] sample1, + final double[] sample2, double meanDifference) throws DimensionMismatchException, + NumberIsTooSmallException { + double sum1 = 0d; + double sum2 = 0d; + double diff = 0d; + int n = sample1.length; + if (n != sample2.length) { + throw new DimensionMismatchException(n, sample2.length); + } + if (n < 2) { + throw new NumberIsTooSmallException(n, 2, true); + } + for (int i = 0; i < n; i++) { + diff = sample1[i] - sample2[i]; + sum1 += (diff - meanDifference) *(diff - meanDifference); + sum2 += diff - meanDifference; + } + return (sum1 - (sum2 * sum2 / n)) / (n - 1); + } + + /** + * Normalize (standardize) the sample, so it is has a mean of 0 and a standard deviation of 1. + * + * @param sample Sample to normalize. + * @return normalized (standardized) sample. + * @since 2.2 + */ + public static double[] normalize(final double[] sample) { + DescriptiveStatistics stats = new DescriptiveStatistics(); + + // Add the data from the series to stats + for (int i = 0; i < sample.length; i++) { + stats.addValue(sample[i]); + } + + // Compute mean and standard deviation + double mean = stats.getMean(); + double standardDeviation = stats.getStandardDeviation(); + + // initialize the standardizedSample, which has the same length as the sample + double[] standardizedSample = new double[sample.length]; + + for (int i = 0; i < sample.length; i++) { + // z = (x- mean)/standardDeviation + standardizedSample[i] = (sample[i] - mean) / standardDeviation; + } + return standardizedSample; + } + + /** + * Returns the sample mode(s). The mode is the most frequently occurring + * value in the sample. If there is a unique value with maximum frequency, + * this value is returned as the only element of the output array. Otherwise, + * the returned array contains the maximum frequency elements in increasing + * order. For example, if {@code sample} is {0, 12, 5, 6, 0, 13, 5, 17}, + * the returned array will have length two, with 0 in the first element and + * 5 in the second. + * + *

        NaN values are ignored when computing the mode - i.e., NaNs will never + * appear in the output array. If the sample includes only NaNs or has + * length 0, an empty array is returned.

        + * + * @param sample input data + * @return array of array of the most frequently occurring element(s) sorted in ascending order. + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.3 + */ + public static double[] mode(double[] sample) throws MathIllegalArgumentException { + if (sample == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + return getMode(sample, 0, sample.length); + } + + /** + * Returns the sample mode(s). The mode is the most frequently occurring + * value in the sample. If there is a unique value with maximum frequency, + * this value is returned as the only element of the output array. Otherwise, + * the returned array contains the maximum frequency elements in increasing + * order. For example, if {@code sample} is {0, 12, 5, 6, 0, 13, 5, 17}, + * the returned array will have length two, with 0 in the first element and + * 5 in the second. + * + *

        NaN values are ignored when computing the mode - i.e., NaNs will never + * appear in the output array. If the sample includes only NaNs or has + * length 0, an empty array is returned.

        + * + * @param sample input data + * @param begin index (0-based) of the first array element to include + * @param length the number of elements to include + * + * @return array of array of the most frequently occurring element(s) sorted in ascending order. + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.3 + */ + public static double[] mode(double[] sample, final int begin, final int length) { + if (sample == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + + if (begin < 0) { + throw new NotPositiveException(LocalizedFormats.START_POSITION, Integer.valueOf(begin)); + } + + if (length < 0) { + throw new NotPositiveException(LocalizedFormats.LENGTH, Integer.valueOf(length)); + } + + return getMode(sample, begin, length); + } + + /** + * Private helper method. + * Assumes parameters have been validated. + * @param values input data + * @param begin index (0-based) of the first array element to include + * @param length the number of elements to include + * @return array of array of the most frequently occurring element(s) sorted in ascending order. + */ + private static double[] getMode(double[] values, final int begin, final int length) { + // Add the values to the frequency table + Frequency freq = new Frequency(); + for (int i = begin; i < begin + length; i++) { + final double value = values[i]; + if (!Double.isNaN(value)) { + freq.addValue(Double.valueOf(value)); + } + } + List> list = freq.getMode(); + // Convert the list to an array of primitive double + double[] modes = new double[list.size()]; + int i = 0; + for(Comparable c : list) { + modes[i++] = ((Double) c).doubleValue(); + } + return modes; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Cluster.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Cluster.java new file mode 100644 index 000000000..9f866dc78 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Cluster.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Cluster holding a set of {@link Clusterable} points. + * @param the type of points that can be clustered + * @since 2.0 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link com.fr.third.org.apache.commons.math3.ml.clustering.Cluster} instead + */ +@Deprecated +public class Cluster> implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -3442297081515880464L; + + /** The points contained in this cluster. */ + private final List 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(); + } + + /** + * 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 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Clusterable.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Clusterable.java new file mode 100644 index 000000000..f1ab0c488 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/Clusterable.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.util.Collection; + +/** + * Interface for points that can be clustered together. + * @param the type of point that can be clustered + * @since 2.0 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link com.fr.third.org.apache.commons.math3.ml.clustering.Clusterable} instead + */ +@Deprecated +public interface Clusterable { + + /** + * 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 p); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java new file mode 100644 index 000000000..b90c0409a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * DBSCAN (density-based spatial clustering of applications with noise) algorithm. + *

        + * The DBSCAN algorithm forms clusters based on the idea of density connectivity, i.e. + * a point p is density connected to another point q, if there exists a chain of + * points pi, with i = 1 .. n and p1 = p and pn = q, + * such that each pair <pi, pi+1> is directly density-reachable. + * A point q is directly density-reachable from point p if it is in the ε-neighborhood + * of this point. + *

        + * Any point that is not density-reachable from a formed cluster is treated as noise, and + * will thus not be present in the result. + *

        + * The algorithm requires two parameters: + *

          + *
        • eps: the distance that defines the ε-neighborhood of a point + *
        • minPoints: the minimum number of density-connected points required to form a cluster + *
        + *

        + * Note: as DBSCAN is not a centroid-based clustering algorithm, the resulting + * {@link Cluster} objects will have no defined center, i.e. {@link Cluster#getCenter()} will + * return {@code null}. + * + * @param type of the points to cluster + * @see DBSCAN (wikipedia) + * @see + * A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise + * @since 3.1 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link com.fr.third.org.apache.commons.math3.ml.clustering.DBSCANClusterer} instead + */ +@Deprecated +public class DBSCANClusterer> { + + /** Maximum radius of the neighborhood to be considered. */ + private final double eps; + + /** Minimum number of points needed for a cluster. */ + private final int minPts; + + /** Status of a point during the clustering process. */ + private enum PointStatus { + /** The point has is considered to be noise. */ + NOISE, + /** The point is already part of a cluster. */ + PART_OF_CLUSTER + } + + /** + * Creates a new instance of a DBSCANClusterer. + * + * @param eps maximum radius of the neighborhood to be considered + * @param minPts minimum number of points needed for a cluster + * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0} + */ + public DBSCANClusterer(final double eps, final int minPts) + throws NotPositiveException { + if (eps < 0.0d) { + throw new NotPositiveException(eps); + } + if (minPts < 0) { + throw new NotPositiveException(minPts); + } + this.eps = eps; + this.minPts = minPts; + } + + /** + * Returns the maximum radius of the neighborhood to be considered. + * + * @return maximum radius of the neighborhood + */ + public double getEps() { + return eps; + } + + /** + * Returns the minimum number of points needed for a cluster. + * + * @return minimum number of points needed for a cluster + */ + public int getMinPts() { + return minPts; + } + + /** + * Performs DBSCAN cluster analysis. + *

        + * Note: as DBSCAN is not a centroid-based clustering algorithm, the resulting + * {@link Cluster} objects will have no defined center, i.e. {@link Cluster#getCenter()} will + * return {@code null}. + * + * @param points the points to cluster + * @return the list of clusters + * @throws NullArgumentException if the data points are null + */ + public List> cluster(final Collection points) throws NullArgumentException { + + // sanity checks + MathUtils.checkNotNull(points); + + final List> clusters = new ArrayList>(); + final Map, PointStatus> visited = new HashMap, PointStatus>(); + + for (final T point : points) { + if (visited.get(point) != null) { + continue; + } + final List neighbors = getNeighbors(point, points); + if (neighbors.size() >= minPts) { + // DBSCAN does not care about center points + final Cluster cluster = new Cluster(null); + clusters.add(expandCluster(cluster, point, neighbors, points, visited)); + } else { + visited.put(point, PointStatus.NOISE); + } + } + + return clusters; + } + + /** + * Expands the cluster to include density-reachable items. + * + * @param cluster Cluster to expand + * @param point Point to add to cluster + * @param neighbors List of neighbors + * @param points the data set + * @param visited the set of already visited points + * @return the expanded cluster + */ + private Cluster expandCluster(final Cluster cluster, + final T point, + final List neighbors, + final Collection points, + final Map, PointStatus> visited) { + cluster.addPoint(point); + visited.put(point, PointStatus.PART_OF_CLUSTER); + + List seeds = new ArrayList(neighbors); + int index = 0; + while (index < seeds.size()) { + final T current = seeds.get(index); + PointStatus pStatus = visited.get(current); + // only check non-visited points + if (pStatus == null) { + final List currentNeighbors = getNeighbors(current, points); + if (currentNeighbors.size() >= minPts) { + seeds = merge(seeds, currentNeighbors); + } + } + + if (pStatus != PointStatus.PART_OF_CLUSTER) { + visited.put(current, PointStatus.PART_OF_CLUSTER); + cluster.addPoint(current); + } + + index++; + } + return cluster; + } + + /** + * Returns a list of density-reachable neighbors of a {@code point}. + * + * @param point the point to look for + * @param points possible neighbors + * @return the List of neighbors + */ + private List getNeighbors(final T point, final Collection points) { + final List neighbors = new ArrayList(); + for (final T neighbor : points) { + if (point != neighbor && neighbor.distanceFrom(point) <= eps) { + neighbors.add(neighbor); + } + } + return neighbors; + } + + /** + * Merges two lists together. + * + * @param one first list + * @param two second list + * @return merged lists + */ + private List merge(final List one, final List two) { + final Set oneSet = new HashSet(one); + for (T item : two) { + if (!oneSet.contains(item)) { + one.add(item); + } + } + return one; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.java new file mode 100644 index 000000000..c37f494e9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.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 com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.ml.clustering.DoublePoint; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * A simple implementation of {@link Clusterable} for points with double coordinates. + * @since 3.1 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link DoublePoint} instead + */ +@Deprecated +public class EuclideanDoublePoint implements Clusterable, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 8026472786091227632L; + + /** Point coordinates. */ + private final double[] point; + + /** + * Build an instance wrapping an integer array. + *

        + * The wrapped array is referenced, it is not copied. + * + * @param point the n-dimensional point in integer space + */ + public EuclideanDoublePoint(final double[] point) { + this.point = point; + } + + /** {@inheritDoc} */ + public EuclideanDoublePoint centroidOf(final Collection points) { + final double[] centroid = new double[getPoint().length]; + for (final EuclideanDoublePoint p : points) { + for (int i = 0; i < centroid.length; i++) { + centroid[i] += p.getPoint()[i]; + } + } + for (int i = 0; i < centroid.length; i++) { + centroid[i] /= points.size(); + } + return new EuclideanDoublePoint(centroid); + } + + /** {@inheritDoc} */ + public double distanceFrom(final EuclideanDoublePoint p) { + return MathArrays.distance(point, p.getPoint()); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object other) { + if (!(other instanceof EuclideanDoublePoint)) { + return false; + } + return Arrays.equals(point, ((EuclideanDoublePoint) other).point); + } + + /** + * Get the n-dimensional point in integer space. + * + * @return a reference (not a copy!) to the wrapped array + */ + public double[] getPoint() { + return point; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(point); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return Arrays.toString(point); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.java new file mode 100644 index 000000000..fb834acb4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.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 com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.ml.clustering.DoublePoint; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * A simple implementation of {@link Clusterable} for points with integer coordinates. + * @since 2.0 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link DoublePoint} instead + */ +@Deprecated +public class EuclideanIntegerPoint implements Clusterable, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 3946024775784901369L; + + /** Point coordinates. */ + private final int[] point; + + /** + * Build an instance wrapping an integer array. + *

        The wrapped array is referenced, it is not copied.

        + * @param point the n-dimensional point in integer space + */ + public EuclideanIntegerPoint(final int[] point) { + this.point = point; + } + + /** + * Get the n-dimensional point in integer space. + * @return a reference (not a copy!) to the wrapped array + */ + public int[] getPoint() { + return point; + } + + /** {@inheritDoc} */ + public double distanceFrom(final EuclideanIntegerPoint p) { + return MathArrays.distance(point, p.getPoint()); + } + + /** {@inheritDoc} */ + public EuclideanIntegerPoint centroidOf(final Collection points) { + int[] centroid = new int[getPoint().length]; + for (EuclideanIntegerPoint p : points) { + for (int i = 0; i < centroid.length; i++) { + centroid[i] += p.getPoint()[i]; + } + } + for (int i = 0; i < centroid.length; i++) { + centroid[i] /= points.size(); + } + return new EuclideanIntegerPoint(centroid); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object other) { + if (!(other instanceof EuclideanIntegerPoint)) { + return false; + } + return Arrays.equals(point, ((EuclideanIntegerPoint) other).point); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(point); + } + + /** + * {@inheritDoc} + * @since 2.1 + */ + @Override + public String toString() { + return Arrays.toString(point); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java new file mode 100644 index 000000000..42454303d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java @@ -0,0 +1,514 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.clustering; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Clustering algorithm based on David Arthur and Sergei Vassilvitski k-means++ algorithm. + * @param type of the points to cluster + * @see K-means++ (wikipedia) + * @since 2.0 + * @deprecated As of 3.2 (to be removed in 4.0), + * use {@link com.fr.third.org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer} instead + */ +@Deprecated +public class KMeansPlusPlusClusterer> { + + /** Strategies to use for replacing an empty cluster. */ + public enum EmptyClusterStrategy { + + /** Split the cluster with largest distance variance. */ + LARGEST_VARIANCE, + + /** Split the cluster with largest number of points. */ + LARGEST_POINTS_NUMBER, + + /** Create a cluster around the point farthest from its centroid. */ + FARTHEST_POINT, + + /** Generate an error. */ + ERROR + + } + + /** Random generator for choosing initial centers. */ + private final Random random; + + /** Selected strategy for empty clusters. */ + private final EmptyClusterStrategy emptyStrategy; + + /** Build a clusterer. + *

        + * The default strategy for handling empty clusters that may appear during + * algorithm iterations is to split the cluster with largest distance variance. + *

        + * @param random random generator to use for choosing initial centers + */ + public KMeansPlusPlusClusterer(final Random random) { + this(random, EmptyClusterStrategy.LARGEST_VARIANCE); + } + + /** Build a clusterer. + * @param random random generator to use for choosing initial centers + * @param emptyStrategy strategy to use for handling empty clusters that + * may appear during algorithm iterations + * @since 2.2 + */ + public KMeansPlusPlusClusterer(final Random random, final EmptyClusterStrategy emptyStrategy) { + this.random = random; + this.emptyStrategy = emptyStrategy; + } + + /** + * Runs the K-means++ clustering algorithm. + * + * @param points the points to cluster + * @param k the number of clusters to split the data into + * @param numTrials number of trial runs + * @param maxIterationsPerTrial the maximum number of iterations to run the algorithm + * for at each trial run. If negative, no maximum will be used + * @return a list of clusters containing the points + * @throws MathIllegalArgumentException if the data points are null or the number + * of clusters is larger than the number of data points + * @throws ConvergenceException if an empty cluster is encountered and the + * {@link #emptyStrategy} is set to {@code ERROR} + */ + public List> cluster(final Collection points, final int k, + int numTrials, int maxIterationsPerTrial) + throws MathIllegalArgumentException, ConvergenceException { + + // at first, we have not found any clusters list yet + List> best = null; + double bestVarianceSum = Double.POSITIVE_INFINITY; + + // do several clustering trials + for (int i = 0; i < numTrials; ++i) { + + // compute a clusters list + List> clusters = cluster(points, k, maxIterationsPerTrial); + + // compute the variance of the current list + double varianceSum = 0.0; + for (final Cluster cluster : clusters) { + if (!cluster.getPoints().isEmpty()) { + + // compute the distance variance of the current cluster + final T center = cluster.getCenter(); + final Variance stat = new Variance(); + for (final T point : cluster.getPoints()) { + stat.increment(point.distanceFrom(center)); + } + varianceSum += stat.getResult(); + + } + } + + if (varianceSum <= bestVarianceSum) { + // this one is the best we have found so far, remember it + best = clusters; + bestVarianceSum = varianceSum; + } + + } + + // return the best clusters list found + return best; + + } + + /** + * Runs the K-means++ clustering algorithm. + * + * @param points the points to cluster + * @param k the number of clusters to split the data into + * @param maxIterations the maximum number of iterations to run the algorithm + * for. If negative, no maximum will be used + * @return a list of clusters containing the points + * @throws MathIllegalArgumentException if the data points are null or the number + * of clusters is larger than the number of data points + * @throws ConvergenceException if an empty cluster is encountered and the + * {@link #emptyStrategy} is set to {@code ERROR} + */ + public List> cluster(final Collection points, final int k, + final int maxIterations) + throws MathIllegalArgumentException, ConvergenceException { + + // sanity checks + MathUtils.checkNotNull(points); + + // number of clusters has to be smaller or equal the number of data points + if (points.size() < k) { + throw new NumberIsTooSmallException(points.size(), k, false); + } + + // create the initial clusters + List> clusters = chooseInitialCenters(points, k, random); + + // create an array containing the latest assignment of a point to a cluster + // no need to initialize the array, as it will be filled with the first assignment + int[] assignments = new int[points.size()]; + assignPointsToClusters(clusters, points, assignments); + + // iterate through updating the centers until we're done + final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations; + for (int count = 0; count < max; count++) { + boolean emptyCluster = false; + List> newClusters = new ArrayList>(); + for (final Cluster cluster : clusters) { + final T newCenter; + if (cluster.getPoints().isEmpty()) { + switch (emptyStrategy) { + case LARGEST_VARIANCE : + newCenter = getPointFromLargestVarianceCluster(clusters); + break; + case LARGEST_POINTS_NUMBER : + newCenter = getPointFromLargestNumberCluster(clusters); + break; + case FARTHEST_POINT : + newCenter = getFarthestPoint(clusters); + break; + default : + throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS); + } + emptyCluster = true; + } else { + newCenter = cluster.getCenter().centroidOf(cluster.getPoints()); + } + newClusters.add(new Cluster(newCenter)); + } + int changes = assignPointsToClusters(newClusters, points, assignments); + clusters = newClusters; + + // if there were no more changes in the point-to-cluster assignment + // and there are no empty clusters left, return the current clusters + if (changes == 0 && !emptyCluster) { + return clusters; + } + } + return clusters; + } + + /** + * Adds the given points to the closest {@link Cluster}. + * + * @param type of the points to cluster + * @param clusters the {@link Cluster}s to add the points to + * @param points the points to add to the given {@link Cluster}s + * @param assignments points assignments to clusters + * @return the number of points assigned to different clusters as the iteration before + */ + private static > int + assignPointsToClusters(final List> clusters, final Collection points, + final int[] assignments) { + int assignedDifferently = 0; + int pointIndex = 0; + for (final T p : points) { + int clusterIndex = getNearestCluster(clusters, p); + if (clusterIndex != assignments[pointIndex]) { + assignedDifferently++; + } + + Cluster cluster = clusters.get(clusterIndex); + cluster.addPoint(p); + assignments[pointIndex++] = clusterIndex; + } + + return assignedDifferently; + } + + /** + * Use K-means++ to choose the initial centers. + * + * @param 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 > List> + chooseInitialCenters(final Collection points, final int k, final Random random) { + + // Convert to list for indexed access. Make it unmodifiable, since removal of items + // would screw up the logic of this method. + final List pointList = Collections.unmodifiableList(new ArrayList (points)); + + // The number of points in the list. + final int numPoints = pointList.size(); + + // Set the corresponding element in this array to indicate when + // elements of pointList are no longer available. + final boolean[] taken = new boolean[numPoints]; + + // The resulting list of initial centers. + final List> resultSet = new ArrayList>(); + + // Choose one center uniformly at random from among the data points. + final int firstPointIndex = random.nextInt(numPoints); + + final T firstPoint = pointList.get(firstPointIndex); + + resultSet.add(new Cluster(firstPoint)); + + // Must mark it as taken + taken[firstPointIndex] = true; + + // To keep track of the minimum distance squared of elements of + // pointList to elements of resultSet. + final double[] minDistSquared = new double[numPoints]; + + // Initialize the elements. Since the only point in resultSet is firstPoint, + // this is very easy. + for (int i = 0; i < numPoints; i++) { + if (i != firstPointIndex) { // That point isn't considered + double d = firstPoint.distanceFrom(pointList.get(i)); + minDistSquared[i] = d*d; + } + } + + while (resultSet.size() < k) { + + // Sum up the squared distances for the points in pointList not + // already taken. + double distSqSum = 0.0; + + for (int i = 0; i < numPoints; i++) { + if (!taken[i]) { + distSqSum += minDistSquared[i]; + } + } + + // Add one new data point as a center. Each point x is chosen with + // probability proportional to D(x)2 + final double r = random.nextDouble() * distSqSum; + + // The index of the next point to be added to the resultSet. + int nextPointIndex = -1; + + // Sum through the squared min distances again, stopping when + // sum >= r. + double sum = 0.0; + for (int i = 0; i < numPoints; i++) { + if (!taken[i]) { + sum += minDistSquared[i]; + if (sum >= r) { + nextPointIndex = i; + break; + } + } + } + + // If it's not set to >= 0, the point wasn't found in the previous + // for loop, probably because distances are extremely small. Just pick + // the last available point. + if (nextPointIndex == -1) { + for (int i = numPoints - 1; i >= 0; i--) { + if (!taken[i]) { + nextPointIndex = i; + break; + } + } + } + + // We found one. + if (nextPointIndex >= 0) { + + final T p = pointList.get(nextPointIndex); + + resultSet.add(new Cluster (p)); + + // Mark it as taken. + taken[nextPointIndex] = true; + + if (resultSet.size() < k) { + // Now update elements of minDistSquared. We only have to compute + // the distance to the new center to do this. + for (int j = 0; j < numPoints; j++) { + // Only have to worry about the points still not taken. + if (!taken[j]) { + double d = p.distanceFrom(pointList.get(j)); + double d2 = d * d; + if (d2 < minDistSquared[j]) { + minDistSquared[j] = d2; + } + } + } + } + + } else { + // None found -- + // Break from the while loop to prevent + // an infinite loop. + break; + } + } + + return resultSet; + } + + /** + * Get a random point from the {@link Cluster} with the largest distance variance. + * + * @param clusters the {@link Cluster}s to search + * @return a random point from the selected cluster + * @throws ConvergenceException if clusters are all empty + */ + private T getPointFromLargestVarianceCluster(final Collection> clusters) + throws ConvergenceException { + + double maxVariance = Double.NEGATIVE_INFINITY; + Cluster selected = null; + for (final Cluster 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 selectedPoints = selected.getPoints(); + return selectedPoints.remove(random.nextInt(selectedPoints.size())); + + } + + /** + * Get a random point from the {@link Cluster} with the largest number of points + * + * @param clusters the {@link Cluster}s to search + * @return a random point from the selected cluster + * @throws ConvergenceException if clusters are all empty + */ + private T getPointFromLargestNumberCluster(final Collection> clusters) throws ConvergenceException { + + int maxNumber = 0; + Cluster selected = null; + for (final Cluster 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 selectedPoints = selected.getPoints(); + return selectedPoints.remove(random.nextInt(selectedPoints.size())); + + } + + /** + * Get the point farthest to its cluster center + * + * @param clusters the {@link Cluster}s to search + * @return point farthest to its cluster center + * @throws ConvergenceException if clusters are all empty + */ + private T getFarthestPoint(final Collection> clusters) throws ConvergenceException { + + double maxDistance = Double.NEGATIVE_INFINITY; + Cluster selectedCluster = null; + int selectedPoint = -1; + for (final Cluster cluster : clusters) { + + // get the farthest point + final T center = cluster.getCenter(); + final List 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 type of the points to cluster + * @param clusters the {@link Cluster}s to search + * @param point the point to find the nearest {@link Cluster} for + * @return the index of the nearest {@link Cluster} to the given point + */ + private static > int + getNearestCluster(final Collection> clusters, final T point) { + double minDistance = Double.MAX_VALUE; + int clusterIndex = 0; + int minCluster = 0; + for (final Cluster c : clusters) { + final double distance = point.distanceFrom(c.getCenter()); + if (distance < minDistance) { + minDistance = distance; + minCluster = clusterIndex; + } + clusterIndex++; + } + return minCluster; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/package-info.java new file mode 100644 index 000000000..3e80c5573 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/clustering/package-info.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

        All classes and sub-packages of this package are deprecated.

        + *

        Please use their replacements, to be found under + *
          + *
        • {@link org.apache.commons.math3.ml.clustering}
        • + *
        + *

        + * + *

        + * Clustering algorithms. + *

        + */ +package com.fr.third.org.apache.commons.math3.stat.clustering; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/Covariance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/Covariance.java new file mode 100644 index 000000000..823babf29 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/Covariance.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.correlation; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Mean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; + +/** + * Computes covariances for pairs of arrays or columns of a matrix. + * + *

        The constructors that take RealMatrix or + * double[][] arguments generate covariance matrices. The + * columns of the input matrices are assumed to represent variable values.

        + * + *

        The constructor argument biasCorrected determines whether or + * not computed covariances are bias-corrected.

        + * + *

        Unbiased covariances are given by the formula

        + * cov(X, Y) = Σ[(xi - E(X))(yi - E(Y))] / (n - 1) + * where E(X) is the mean of X and E(Y) + * is the mean of the Y values. + * + *

        Non-bias-corrected estimates use n in place of n - 1 + * + * @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. + * + *

        The biasCorrected parameter determines whether or not + * covariance estimates are bias-corrected.

        + * + *

        The input array must be rectangular with at least one column + * and two rows.

        + * + * @param data rectangular array with columns representing covariates + * @param biasCorrected true means covariances are bias-corrected + * @throws MathIllegalArgumentException if the input data array is not + * rectangular with at least two rows and one column. + * @throws NotStrictlyPositiveException if the input data array is not + * rectangular with at least one row and one column. + */ + public Covariance(double[][] data, boolean biasCorrected) + throws MathIllegalArgumentException, NotStrictlyPositiveException { + this(new BlockRealMatrix(data), biasCorrected); + } + + /** + * Create a Covariance matrix from a rectangular array + * whose columns represent covariates. + * + *

        The input array must be rectangular with at least one column + * and two rows

        + * + * @param data rectangular array with columns representing covariates + * @throws MathIllegalArgumentException if the input data array is not + * rectangular with at least two rows and one column. + * @throws NotStrictlyPositiveException if the input data array is not + * rectangular with at least one row and one column. + */ + public Covariance(double[][] data) + throws MathIllegalArgumentException, NotStrictlyPositiveException { + this(data, true); + } + + /** + * Create a covariance matrix from a matrix whose columns + * represent covariates. + * + *

        The biasCorrected parameter determines whether or not + * covariance estimates are bias-corrected.

        + * + *

        The matrix must have at least one column and two rows

        + * + * @param matrix matrix with columns representing covariates + * @param biasCorrected true means covariances are bias-corrected + * @throws MathIllegalArgumentException if the input matrix does not have + * at least two rows and one column + */ + public Covariance(RealMatrix matrix, boolean biasCorrected) + throws MathIllegalArgumentException { + checkSufficientData(matrix); + n = matrix.getRowDimension(); + covarianceMatrix = computeCovarianceMatrix(matrix, biasCorrected); + } + + /** + * Create a covariance matrix from a matrix whose columns + * represent covariates. + * + *

        The matrix must have at least one column and two rows

        + * + * @param matrix matrix with columns representing covariates + * @throws MathIllegalArgumentException if the input matrix does not have + * at least two rows and one column + */ + public Covariance(RealMatrix matrix) throws MathIllegalArgumentException { + this(matrix, true); + } + + /** + * Returns the covariance matrix + * + * @return covariance matrix + */ + public RealMatrix getCovarianceMatrix() { + return covarianceMatrix; + } + + /** + * Returns the number of observations (length of covariate vectors) + * + * @return number of observations + */ + public int getN() { + return n; + } + + /** + * Compute a covariance matrix from a matrix whose columns represent + * covariates. + * @param matrix input matrix (must have at least one column and two rows) + * @param biasCorrected determines whether or not covariance estimates are bias-corrected + * @return covariance matrix + * @throws MathIllegalArgumentException if the matrix does not contain sufficient data + */ + protected RealMatrix computeCovarianceMatrix(RealMatrix matrix, boolean biasCorrected) + throws MathIllegalArgumentException { + int dimension = matrix.getColumnDimension(); + Variance variance = new Variance(biasCorrected); + RealMatrix outMatrix = new BlockRealMatrix(dimension, dimension); + for (int i = 0; i < dimension; i++) { + for (int j = 0; j < i; j++) { + double cov = covariance(matrix.getColumn(i), matrix.getColumn(j), biasCorrected); + outMatrix.setEntry(i, j, cov); + outMatrix.setEntry(j, i, cov); + } + outMatrix.setEntry(i, i, variance.evaluate(matrix.getColumn(i))); + } + return outMatrix; + } + + /** + * Create a covariance matrix from a matrix whose columns represent + * covariates. Covariances are computed using the bias-corrected formula. + * @param matrix input matrix (must have at least one column and two rows) + * @return covariance matrix + * @throws MathIllegalArgumentException if matrix does not contain sufficient data + * @see #Covariance + */ + protected RealMatrix computeCovarianceMatrix(RealMatrix matrix) + throws MathIllegalArgumentException { + return computeCovarianceMatrix(matrix, true); + } + + /** + * Compute a covariance matrix from a rectangular array whose columns represent + * covariates. + * @param data input array (must have at least one column and two rows) + * @param biasCorrected determines whether or not covariance estimates are bias-corrected + * @return covariance matrix + * @throws MathIllegalArgumentException if the data array does not contain sufficient + * data + * @throws NotStrictlyPositiveException if the input data array is not + * rectangular with at least one row and one column. + */ + protected RealMatrix computeCovarianceMatrix(double[][] data, boolean biasCorrected) + throws MathIllegalArgumentException, NotStrictlyPositiveException { + return computeCovarianceMatrix(new BlockRealMatrix(data), biasCorrected); + } + + /** + * Create a covariance matrix from a rectangular array whose columns represent + * covariates. Covariances are computed using the bias-corrected formula. + * @param data input array (must have at least one column and two rows) + * @return covariance matrix + * @throws MathIllegalArgumentException if the data array does not contain sufficient data + * @throws NotStrictlyPositiveException if the input data array is not + * rectangular with at least one row and one column. + * @see #Covariance + */ + protected RealMatrix computeCovarianceMatrix(double[][] data) + throws MathIllegalArgumentException, NotStrictlyPositiveException { + return computeCovarianceMatrix(data, true); + } + + /** + * Computes the covariance between the two arrays. + * + *

        Array lengths must match and the common length must be at least 2.

        + * + * @param xArray first data array + * @param yArray second data array + * @param biasCorrected if true, returned value will be bias-corrected + * @return returns the covariance for the two arrays + * @throws MathIllegalArgumentException if the arrays lengths do not match or + * there is insufficient data + */ + public double covariance(final double[] xArray, final double[] yArray, boolean biasCorrected) + throws MathIllegalArgumentException { + Mean mean = new Mean(); + double result = 0d; + int length = xArray.length; + if (length != yArray.length) { + throw new MathIllegalArgumentException( + LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, length, yArray.length); + } else if (length < 2) { + throw new MathIllegalArgumentException( + LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, length, 2); + } else { + double xMean = mean.evaluate(xArray); + double yMean = mean.evaluate(yArray); + for (int i = 0; i < length; i++) { + double xDev = xArray[i] - xMean; + double yDev = yArray[i] - yMean; + result += (xDev * yDev - result) / (i + 1); + } + } + return biasCorrected ? result * ((double) length / (double)(length - 1)) : result; + } + + /** + * Computes the covariance between the two arrays, using the bias-corrected + * formula. + * + *

        Array lengths must match and the common length must be at least 2.

        + * + * @param xArray first data array + * @param yArray second data array + * @return returns the covariance for the two arrays + * @throws MathIllegalArgumentException if the arrays lengths do not match or + * there is insufficient data + */ + public double covariance(final double[] xArray, final double[] yArray) + throws MathIllegalArgumentException { + return covariance(xArray, yArray, true); + } + + /** + * Throws MathIllegalArgumentException if the matrix does not have at least + * one column and two rows. + * @param matrix matrix to check + * @throws MathIllegalArgumentException if the matrix does not contain sufficient data + * to compute covariance + */ + private void checkSufficientData(final RealMatrix matrix) throws MathIllegalArgumentException { + int nRows = matrix.getRowDimension(); + int nCols = matrix.getColumnDimension(); + if (nRows < 2 || nCols < 1) { + throw new MathIllegalArgumentException( + LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS, + nRows, nCols); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java new file mode 100644 index 000000000..57e996334 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.correlation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Pair; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Implementation of Kendall's Tau-b rank correlation. + *

        + * A pair of observations (x1, y1) and + * (x2, y2) are considered concordant if + * x1 < x2 and y1 < y2 + * or x2 < x1 and y2 < y1. + * The pair is discordant if x1 < x2 and + * y2 < y1 or x2 < x1 and + * y1 < y2. If either x1 = x2 + * or y1 = y2, the pair is neither concordant nor + * discordant. + *

        + * Kendall's Tau-b is defined as: + *

        + * taub = (nc - nd) / sqrt((n0 - n1) * (n0 - n2))
        + * 
        + *

        + * where: + *

          + *
        • n0 = n * (n - 1) / 2
        • + *
        • nc = Number of concordant pairs
        • + *
        • nd = Number of discordant pairs
        • + *
        • n1 = sum of ti * (ti - 1) / 2 for all i
        • + *
        • n2 = sum of uj * (uj - 1) / 2 for all j
        • + *
        • ti = Number of tied values in the ith group of ties in x
        • + *
        • uj = Number of tied values in the jth group of ties in y
        • + *
        + *

        + * This implementation uses the O(n log n) algorithm described in + * William R. Knight's 1966 paper "A Computer Method for Calculating + * Kendall's Tau with Ungrouped Data" in the Journal of the American + * Statistical Association. + * + * @see + * Kendall tau rank correlation coefficient (Wikipedia) + * @see A Computer + * Method for Calculating Kendall's Tau with Ungrouped Data + * + * @since 3.3 + */ +public class KendallsCorrelation { + + /** correlation matrix */ + private final RealMatrix correlationMatrix; + + /** + * Create a KendallsCorrelation instance without data. + */ + public KendallsCorrelation() { + correlationMatrix = null; + } + + /** + * Create a KendallsCorrelation from a rectangular array + * whose columns represent values of variables to be correlated. + * + * @param data rectangular array with columns representing variables + * @throws IllegalArgumentException if the input data array is not + * rectangular with at least two rows and two columns. + */ + public KendallsCorrelation(double[][] data) { + this(MatrixUtils.createRealMatrix(data)); + } + + /** + * Create a KendallsCorrelation from a RealMatrix whose columns + * represent variables to be correlated. + * + * @param matrix matrix with columns representing variables to correlate + */ + public KendallsCorrelation(RealMatrix matrix) { + correlationMatrix = computeCorrelationMatrix(matrix); + } + + /** + * Returns the correlation matrix. + * + * @return correlation matrix + */ + public RealMatrix getCorrelationMatrix() { + return correlationMatrix; + } + + /** + * Computes the Kendall's Tau rank correlation matrix for the columns of + * the input matrix. + * + * @param matrix matrix with columns representing variables to correlate + * @return correlation matrix + */ + public RealMatrix computeCorrelationMatrix(final RealMatrix matrix) { + int nVars = matrix.getColumnDimension(); + RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars); + for (int i = 0; i < nVars; i++) { + for (int j = 0; j < i; j++) { + double corr = correlation(matrix.getColumn(i), matrix.getColumn(j)); + outMatrix.setEntry(i, j, corr); + outMatrix.setEntry(j, i, corr); + } + outMatrix.setEntry(i, i, 1d); + } + return outMatrix; + } + + /** + * Computes the Kendall's Tau rank correlation matrix for the columns of + * the input rectangular array. The columns of the array represent values + * of variables to be correlated. + * + * @param matrix matrix with columns representing variables to correlate + * @return correlation matrix + */ + public RealMatrix computeCorrelationMatrix(final double[][] matrix) { + return computeCorrelationMatrix(new BlockRealMatrix(matrix)); + } + + /** + * Computes the Kendall's Tau rank correlation coefficient between the two arrays. + * + * @param xArray first data array + * @param yArray second data array + * @return Returns Kendall's Tau rank correlation coefficient for the two arrays + * @throws DimensionMismatchException if the arrays lengths do not match + */ + public double correlation(final double[] xArray, final double[] yArray) + throws DimensionMismatchException { + + if (xArray.length != yArray.length) { + throw new DimensionMismatchException(xArray.length, yArray.length); + } + + final int n = xArray.length; + final long numPairs = sum(n - 1); + + @SuppressWarnings("unchecked") + Pair[] pairs = new Pair[n]; + for (int i = 0; i < n; i++) { + pairs[i] = new Pair(xArray[i], yArray[i]); + } + + Arrays.sort(pairs, new Comparator>() { + /** {@inheritDoc} */ + public int compare(Pair pair1, Pair pair2) { + int compareFirst = pair1.getFirst().compareTo(pair2.getFirst()); + return compareFirst != 0 ? compareFirst : pair1.getSecond().compareTo(pair2.getSecond()); + } + }); + + long tiedXPairs = 0; + long tiedXYPairs = 0; + long consecutiveXTies = 1; + long consecutiveXYTies = 1; + Pair prev = pairs[0]; + for (int i = 1; i < n; i++) { + final Pair curr = pairs[i]; + if (curr.getFirst().equals(prev.getFirst())) { + consecutiveXTies++; + if (curr.getSecond().equals(prev.getSecond())) { + consecutiveXYTies++; + } else { + tiedXYPairs += sum(consecutiveXYTies - 1); + consecutiveXYTies = 1; + } + } else { + tiedXPairs += sum(consecutiveXTies - 1); + consecutiveXTies = 1; + tiedXYPairs += sum(consecutiveXYTies - 1); + consecutiveXYTies = 1; + } + prev = curr; + } + tiedXPairs += sum(consecutiveXTies - 1); + tiedXYPairs += sum(consecutiveXYTies - 1); + + long swaps = 0; + @SuppressWarnings("unchecked") + Pair[] pairsDestination = new Pair[n]; + for (int segmentSize = 1; segmentSize < n; segmentSize <<= 1) { + for (int offset = 0; offset < n; offset += 2 * segmentSize) { + int i = offset; + final int iEnd = FastMath.min(i + segmentSize, n); + int j = iEnd; + final int jEnd = FastMath.min(j + segmentSize, n); + + int copyLocation = offset; + while (i < iEnd || j < jEnd) { + if (i < iEnd) { + if (j < jEnd) { + if (pairs[i].getSecond().compareTo(pairs[j].getSecond()) <= 0) { + pairsDestination[copyLocation] = pairs[i]; + i++; + } else { + pairsDestination[copyLocation] = pairs[j]; + j++; + swaps += iEnd - i; + } + } else { + pairsDestination[copyLocation] = pairs[i]; + i++; + } + } else { + pairsDestination[copyLocation] = pairs[j]; + j++; + } + copyLocation++; + } + } + final Pair[] pairsTemp = pairs; + pairs = pairsDestination; + pairsDestination = pairsTemp; + } + + long tiedYPairs = 0; + long consecutiveYTies = 1; + prev = pairs[0]; + for (int i = 1; i < n; i++) { + final Pair curr = pairs[i]; + if (curr.getSecond().equals(prev.getSecond())) { + consecutiveYTies++; + } else { + tiedYPairs += sum(consecutiveYTies - 1); + consecutiveYTies = 1; + } + prev = curr; + } + tiedYPairs += sum(consecutiveYTies - 1); + + final long concordantMinusDiscordant = numPairs - tiedXPairs - tiedYPairs + tiedXYPairs - 2 * swaps; + final double nonTiedPairsMultiplied = (numPairs - tiedXPairs) * (double) (numPairs - tiedYPairs); + return concordantMinusDiscordant / FastMath.sqrt(nonTiedPairsMultiplied); + } + + /** + * Returns the sum of the number from 1 .. n according to Gauss' summation formula: + * \[ \sum\limits_{k=1}^n k = \frac{n(n + 1)}{2} \] + * + * @param n the summation end + * @return the sum of the number from 1 to n + */ + private static long sum(long n) { + return n * (n + 1) / 2l; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.java new file mode 100644 index 000000000..f1de3237b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.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 com.fr.third.org.apache.commons.math3.stat.correlation; + +import com.fr.third.org.apache.commons.math3.distribution.TDistribution; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.stat.regression.SimpleRegression; + +/** + * Computes Pearson's product-moment correlation coefficients for pairs of arrays + * or columns of a matrix. + * + *

        The constructors that take RealMatrix or + * double[][] arguments generate correlation matrices. The + * columns of the input matrices are assumed to represent variable values. + * Correlations are given by the formula

        + * + *

        cor(X, Y) = Σ[(xi - E(X))(yi - E(Y))] / [(n - 1)s(X)s(Y)] + * where E(X) is the mean of X, E(Y) + * is the mean of the Y values and s(X), s(Y) are standard deviations.

        + * + *

        To compute the correlation coefficient for a single pair of arrays, use {@link #PearsonsCorrelation()} + * to construct an instance with no data and then {@link #correlation(double[], double[])}. + * Correlation matrices can also be computed directly from an instance with no data using + * {@link #computeCorrelationMatrix(double[][])}. In order to use {@link #getCorrelationMatrix()}, + * {@link #getCorrelationPValues()}, or {@link #getCorrelationStandardErrors()}; however, one of the + * constructors supplying data or a covariance matrix must be used to create the instance.

        + * + * @since 2.0 + */ +public class PearsonsCorrelation { + + /** correlation matrix */ + private final RealMatrix correlationMatrix; + + /** number of observations */ + private final int nObs; + + /** + * Create a PearsonsCorrelation instance without data. + */ + public PearsonsCorrelation() { + super(); + correlationMatrix = null; + nObs = 0; + } + + /** + * Create a PearsonsCorrelation from a rectangular array + * whose columns represent values of variables to be correlated. + * + * Throws MathIllegalArgumentException if the input array does not have at least + * two columns and two rows. Pairwise correlations are set to NaN if one + * of the correlates has zero variance. + * + * @param data rectangular array with columns representing variables + * @throws MathIllegalArgumentException if the input data array is not + * rectangular with at least two rows and two columns. + * @see #correlation(double[], double[]) + */ + public PearsonsCorrelation(double[][] data) { + this(new BlockRealMatrix(data)); + } + + /** + * Create a PearsonsCorrelation from a RealMatrix whose columns + * represent variables to be correlated. + * + * Throws MathIllegalArgumentException if the matrix does not have at least + * two columns and two rows. Pairwise correlations are set to NaN if one + * of the correlates has zero variance. + * + * @param matrix matrix with columns representing variables to correlate + * @throws MathIllegalArgumentException if the matrix does not contain sufficient data + * @see #correlation(double[], double[]) + */ + public PearsonsCorrelation(RealMatrix matrix) { + nObs = matrix.getRowDimension(); + correlationMatrix = computeCorrelationMatrix(matrix); + } + + /** + * Create a PearsonsCorrelation from a {@link Covariance}. The correlation + * matrix is computed by scaling the Covariance's covariance matrix. + * The Covariance instance must have been created from a data matrix with + * columns representing variable values. + * + * @param covariance Covariance instance + */ + public PearsonsCorrelation(Covariance covariance) { + RealMatrix covarianceMatrix = covariance.getCovarianceMatrix(); + if (covarianceMatrix == null) { + throw new NullArgumentException(LocalizedFormats.COVARIANCE_MATRIX); + } + nObs = covariance.getN(); + correlationMatrix = covarianceToCorrelation(covarianceMatrix); + } + + /** + * Create a PearsonsCorrelation from a covariance matrix. The correlation + * matrix is computed by scaling the covariance matrix. + * + * @param covarianceMatrix covariance matrix + * @param numberOfObservations the number of observations in the dataset used to compute + * the covariance matrix + */ + public PearsonsCorrelation(RealMatrix covarianceMatrix, int numberOfObservations) { + nObs = numberOfObservations; + correlationMatrix = covarianceToCorrelation(covarianceMatrix); + } + + /** + * Returns the correlation matrix. + * + *

        This method will return null if the argumentless constructor was used + * to create this instance, even if {@link #computeCorrelationMatrix(double[][])} + * has been called before it is activated.

        + * + * @return correlation matrix + */ + public RealMatrix getCorrelationMatrix() { + return correlationMatrix; + } + + /** + * Returns a matrix of standard errors associated with the estimates + * in the correlation matrix.
        + * getCorrelationStandardErrors().getEntry(i,j) is the standard + * error associated with getCorrelationMatrix.getEntry(i,j) + * + *

        The formula used to compute the standard error is
        + * SEr = ((1 - r2) / (n - 2))1/2 + * where r is the estimated correlation coefficient and + * n is the number of observations in the source dataset.

        + * + *

        To use this method, one of the constructors that supply an input + * matrix must have been used to create this instance.

        + * + * @return matrix of correlation standard errors + * @throws NullPointerException if this instance was created with no data + */ + public RealMatrix getCorrelationStandardErrors() { + int nVars = correlationMatrix.getColumnDimension(); + double[][] out = new double[nVars][nVars]; + for (int i = 0; i < nVars; i++) { + for (int j = 0; j < nVars; j++) { + double r = correlationMatrix.getEntry(i, j); + out[i][j] = FastMath.sqrt((1 - r * r) /(nObs - 2)); + } + } + return new BlockRealMatrix(out); + } + + /** + * Returns a matrix of p-values associated with the (two-sided) null + * hypothesis that the corresponding correlation coefficient is zero. + * + *

        getCorrelationPValues().getEntry(i,j) is the probability + * that a random variable distributed as tn-2 takes + * a value with absolute value greater than or equal to
        + * |r|((n - 2) / (1 - r2))1/2

        + * + *

        The values in the matrix are sometimes referred to as the + * significance of the corresponding correlation coefficients.

        + * + *

        To use this method, one of the constructors that supply an input + * matrix must have been used to create this instance.

        + * + * @return matrix of p-values + * @throws MaxCountExceededException + * if an error occurs estimating probabilities + * @throws NullPointerException if this instance was created with no data + */ + public RealMatrix getCorrelationPValues() { + TDistribution tDistribution = new TDistribution(nObs - 2); + int nVars = correlationMatrix.getColumnDimension(); + double[][] out = new double[nVars][nVars]; + for (int i = 0; i < nVars; i++) { + for (int j = 0; j < nVars; j++) { + if (i == j) { + out[i][j] = 0d; + } else { + double r = correlationMatrix.getEntry(i, j); + double t = FastMath.abs(r * FastMath.sqrt((nObs - 2)/(1 - r * r))); + out[i][j] = 2 * tDistribution.cumulativeProbability(-t); + } + } + } + return new BlockRealMatrix(out); + } + + + /** + * Computes the correlation matrix for the columns of the + * input matrix, using {@link #correlation(double[], double[])}. + * + * Throws MathIllegalArgumentException if the matrix does not have at least + * two columns and two rows. Pairwise correlations are set to NaN if one + * of the correlates has zero variance. + * + * @param matrix matrix with columns representing variables to correlate + * @return correlation matrix + * @throws MathIllegalArgumentException if the matrix does not contain sufficient data + * @see #correlation(double[], double[]) + */ + public RealMatrix computeCorrelationMatrix(RealMatrix matrix) { + checkSufficientData(matrix); + int nVars = matrix.getColumnDimension(); + RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars); + for (int i = 0; i < nVars; i++) { + for (int j = 0; j < i; j++) { + double corr = correlation(matrix.getColumn(i), matrix.getColumn(j)); + outMatrix.setEntry(i, j, corr); + outMatrix.setEntry(j, i, corr); + } + outMatrix.setEntry(i, i, 1d); + } + return outMatrix; + } + + /** + * Computes the correlation matrix for the columns of the + * input rectangular array. The columns of the array represent values + * of variables to be correlated. + * + * Throws MathIllegalArgumentException if the matrix does not have at least + * two columns and two rows or if the array is not rectangular. Pairwise + * correlations are set to NaN if one of the correlates has zero variance. + * + * @param data matrix with columns representing variables to correlate + * @return correlation matrix + * @throws MathIllegalArgumentException if the array does not contain sufficient data + * @see #correlation(double[], double[]) + */ + public RealMatrix computeCorrelationMatrix(double[][] data) { + return computeCorrelationMatrix(new BlockRealMatrix(data)); + } + + /** + * Computes the Pearson's product-moment correlation coefficient between two arrays. + * + *

        Throws MathIllegalArgumentException if the arrays do not have the same length + * or their common length is less than 2. Returns {@code NaN} if either of the arrays + * has zero variance (i.e., if one of the arrays does not contain at least two distinct + * values).

        + * + * @param xArray first data array + * @param yArray second data array + * @return Returns Pearson's correlation coefficient for the two arrays + * @throws DimensionMismatchException if the arrays lengths do not match + * @throws MathIllegalArgumentException if there is insufficient data + */ + public double correlation(final double[] xArray, final double[] yArray) { + SimpleRegression regression = new SimpleRegression(); + if (xArray.length != yArray.length) { + throw new DimensionMismatchException(xArray.length, yArray.length); + } else if (xArray.length < 2) { + throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_DIMENSION, + xArray.length, 2); + } else { + for(int i=0; iUses the formula
        + * r(X,Y) = cov(X,Y)/s(X)s(Y) where + * r(·,·) is the correlation coefficient and + * s(·) means standard deviation.

        + * + * @param covarianceMatrix the covariance matrix + * @return correlation matrix + */ + public RealMatrix covarianceToCorrelation(RealMatrix covarianceMatrix) { + int nVars = covarianceMatrix.getColumnDimension(); + RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars); + for (int i = 0; i < nVars; i++) { + double sigma = FastMath.sqrt(covarianceMatrix.getEntry(i, i)); + outMatrix.setEntry(i, i, 1d); + for (int j = 0; j < i; j++) { + double entry = covarianceMatrix.getEntry(i, j) / + (sigma * FastMath.sqrt(covarianceMatrix.getEntry(j, j))); + outMatrix.setEntry(i, j, entry); + outMatrix.setEntry(j, i, entry); + } + } + return outMatrix; + } + + /** + * Throws MathIllegalArgumentException if the matrix does not have at least + * two columns and two rows. + * + * @param matrix matrix to check for sufficiency + * @throws MathIllegalArgumentException if there is insufficient data + */ + private void checkSufficientData(final RealMatrix matrix) { + int nRows = matrix.getRowDimension(); + int nCols = matrix.getColumnDimension(); + if (nRows < 2 || nCols < 2) { + throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS, + nRows, nCols); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java new file mode 100644 index 000000000..1617f50f2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.correlation; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.BlockRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaNStrategy; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaturalRanking; +import com.fr.third.org.apache.commons.math3.stat.ranking.RankingAlgorithm; + +/** + * Spearman's rank correlation. This implementation performs a rank + * transformation on the input data and then computes {@link PearsonsCorrelation} + * on the ranked data. + *

        + * By default, ranks are computed using {@link NaturalRanking} with default + * strategies for handling NaNs and ties in the data (NaNs maximal, ties averaged). + * The ranking algorithm can be set using a constructor argument. + * + * @since 2.0 + */ +public class SpearmansCorrelation { + + /** Input data */ + private final RealMatrix data; + + /** Ranking algorithm */ + private final RankingAlgorithm rankingAlgorithm; + + /** Rank correlation */ + private final PearsonsCorrelation rankCorrelation; + + /** + * Create a SpearmansCorrelation without data. + */ + public SpearmansCorrelation() { + this(new NaturalRanking()); + } + + /** + * Create a SpearmansCorrelation with the given ranking algorithm. + *

        + * From version 4.0 onwards this constructor will throw an exception + * if the provided {@link NaturalRanking} uses a {@link NaNStrategy#REMOVED} strategy. + * + * @param rankingAlgorithm ranking algorithm + * @since 3.1 + */ + public SpearmansCorrelation(final RankingAlgorithm rankingAlgorithm) { + data = null; + this.rankingAlgorithm = rankingAlgorithm; + rankCorrelation = null; + } + + /** + * Create a SpearmansCorrelation from the given data matrix. + * + * @param dataMatrix matrix of data with columns representing + * variables to correlate + */ + public SpearmansCorrelation(final RealMatrix dataMatrix) { + this(dataMatrix, new NaturalRanking()); + } + + /** + * Create a SpearmansCorrelation with the given input data matrix + * and ranking algorithm. + *

        + * From version 4.0 onwards this constructor will throw an exception + * if the provided {@link NaturalRanking} uses a {@link NaNStrategy#REMOVED} strategy. + * + * @param dataMatrix matrix of data with columns representing + * variables to correlate + * @param rankingAlgorithm ranking algorithm + */ + public SpearmansCorrelation(final RealMatrix dataMatrix, final RankingAlgorithm rankingAlgorithm) { + this.rankingAlgorithm = rankingAlgorithm; + this.data = rankTransform(dataMatrix); + rankCorrelation = new PearsonsCorrelation(data); + } + + /** + * Calculate the Spearman Rank Correlation Matrix. + * + * @return Spearman Rank Correlation Matrix + * @throws NullPointerException if this instance was created with no data + */ + public RealMatrix getCorrelationMatrix() { + return rankCorrelation.getCorrelationMatrix(); + } + + /** + * Returns a {@link PearsonsCorrelation} instance constructed from the + * ranked input data. That is, + * new SpearmansCorrelation(matrix).getRankCorrelation() + * is equivalent to + * new PearsonsCorrelation(rankTransform(matrix)) where + * rankTransform(matrix) is the result of applying the + * configured RankingAlgorithm to each of the columns of + * matrix. + * + *

        Returns null if this instance was created with no data.

        + * + * @return PearsonsCorrelation among ranked column data + */ + public PearsonsCorrelation getRankCorrelation() { + return rankCorrelation; + } + + /** + * Computes the Spearman's rank correlation matrix for the columns of the + * input matrix. + * + * @param matrix matrix with columns representing variables to correlate + * @return correlation matrix + */ + public RealMatrix computeCorrelationMatrix(final RealMatrix matrix) { + final RealMatrix matrixCopy = rankTransform(matrix); + return new PearsonsCorrelation().computeCorrelationMatrix(matrixCopy); + } + + /** + * Computes the Spearman's rank correlation matrix for the columns of the + * input rectangular array. The columns of the array represent values + * of variables to be correlated. + * + * @param matrix matrix with columns representing variables to correlate + * @return correlation matrix + */ + public RealMatrix computeCorrelationMatrix(final double[][] matrix) { + return computeCorrelationMatrix(new BlockRealMatrix(matrix)); + } + + /** + * Computes the Spearman's rank correlation coefficient between the two arrays. + * + * @param xArray first data array + * @param yArray second data array + * @return Returns Spearman's rank correlation coefficient for the two arrays + * @throws DimensionMismatchException if the arrays lengths do not match + * @throws MathIllegalArgumentException if the array length is less than 2 + */ + public double correlation(final double[] xArray, final double[] yArray) { + if (xArray.length != yArray.length) { + throw new DimensionMismatchException(xArray.length, yArray.length); + } else if (xArray.length < 2) { + throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_DIMENSION, + xArray.length, 2); + } else { + double[] x = xArray; + double[] y = yArray; + if (rankingAlgorithm instanceof NaturalRanking && + NaNStrategy.REMOVED == ((NaturalRanking) rankingAlgorithm).getNanStrategy()) { + final Set nanPositions = new HashSet(); + + nanPositions.addAll(getNaNPositions(xArray)); + nanPositions.addAll(getNaNPositions(yArray)); + + x = removeValues(xArray, nanPositions); + y = removeValues(yArray, nanPositions); + } + return new PearsonsCorrelation().correlation(rankingAlgorithm.rank(x), rankingAlgorithm.rank(y)); + } + } + + /** + * Applies rank transform to each of the columns of matrix + * using the current rankingAlgorithm. + * + * @param matrix matrix to transform + * @return a rank-transformed matrix + */ + private RealMatrix rankTransform(final RealMatrix matrix) { + RealMatrix transformed = null; + + if (rankingAlgorithm instanceof NaturalRanking && + ((NaturalRanking) rankingAlgorithm).getNanStrategy() == NaNStrategy.REMOVED) { + final Set nanPositions = new HashSet(); + for (int i = 0; i < matrix.getColumnDimension(); i++) { + nanPositions.addAll(getNaNPositions(matrix.getColumn(i))); + } + + // if we have found NaN values, we have to update the matrix size + if (!nanPositions.isEmpty()) { + transformed = new BlockRealMatrix(matrix.getRowDimension() - nanPositions.size(), + matrix.getColumnDimension()); + for (int i = 0; i < transformed.getColumnDimension(); i++) { + transformed.setColumn(i, removeValues(matrix.getColumn(i), nanPositions)); + } + } + } + + if (transformed == null) { + transformed = matrix.copy(); + } + + for (int i = 0; i < transformed.getColumnDimension(); i++) { + transformed.setColumn(i, rankingAlgorithm.rank(transformed.getColumn(i))); + } + + return transformed; + } + + /** + * Returns a list containing the indices of NaN values in the input array. + * + * @param input the input array + * @return a list of NaN positions in the input array + */ + private List getNaNPositions(final double[] input) { + final List positions = new ArrayList(); + for (int i = 0; i < input.length; i++) { + if (Double.isNaN(input[i])) { + positions.add(i); + } + } + return positions; + } + + /** + * Removes all values from the input array at the specified indices. + * + * @param input the input array + * @param indices a set containing the indices to be removed + * @return the input array without the values at the specified indices + */ + private double[] removeValues(final double[] input, final Set indices) { + if (indices.isEmpty()) { + return input; + } + final double[] result = new double[input.length - indices.size()]; + for (int i = 0, j = 0; i < input.length; i++) { + if (!indices.contains(i)) { + result[j++] = input[i]; + } + } + return result; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java new file mode 100644 index 000000000..6cc78488c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.correlation; + +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Bivariate Covariance implementation that does not require input data to be + * stored in memory. + * + *

        This class is based on a paper written by Philippe Pébay: + * + * Formulas for Robust, One-Pass Parallel Computation of Covariances and + * Arbitrary-Order Statistical Moments, 2008, Technical Report SAND2008-6212, + * Sandia National Laboratories. It computes the covariance for a pair of variables. + * Use {@link StorelessCovariance} to estimate an entire covariance matrix.

        + * + *

        Note: This class is package private as it is only used internally in + * the {@link StorelessCovariance} class.

        + * + * @since 3.0 + */ +class StorelessBivariateCovariance { + + /** the mean of variable x */ + private double meanX; + + /** the mean of variable y */ + private double meanY; + + /** number of observations */ + private double n; + + /** the running covariance estimate */ + private double covarianceNumerator; + + /** flag for bias correction */ + private boolean biasCorrected; + + /** + * Create an empty {@link StorelessBivariateCovariance} instance with + * bias correction. + */ + StorelessBivariateCovariance() { + this(true); + } + + /** + * Create an empty {@link StorelessBivariateCovariance} instance. + * + * @param biasCorrection if true the covariance estimate is corrected + * for bias, i.e. n-1 in the denominator, otherwise there is no bias correction, + * i.e. n in the denominator. + */ + StorelessBivariateCovariance(final boolean biasCorrection) { + meanX = meanY = 0.0; + n = 0; + covarianceNumerator = 0.0; + biasCorrected = biasCorrection; + } + + /** + * Update the covariance estimation with a pair of variables (x, y). + * + * @param x the x value + * @param y the y value + */ + public void increment(final double x, final double y) { + n++; + final double deltaX = x - meanX; + final double deltaY = y - meanY; + meanX += deltaX / n; + meanY += deltaY / n; + covarianceNumerator += ((n - 1.0) / n) * deltaX * deltaY; + } + + /** + * Appends another bivariate covariance calculation to this. + * After this operation, statistics returned should be close to what would + * have been obtained by by performing all of the {@link #increment(double, double)} + * operations in {@code cov} directly on this. + * + * @param cov StorelessBivariateCovariance instance to append. + */ + public void append(StorelessBivariateCovariance cov) { + double oldN = n; + n += cov.n; + final double deltaX = cov.meanX - meanX; + final double deltaY = cov.meanY - meanY; + meanX += deltaX * cov.n / n; + meanY += deltaY * cov.n / n; + covarianceNumerator += cov.covarianceNumerator + oldN * cov.n / n * deltaX * deltaY; + } + + /** + * Returns the number of observations. + * + * @return number of observations + */ + public double getN() { + return n; + } + + /** + * Return the current covariance estimate. + * + * @return the current covariance + * @throws NumberIsTooSmallException if the number of observations + * is < 2 + */ + public double getResult() throws NumberIsTooSmallException { + if (n < 2) { + throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_DIMENSION, + n, 2, true); + } + if (biasCorrected) { + return covarianceNumerator / (n - 1d); + } else { + return covarianceNumerator / n; + } + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessCovariance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessCovariance.java new file mode 100644 index 000000000..923005ac9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/StorelessCovariance.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.correlation; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Covariance implementation that does not require input data to be + * stored in memory. The size of the covariance matrix is specified in the + * constructor. Specific elements of the matrix are incrementally updated with + * calls to incrementRow() or increment Covariance(). + * + *

        This class is based on a paper written by Philippe Pébay: + * + * Formulas for Robust, One-Pass Parallel Computation of Covariances and + * Arbitrary-Order Statistical Moments, 2008, Technical Report SAND2008-6212, + * Sandia National Laboratories.

        + * + *

        Note: the underlying covariance matrix is symmetric, thus only the + * upper triangular part of the matrix is stored and updated each increment.

        + * + * @since 3.0 + */ +public class StorelessCovariance extends Covariance { + + /** the square covariance matrix (upper triangular part) */ + private StorelessBivariateCovariance[] covMatrix; + + /** dimension of the square covariance matrix */ + private int dimension; + + /** + * Create a bias corrected covariance matrix with a given dimension. + * + * @param dim the dimension of the square covariance matrix + */ + public StorelessCovariance(final int dim) { + this(dim, true); + } + + /** + * Create a covariance matrix with a given number of rows and columns and the + * indicated bias correction. + * + * @param dim the dimension of the covariance matrix + * @param biasCorrected if true the covariance estimate is corrected + * for bias, i.e. n-1 in the denominator, otherwise there is no bias correction, + * i.e. n in the denominator. + */ + public StorelessCovariance(final int dim, final boolean biasCorrected) { + dimension = dim; + covMatrix = new StorelessBivariateCovariance[dimension * (dimension + 1) / 2]; + initializeMatrix(biasCorrected); + } + + /** + * Initialize the internal two-dimensional array of + * {@link StorelessBivariateCovariance} instances. + * + * @param biasCorrected if the covariance estimate shall be corrected for bias + */ + private void initializeMatrix(final boolean biasCorrected) { + for(int i = 0; i < dimension; i++){ + for(int j = 0; j < dimension; j++){ + setElement(i, j, new StorelessBivariateCovariance(biasCorrected)); + } + } + } + + /** + * Returns the index (i, j) translated into the one-dimensional + * array used to store the upper triangular part of the symmetric + * covariance matrix. + * + * @param i the row index + * @param j the column index + * @return the corresponding index in the matrix array + */ + private int indexOf(final int i, final int j) { + return j < i ? i * (i + 1) / 2 + j : j * (j + 1) / 2 + i; + } + + /** + * Gets the element at index (i, j) from the covariance matrix + * @param i the row index + * @param j the column index + * @return the {@link StorelessBivariateCovariance} element at the given index + */ + private StorelessBivariateCovariance getElement(final int i, final int j) { + return covMatrix[indexOf(i, j)]; + } + + /** + * Sets the covariance element at index (i, j) in the covariance matrix + * @param i the row index + * @param j the column index + * @param cov the {@link StorelessBivariateCovariance} element to be set + */ + private void setElement(final int i, final int j, + final StorelessBivariateCovariance cov) { + covMatrix[indexOf(i, j)] = cov; + } + + /** + * Get the covariance for an individual element of the covariance matrix. + * + * @param xIndex row index in the covariance matrix + * @param yIndex column index in the covariance matrix + * @return the covariance of the given element + * @throws NumberIsTooSmallException if the number of observations + * in the cell is < 2 + */ + public double getCovariance(final int xIndex, + final int yIndex) + throws NumberIsTooSmallException { + + return getElement(xIndex, yIndex).getResult(); + + } + + /** + * Increment the covariance matrix with one row of data. + * + * @param data array representing one row of data. + * @throws DimensionMismatchException if the length of rowData + * does not match with the covariance matrix + */ + public void increment(final double[] data) + throws DimensionMismatchException { + + int length = data.length; + if (length != dimension) { + throw new DimensionMismatchException(length, dimension); + } + + // only update the upper triangular part of the covariance matrix + // as only these parts are actually stored + for (int i = 0; i < length; i++){ + for (int j = i; j < length; j++){ + getElement(i, j).increment(data[i], data[j]); + } + } + + } + + /** + * Appends {@code sc} to this, effectively aggregating the computations in {@code sc} + * with this. After invoking this method, covariances returned should be close + * to what would have been obtained by performing all of the {@link #increment(double[])} + * operations in {@code sc} directly on this. + * + * @param sc externally computed StorelessCovariance to add to this + * @throws DimensionMismatchException if the dimension of sc does not match this + * @since 3.3 + */ + public void append(StorelessCovariance sc) throws DimensionMismatchException { + if (sc.dimension != dimension) { + throw new DimensionMismatchException(sc.dimension, dimension); + } + + // only update the upper triangular part of the covariance matrix + // as only these parts are actually stored + for (int i = 0; i < dimension; i++) { + for (int j = i; j < dimension; j++) { + getElement(i, j).append(sc.getElement(i, j)); + } + } + } + + /** + * {@inheritDoc} + * @throws NumberIsTooSmallException if the number of observations + * in a cell is < 2 + */ + @Override + public RealMatrix getCovarianceMatrix() throws NumberIsTooSmallException { + return MatrixUtils.createRealMatrix(getData()); + } + + /** + * Return the covariance matrix as two-dimensional array. + * + * @return a two-dimensional double array of covariance values + * @throws NumberIsTooSmallException if the number of observations + * for a cell is < 2 + */ + public double[][] getData() throws NumberIsTooSmallException { + final double[][] data = new double[dimension][dimension]; + for (int i = 0; i < dimension; i++) { + for (int j = 0; j < dimension; j++) { + data[i][j] = getElement(i, j).getResult(); + } + } + return data; + } + + /** + * This {@link Covariance} method is not supported by a {@link StorelessCovariance}, + * since the number of bivariate observations does not have to be the same for different + * pairs of covariates - i.e., N as defined in {@link Covariance#getN()} is undefined. + * + * @return nothing as this implementation always throws a + * {@link MathUnsupportedOperationException} + * @throws MathUnsupportedOperationException in all cases + */ + @Override + public int getN() + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/package-info.java new file mode 100644 index 000000000..37db2c92a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/correlation/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Correlations/Covariance computations. + * + */ +package com.fr.third.org.apache.commons.math3.stat.correlation; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java new file mode 100644 index 000000000..a59bb90f6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * + * Abstract implementation of the {@link StorelessUnivariateStatistic} interface. + *

        + * Provides default evaluate() and incrementAll(double[]) + * implementations.

        + *

        + * Note that these implementations are not synchronized.

        + * + */ +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. + *

        + * 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[])}.

        + *

        + * Implementations may override this method with a more efficient and + * possibly more accurate implementation that works directly with the + * input array.

        + *

        + * If the array is null, a MathIllegalArgumentException is thrown.

        + * @param values input array + * @return the value of the statistic applied to the input array + * @throws MathIllegalArgumentException if values is null + * @see UnivariateStatistic#evaluate(double[]) + */ + @Override + public double evaluate(final double[] values) throws MathIllegalArgumentException { + if (values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + return evaluate(values, 0, values.length); + } + + /** + * This default implementation calls {@link #clear}, then invokes + * {@link #increment} in a loop over the specified portion of the input + * array, and then uses {@link #getResult} to compute the return value. + *

        + * 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)}.

        + *

        + * Implementations may override this method with a more efficient and + * possibly more accurate implementation that works directly with the + * input array.

        + *

        + * If the array is null or the index parameters are not valid, an + * MathIllegalArgumentException is thrown.

        + * @param values the input array + * @param begin the index of the first element to include + * @param length the number of elements to include + * @return the value of the statistic applied to the included array entries + * @throws MathIllegalArgumentException if the array is null or the indices are not valid + * @see UnivariateStatistic#evaluate(double[], int, int) + */ + @Override + public double evaluate(final double[] values, final int begin, + final int length) throws MathIllegalArgumentException { + if (test(values, begin, length)) { + clear(); + incrementAll(values, begin, length); + } + return getResult(); + } + + /** + * {@inheritDoc} + */ + @Override + public abstract StorelessUnivariateStatistic copy(); + + /** + * {@inheritDoc} + */ + public abstract void clear(); + + /** + * {@inheritDoc} + */ + public abstract double getResult(); + + /** + * {@inheritDoc} + */ + public abstract void increment(final double d); + + /** + * This default implementation just calls {@link #increment} in a loop over + * the input array. + *

        + * Throws IllegalArgumentException if the input values array is null.

        + * + * @param values values to add + * @throws MathIllegalArgumentException if values is null + * @see StorelessUnivariateStatistic#incrementAll(double[]) + */ + public void incrementAll(double[] values) throws MathIllegalArgumentException { + if (values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + incrementAll(values, 0, values.length); + } + + /** + * This default implementation just calls {@link #increment} in a loop over + * the specified portion of the input array. + *

        + * Throws IllegalArgumentException if the input values array is null.

        + * + * @param values array holding values to add + * @param begin index of the first array element to add + * @param length number of array elements to add + * @throws MathIllegalArgumentException if values is null + * @see StorelessUnivariateStatistic#incrementAll(double[], int, int) + */ + public void incrementAll(double[] values, int begin, int length) throws MathIllegalArgumentException { + if (test(values, begin, length)) { + int k = begin + length; + for (int i = begin; i < k; i++) { + increment(values[i]); + } + } + } + + /** + * Returns true iff object is an + * AbstractStorelessUnivariateStatistic returning the same + * values as this for getResult() and getN() + * @param object object to test equality against. + * @return true if object returns the same value as this + */ + @Override + public boolean equals(Object object) { + if (object == this ) { + return true; + } + if (object instanceof AbstractStorelessUnivariateStatistic == false) { + return false; + } + AbstractStorelessUnivariateStatistic stat = (AbstractStorelessUnivariateStatistic) object; + return Precision.equalsIncludingNaN(stat.getResult(), this.getResult()) && + Precision.equalsIncludingNaN(stat.getN(), this.getN()); + } + + /** + * Returns hash code based on getResult() and getN() + * + * @return hash code + */ + @Override + public int hashCode() { + return 31* (31 + MathUtils.hash(getResult())) + MathUtils.hash(getN()); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java new file mode 100644 index 000000000..3ca892d19 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Abstract base class for all implementations of the + * {@link UnivariateStatistic} interface. + *

        + * Provides a default implementation of evaluate(double[]), + * delegating to evaluate(double[], int, int) in the natural way. + *

        + *

        + * Also includes a test method that performs generic parameter + * validation for the evaluate methods.

        + * + */ +public abstract class AbstractUnivariateStatistic + implements UnivariateStatistic { + + /** Stored data. */ + private double[] storedData; + + /** + * Set the data array. + *

        + * The stored value is a copy of the parameter array, not the array itself. + *

        + * @param values data array to store (may be null to remove stored data) + * @see #evaluate() + */ + public void setData(final double[] values) { + storedData = (values == null) ? null : values.clone(); + } + + /** + * Get a copy of the stored data array. + * @return copy of the stored data array (may be null) + */ + public double[] getData() { + return (storedData == null) ? null : storedData.clone(); + } + + /** + * Get a reference to the stored data array. + * @return reference to the stored data array (may be null) + */ + protected double[] getDataRef() { + return storedData; + } + + /** + * Set the data array. The input array is copied, not referenced. + * + * @param values data array to store + * @param begin the index of the first element to include + * @param length the number of elements to include + * @throws MathIllegalArgumentException if values is null or the indices + * are not valid + * @see #evaluate() + */ + public void setData(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + if (values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + + if (begin < 0) { + throw new NotPositiveException(LocalizedFormats.START_POSITION, begin); + } + + if (length < 0) { + throw new NotPositiveException(LocalizedFormats.LENGTH, length); + } + + if (begin + length > values.length) { + throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END, + begin + length, values.length, true); + } + storedData = new double[length]; + System.arraycopy(values, begin, storedData, 0, length); + } + + /** + * Returns the result of evaluating the statistic over the stored data. + *

        + * The stored array is the one which was set by previous calls to {@link #setData(double[])}. + *

        + * @return the value of the statistic applied to the stored data + * @throws MathIllegalArgumentException if the stored data array is null + */ + public double evaluate() throws MathIllegalArgumentException { + return evaluate(storedData); + } + + /** + * {@inheritDoc} + */ + public double evaluate(final double[] values) throws MathIllegalArgumentException { + test(values, 0, 0); + return evaluate(values, 0, values.length); + } + + /** + * {@inheritDoc} + */ + public abstract double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException; + + /** + * {@inheritDoc} + */ + public abstract UnivariateStatistic copy(); + + /** + * This method is used by evaluate(double[], int, int) methods + * to verify that the input parameters designate a subarray of positive length. + *

        + *

          + *
        • returns true iff the parameters designate a subarray of + * positive length
        • + *
        • throws MathIllegalArgumentException if the array is null or + * or the indices are invalid
        • + *
        • returns false
        • if the array is non-null, but + * length is 0. + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return true if the parameters are valid and designate a subarray of positive length + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + */ + protected boolean test( + final double[] values, + final int begin, + final int length) throws MathIllegalArgumentException { + return MathArrays.verifyValues(values, begin, length, false); + } + + /** + * This method is used by evaluate(double[], int, int) methods + * to verify that the input parameters designate a subarray of positive length. + *

        + *

          + *
        • returns true iff the parameters designate a subarray of + * non-negative length
        • + *
        • throws IllegalArgumentException if the array is null or + * or the indices are invalid
        • + *
        • returns false
        • if the array is non-null, but + * length is 0 unless allowEmpty is true + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @param allowEmpty if true then zero length arrays are allowed + * @return true if the parameters are valid + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.0 + */ + protected boolean test(final double[] values, final int begin, + final int length, final boolean allowEmpty) throws MathIllegalArgumentException { + return MathArrays.verifyValues(values, begin, length, allowEmpty); + } + + /** + * This method is used by evaluate(double[], double[], int, int) 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. + *

        + *

          + *
        • returns true iff the parameters designate a subarray of + * positive length and the weights array contains legitimate values.
        • + *
        • throws IllegalArgumentException if any of the following are true: + *
          • the values array is null
          • + *
          • the weights array is null
          • + *
          • the weights array does not have the same length as the values array
          • + *
          • the weights array contains one or more infinite values
          • + *
          • the weights array contains one or more NaN values
          • + *
          • the weights array contains negative values
          • + *
          • the start and length arguments do not determine a valid array
          + *
        • + *
        • returns false
        • if the array is non-null, but + * length is 0. + *

        + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return true if the parameters are valid and designate a subarray of positive length + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 2.1 + */ + protected boolean test( + final double[] values, + final double[] weights, + final int begin, + final int length) throws MathIllegalArgumentException { + return MathArrays.verifyValues(values, weights, begin, length, false); + } + + /** + * This method is used by evaluate(double[], double[], int, int) 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. + *

        + *

          + *
        • returns true iff the parameters designate a subarray of + * non-negative length and the weights array contains legitimate values.
        • + *
        • throws MathIllegalArgumentException if any of the following are true: + *
          • the values array is null
          • + *
          • the weights array is null
          • + *
          • the weights array does not have the same length as the values array
          • + *
          • the weights array contains one or more infinite values
          • + *
          • the weights array contains one or more NaN values
          • + *
          • the weights array contains negative values
          • + *
          • the start and length arguments do not determine a valid array
          + *
        • + *
        • returns false
        • if the array is non-null, but + * length is 0 unless allowEmpty is true. + *

        + * + * @param values the input array. + * @param weights the weights array. + * @param begin index of the first array element to include. + * @param length the number of elements to include. + * @param allowEmpty if {@code true} than allow zero length arrays to pass. + * @return {@code true} if the parameters are valid. + * @throws NullArgumentException if either of the arrays are null + * @throws MathIllegalArgumentException if the array indices are not valid, + * the weights array contains NaN, infinite or negative elements, or there + * are no positive weights. + * @since 3.0 + */ + protected boolean test(final double[] values, final double[] weights, + final int begin, final int length, final boolean allowEmpty) throws MathIllegalArgumentException { + + return MathArrays.verifyValues(values, weights, begin, length, allowEmpty); + } +} + diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java new file mode 100644 index 000000000..5099ce692 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java @@ -0,0 +1,422 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; + +/** + *

        + * 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. + *

        + * 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. + *

        + * A static {@link #aggregate(Collection)} method is also included that computes + * aggregate statistics directly from a Collection of SummaryStatistics instances. + *

        + * When {@link #createContributingStatistics()} is used to create SummaryStatistics + * instances to be aggregated concurrently, the created instances' + * {@link SummaryStatistics#addValue(double)} methods must synchronize on the aggregating + * instance maintained by this class. In multithreaded environments, if the functionality + * provided by {@link #aggregate(Collection)} is adequate, that method should be used + * to avoid unnecessary computation and synchronization delays.

        + * + * @since 2.0 + * + */ +public class AggregateSummaryStatistics implements StatisticalSummary, + Serializable { + + + /** Serializable version identifier */ + private static final long serialVersionUID = -8207112444016386906L; + + /** + * A SummaryStatistics serving as a prototype for creating SummaryStatistics + * contributing to this aggregate + */ + private final SummaryStatistics statisticsPrototype; + + /** + * The SummaryStatistics in which aggregate statistics are accumulated. + */ + private final SummaryStatistics statistics; + + /** + * Initializes a new AggregateSummaryStatistics with default statistics + * implementations. + * + */ + public AggregateSummaryStatistics() { + // No try-catch or throws NAE because arg is guaranteed non-null + this(new SummaryStatistics()); + } + + /** + * Initializes a new AggregateSummaryStatistics with the specified statistics + * object as a prototype for contributing statistics and for the internal + * aggregate statistics. This provides for customized statistics implementations + * to be used by contributing and aggregate statistics. + * + * @param prototypeStatistics a {@code SummaryStatistics} serving as a + * prototype both for the internal aggregate statistics and for + * contributing statistics obtained via the + * {@code createContributingStatistics()} method. Being a prototype + * means that other objects are initialized by copying this object's state. + * If {@code null}, a new, default statistics object is used. Any statistic + * values in the prototype are propagated to contributing statistics + * objects and (once) into these aggregate statistics. + * @throws NullArgumentException if prototypeStatistics is null + * @see #createContributingStatistics() + */ + public AggregateSummaryStatistics(SummaryStatistics prototypeStatistics) throws NullArgumentException { + this(prototypeStatistics, + prototypeStatistics == null ? null : new SummaryStatistics(prototypeStatistics)); + } + + /** + * Initializes a new AggregateSummaryStatistics with the specified statistics + * object as a prototype for contributing statistics and for the internal + * aggregate statistics. This provides for different statistics implementations + * to be used by contributing and aggregate statistics and for an initial + * state to be supplied for the aggregate statistics. + * + * @param prototypeStatistics a {@code SummaryStatistics} serving as a + * prototype both for the internal aggregate statistics and for + * contributing statistics obtained via the + * {@code createContributingStatistics()} method. Being a prototype + * means that other objects are initialized by copying this object's state. + * If {@code null}, a new, default statistics object is used. Any statistic + * values in the prototype are propagated to contributing statistics + * objects, but not into these aggregate statistics. + * @param initialStatistics a {@code SummaryStatistics} to serve as the + * internal aggregate statistics object. If {@code null}, a new, default + * statistics object is used. + * @see #createContributingStatistics() + */ + public AggregateSummaryStatistics(SummaryStatistics prototypeStatistics, + SummaryStatistics initialStatistics) { + this.statisticsPrototype = + (prototypeStatistics == null) ? new SummaryStatistics() : prototypeStatistics; + this.statistics = + (initialStatistics == null) ? new SummaryStatistics() : initialStatistics; + } + + /** + * {@inheritDoc}. This version returns the maximum over all the aggregated + * data. + * + * @see StatisticalSummary#getMax() + */ + public double getMax() { + synchronized (statistics) { + return statistics.getMax(); + } + } + + /** + * {@inheritDoc}. This version returns the mean of all the aggregated data. + * + * @see StatisticalSummary#getMean() + */ + public double getMean() { + synchronized (statistics) { + return statistics.getMean(); + } + } + + /** + * {@inheritDoc}. This version returns the minimum over all the aggregated + * data. + * + * @see StatisticalSummary#getMin() + */ + public double getMin() { + synchronized (statistics) { + return statistics.getMin(); + } + } + + /** + * {@inheritDoc}. This version returns a count of all the aggregated data. + * + * @see StatisticalSummary#getN() + */ + public long getN() { + synchronized (statistics) { + return statistics.getN(); + } + } + + /** + * {@inheritDoc}. This version returns the standard deviation of all the + * aggregated data. + * + * @see StatisticalSummary#getStandardDeviation() + */ + public double getStandardDeviation() { + synchronized (statistics) { + return statistics.getStandardDeviation(); + } + } + + /** + * {@inheritDoc}. This version returns a sum of all the aggregated data. + * + * @see StatisticalSummary#getSum() + */ + public double getSum() { + synchronized (statistics) { + return statistics.getSum(); + } + } + + /** + * {@inheritDoc}. This version returns the variance of all the aggregated + * data. + * + * @see StatisticalSummary#getVariance() + */ + public double getVariance() { + synchronized (statistics) { + return statistics.getVariance(); + } + } + + /** + * Returns the sum of the logs of all the aggregated data. + * + * @return the sum of logs + * @see SummaryStatistics#getSumOfLogs() + */ + public double getSumOfLogs() { + synchronized (statistics) { + return statistics.getSumOfLogs(); + } + } + + /** + * Returns the geometric mean of all the aggregated data. + * + * @return the geometric mean + * @see SummaryStatistics#getGeometricMean() + */ + public double getGeometricMean() { + synchronized (statistics) { + return statistics.getGeometricMean(); + } + } + + /** + * Returns the sum of the squares of all the aggregated data. + * + * @return The sum of squares + * @see SummaryStatistics#getSumsq() + */ + public double getSumsq() { + synchronized (statistics) { + return statistics.getSumsq(); + } + } + + /** + * Returns a statistic related to the Second Central Moment. Specifically, + * what is returned is the sum of squared deviations from the sample mean + * among the all of the aggregated data. + * + * @return second central moment statistic + * @see SummaryStatistics#getSecondMoment() + */ + public double getSecondMoment() { + synchronized (statistics) { + return statistics.getSecondMoment(); + } + } + + /** + * Return a {@link StatisticalSummaryValues} instance reporting current + * aggregate statistics. + * + * @return Current values of aggregate statistics + */ + public StatisticalSummary getSummary() { + synchronized (statistics) { + return new StatisticalSummaryValues(getMean(), getVariance(), getN(), + getMax(), getMin(), getSum()); + } + } + + /** + * Creates and returns a {@code SummaryStatistics} whose data will be + * aggregated with those of this {@code AggregateSummaryStatistics}. + * + * @return a {@code SummaryStatistics} whose data will be aggregated with + * those of this {@code AggregateSummaryStatistics}. The initial state + * is a copy of the configured prototype statistics. + */ + public SummaryStatistics createContributingStatistics() { + SummaryStatistics contributingStatistics + = new AggregatingSummaryStatistics(statistics); + + // No try - catch or advertising NAE because neither argument will ever be null + SummaryStatistics.copy(statisticsPrototype, contributingStatistics); + + return contributingStatistics; + } + + /** + * Computes aggregate summary statistics. This method can be used to combine statistics + * computed over partitions or subsamples - i.e., the StatisticalSummaryValues returned + * should contain the same values that would have been obtained by computing a single + * StatisticalSummary over the combined dataset. + *

        + * Returns null if the collection is empty or null. + *

        + * + * @param statistics collection of SummaryStatistics to aggregate + * @return summary statistics for the combined dataset + */ + public static StatisticalSummaryValues aggregate(Collection statistics) { + if (statistics == null) { + return null; + } + Iterator iterator = statistics.iterator(); + if (!iterator.hasNext()) { + return null; + } + StatisticalSummary current = iterator.next(); + long n = current.getN(); + double min = current.getMin(); + double sum = current.getSum(); + double max = current.getMax(); + double var = current.getVariance(); + double m2 = var * (n - 1d); + double mean = current.getMean(); + while (iterator.hasNext()) { + current = iterator.next(); + if (current.getMin() < min || Double.isNaN(min)) { + min = current.getMin(); + } + if (current.getMax() > max || Double.isNaN(max)) { + max = current.getMax(); + } + sum += current.getSum(); + final double oldN = n; + final double curN = current.getN(); + n += curN; + final double meanDiff = current.getMean() - mean; + mean = sum / n; + final double curM2 = current.getVariance() * (curN - 1d); + m2 = m2 + curM2 + meanDiff * meanDiff * oldN * curN / n; + } + final double variance; + if (n == 0) { + variance = Double.NaN; + } else if (n == 1) { + variance = 0d; + } else { + variance = m2 / (n - 1); + } + return new StatisticalSummaryValues(mean, variance, n, max, min, sum); + } + + /** + * A SummaryStatistics that also forwards all values added to it to a second + * {@code SummaryStatistics} for aggregation. + * + * @since 2.0 + */ + private static class AggregatingSummaryStatistics extends SummaryStatistics { + + /** + * The serialization version of this class + */ + private static final long serialVersionUID = 1L; + + /** + * An additional SummaryStatistics into which values added to these + * statistics (and possibly others) are aggregated + */ + private final SummaryStatistics aggregateStatistics; + + /** + * Initializes a new AggregatingSummaryStatistics with the specified + * aggregate statistics object + * + * @param aggregateStatistics a {@code SummaryStatistics} into which + * values added to this statistics object should be aggregated + */ + AggregatingSummaryStatistics(SummaryStatistics aggregateStatistics) { + this.aggregateStatistics = aggregateStatistics; + } + + /** + * {@inheritDoc}. This version adds the provided value to the configured + * aggregate after adding it to these statistics. + * + * @see SummaryStatistics#addValue(double) + */ + @Override + public void addValue(double value) { + super.addValue(value); + synchronized (aggregateStatistics) { + aggregateStatistics.addValue(value); + } + } + + /** + * Returns true iff object is a + * SummaryStatistics 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java new file mode 100644 index 000000000..5ccb6f350 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java @@ -0,0 +1,777 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.GeometricMean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Kurtosis; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Mean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Skewness; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Max; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Min; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Percentile; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Sum; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfSquares; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.ResizableDoubleArray; + + +/** + * 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 windowSize 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. + * + *

        Note: this class is not threadsafe. Use + * {@link SynchronizedDescriptiveStatistics} if concurrent access from multiple + * threads is required.

        + * + */ +public class DescriptiveStatistics implements StatisticalSummary, Serializable { + + /** + * Represents an infinite window size. When the {@link #getWindowSize()} + * returns this value, there is no limit to the number of data values + * that can be stored in the dataset. + */ + public static final int INFINITE_WINDOW = -1; + + /** Serialization UID */ + private static final long serialVersionUID = 4133067267405273064L; + + /** Name of the setQuantile method. */ + private static final String SET_QUANTILE_METHOD_NAME = "setQuantile"; + + /** hold the window size **/ + protected int windowSize = INFINITE_WINDOW; + + /** + * Stored data values + */ + private ResizableDoubleArray eDA = new ResizableDoubleArray(); + + /** Mean statistic implementation - can be reset by setter. */ + private UnivariateStatistic meanImpl = new Mean(); + + /** Geometric mean statistic implementation - can be reset by setter. */ + private UnivariateStatistic geometricMeanImpl = new GeometricMean(); + + /** Kurtosis statistic implementation - can be reset by setter. */ + private UnivariateStatistic kurtosisImpl = new Kurtosis(); + + /** Maximum statistic implementation - can be reset by setter. */ + private UnivariateStatistic maxImpl = new Max(); + + /** Minimum statistic implementation - can be reset by setter. */ + private UnivariateStatistic minImpl = new Min(); + + /** Percentile statistic implementation - can be reset by setter. */ + private UnivariateStatistic percentileImpl = new Percentile(); + + /** Skewness statistic implementation - can be reset by setter. */ + private UnivariateStatistic skewnessImpl = new Skewness(); + + /** Variance statistic implementation - can be reset by setter. */ + private UnivariateStatistic varianceImpl = new Variance(); + + /** Sum of squares statistic implementation - can be reset by setter. */ + private UnivariateStatistic sumsqImpl = new SumOfSquares(); + + /** Sum statistic implementation - can be reset by setter. */ + private UnivariateStatistic sumImpl = new Sum(); + + /** + * Construct a DescriptiveStatistics instance with an infinite window + */ + public DescriptiveStatistics() { + } + + /** + * Construct a DescriptiveStatistics instance with the specified window + * + * @param window the window size. + * @throws MathIllegalArgumentException if window size is less than 1 but + * not equal to {@link #INFINITE_WINDOW} + */ + public DescriptiveStatistics(int window) throws MathIllegalArgumentException { + setWindowSize(window); + } + + /** + * Construct a DescriptiveStatistics instance with an infinite window + * and the initial data values in double[] initialDoubleArray. + * If initialDoubleArray is null, then this constructor corresponds to + * DescriptiveStatistics() + * + * @param initialDoubleArray the initial double[]. + */ + public DescriptiveStatistics(double[] initialDoubleArray) { + if (initialDoubleArray != null) { + eDA = new ResizableDoubleArray(initialDoubleArray); + } + } + + /** + * Copy constructor. Construct a new DescriptiveStatistics instance that + * is a copy of original. + * + * @param original DescriptiveStatistics instance to copy + * @throws NullArgumentException if original is null + */ + public DescriptiveStatistics(DescriptiveStatistics original) throws NullArgumentException { + copy(original, this); + } + + /** + * Adds the value to the dataset. If the dataset is at the maximum size + * (i.e., the number of stored elements equals the currently configured + * windowSize), the first (oldest) element in the dataset is discarded + * to make room for the new value. + * + * @param v the value to be added + */ + public void addValue(double v) { + if (windowSize != INFINITE_WINDOW) { + if (getN() == windowSize) { + eDA.addElementRolling(v); + } else if (getN() < windowSize) { + eDA.addElement(v); + } + } else { + eDA.addElement(v); + } + } + + /** + * Removes the most recent value from the dataset. + * + * @throws MathIllegalStateException if there are no elements stored + */ + public void removeMostRecentValue() throws MathIllegalStateException { + try { + eDA.discardMostRecentElements(1); + } catch (MathIllegalArgumentException ex) { + throw new MathIllegalStateException(LocalizedFormats.NO_DATA); + } + } + + /** + * Replaces the most recently stored value with the given value. + * There must be at least one element stored to call this method. + * + * @param v the value to replace the most recent stored value + * @return replaced value + * @throws MathIllegalStateException if there are no elements stored + */ + public double replaceMostRecentValue(double v) throws MathIllegalStateException { + return eDA.substituteMostRecentElement(v); + } + + /** + * Returns the + * arithmetic mean of the available values + * @return The mean or Double.NaN if no values have been added. + */ + public double getMean() { + return apply(meanImpl); + } + + /** + * Returns the + * geometric mean of the available values. + *

        + * See {@link GeometricMean} for details on the computing algorithm.

        + * + * @return The geometricMean, Double.NaN if no values have been added, + * or if any negative values have been added. + */ + public double getGeometricMean() { + return apply(geometricMeanImpl); + } + + /** + * Returns the (sample) variance of the available values. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #getPopulationVariance()} for the non-bias-corrected + * population variance.

        + * + * @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 + * population variance of the available values. + * + * @return The population variance, Double.NaN if no values have been added, + * or 0.0 for a single value set. + */ + public double getPopulationVariance() { + return apply(new Variance(false)); + } + + /** + * Returns the standard deviation of the available values. + * @return The standard deviation, Double.NaN if no values have been added + * or 0.0 for a single value set. + */ + public double getStandardDeviation() { + double stdDev = Double.NaN; + if (getN() > 0) { + if (getN() > 1) { + stdDev = FastMath.sqrt(getVariance()); + } else { + stdDev = 0.0; + } + } + return stdDev; + } + + /** + * Returns the quadratic mean, a.k.a. + * + * root-mean-square of the available values + * @return The quadratic mean or {@code Double.NaN} if no values + * have been added. + */ + public double getQuadraticMean() { + final long n = getN(); + return n > 0 ? FastMath.sqrt(getSumsq() / n) : Double.NaN; + } + + /** + * Returns the skewness of the available values. Skewness is a + * measure of the asymmetry of a given distribution. + * + * @return The skewness, Double.NaN if less than 3 values have been added. + */ + public double getSkewness() { + return apply(skewnessImpl); + } + + /** + * Returns the Kurtosis of the available values. Kurtosis is a + * measure of the "peakedness" of a distribution. + * + * @return The kurtosis, Double.NaN if less than 4 values have been added. + */ + public double getKurtosis() { + return apply(kurtosisImpl); + } + + /** + * Returns the maximum of the available values + * @return The max or Double.NaN if no values have been added. + */ + public double getMax() { + return apply(maxImpl); + } + + /** + * Returns the minimum of the available values + * @return The min or Double.NaN if no values have been added. + */ + public double getMin() { + return apply(minImpl); + } + + /** + * Returns the number of available values + * @return The number of available values + */ + public long getN() { + return eDA.getNumElements(); + } + + /** + * Returns the sum of the values that have been added to Univariate. + * @return The sum or Double.NaN if no values have been added + */ + public double getSum() { + return apply(sumImpl); + } + + /** + * Returns the sum of the squares of the available values. + * @return The sum of the squares or Double.NaN if no + * values have been added. + */ + public double getSumsq() { + return apply(sumsqImpl); + } + + /** + * Resets all statistics and storage + */ + public void clear() { + eDA.clear(); + } + + + /** + * Returns the maximum number of values that can be stored in the + * dataset, or INFINITE_WINDOW (-1) if there is no limit. + * + * @return The current window size or -1 if its Infinite. + */ + public int getWindowSize() { + return windowSize; + } + + /** + * WindowSize controls the number of values that contribute to the + * reported statistics. For example, if windowSize is set to 3 and the + * values {1,2,3,4,5} have been added in that order then + * the available values are {3,4,5} and all reported statistics will + * be based on these values. If {@code windowSize} is decreased as a result + * of this call and there are more than the new value of elements in the + * current dataset, values from the front of the array are discarded to + * reduce the dataset to {@code windowSize} elements. + * + * @param windowSize sets the size of the window. + * @throws MathIllegalArgumentException if window size is less than 1 but + * not equal to {@link #INFINITE_WINDOW} + */ + public void setWindowSize(int windowSize) throws MathIllegalArgumentException { + if (windowSize < 1 && windowSize != INFINITE_WINDOW) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POSITIVE_WINDOW_SIZE, windowSize); + } + + this.windowSize = windowSize; + + // We need to check to see if we need to discard elements + // from the front of the array. If the windowSize is less than + // the current number of elements. + if (windowSize != INFINITE_WINDOW && windowSize < eDA.getNumElements()) { + eDA.discardFrontElements(eDA.getNumElements() - windowSize); + } + } + + /** + * Returns the current set of values in an array of double primitives. + * The order of addition is preserved. The returned array is a fresh + * copy of the underlying data -- i.e., it is not a reference to the + * stored data. + * + * @return returns the current set of numbers in the order in which they + * were added to this set + */ + public double[] getValues() { + return eDA.getElements(); + } + + /** + * Returns the current set of values in an array of double primitives, + * sorted in ascending order. The returned array is a fresh + * copy of the underlying data -- i.e., it is not a reference to the + * stored data. + * @return returns the current set of + * numbers sorted in ascending order + */ + public double[] getSortedValues() { + double[] sort = getValues(); + Arrays.sort(sort); + return sort; + } + + /** + * Returns the element at the specified index + * @param index The Index of the element + * @return return the element at the specified index + */ + public double getElement(int index) { + return eDA.getElement(index); + } + + /** + * Returns an estimate for the pth percentile of the stored values. + *

        + * The implementation provided here follows the first estimation procedure presented + * here. + *

        + * Preconditions:

          + *
        • 0 < p ≤ 100 (otherwise an + * MathIllegalArgumentException is thrown)
        • + *
        • at least one value must be stored (returns Double.NaN + * otherwise)
        • + *

        + * + * @param p the requested percentile (scaled from 0 - 100) + * @return An estimate for the pth percentile of the stored data + * @throws MathIllegalStateException if percentile implementation has been + * overridden and the supplied implementation does not support setQuantile + * @throws MathIllegalArgumentException if p is not a valid quantile + */ + public double getPercentile(double p) throws MathIllegalStateException, MathIllegalArgumentException { + if (percentileImpl instanceof Percentile) { + ((Percentile) percentileImpl).setQuantile(p); + } else { + try { + percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME, + new Class[] {Double.TYPE}).invoke(percentileImpl, + new Object[] {Double.valueOf(p)}); + } catch (NoSuchMethodException e1) { // Setter guard should prevent + throw new MathIllegalStateException( + LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD, + percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME); + } catch (IllegalAccessException e2) { + throw new MathIllegalStateException( + LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD, + SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName()); + } catch (InvocationTargetException e3) { + throw new IllegalStateException(e3.getCause()); + } + } + return apply(percentileImpl); + } + + /** + * Generates a text report displaying univariate statistics from values + * that have been added. Each statistic is displayed on a separate + * line. + * + * @return String with line feeds displaying statistics + */ + @Override + public String toString() { + StringBuilder outBuffer = new StringBuilder(); + String endl = "\n"; + outBuffer.append("DescriptiveStatistics:").append(endl); + outBuffer.append("n: ").append(getN()).append(endl); + outBuffer.append("min: ").append(getMin()).append(endl); + outBuffer.append("max: ").append(getMax()).append(endl); + outBuffer.append("mean: ").append(getMean()).append(endl); + outBuffer.append("std dev: ").append(getStandardDeviation()) + .append(endl); + try { + // No catch for MIAE because actual parameter is valid below + outBuffer.append("median: ").append(getPercentile(50)).append(endl); + } catch (MathIllegalStateException ex) { + outBuffer.append("median: unavailable").append(endl); + } + outBuffer.append("skewness: ").append(getSkewness()).append(endl); + outBuffer.append("kurtosis: ").append(getKurtosis()).append(endl); + return outBuffer.toString(); + } + + /** + * Apply the given statistic to the data associated with this set of statistics. + * @param stat the statistic to apply + * @return the computed value of the statistic. + */ + public double apply(UnivariateStatistic stat) { + // No try-catch or advertised exception here because arguments are guaranteed valid + return eDA.compute(stat); + } + + // Implementation getters and setter + + /** + * Returns the currently configured mean implementation. + * + * @return the UnivariateStatistic implementing the mean + * @since 1.2 + */ + public synchronized UnivariateStatistic getMeanImpl() { + return meanImpl; + } + + /** + *

        Sets the implementation for the mean.

        + * + * @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; + } + + /** + *

        Sets the implementation for the gemoetric mean.

        + * + * @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; + } + + /** + *

        Sets the implementation for the kurtosis.

        + * + * @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; + } + + /** + *

        Sets the implementation for the maximum.

        + * + * @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; + } + + /** + *

        Sets the implementation for the minimum.

        + * + * @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 UnivariateStatistic must provide a + * setQuantile(double) method; otherwise + * IllegalArgumentException is thrown. + * + * @param percentileImpl the percentileImpl to set + * @throws MathIllegalArgumentException if the supplied implementation does not + * provide a setQuantile method + * @since 1.2 + */ + public synchronized void setPercentileImpl(UnivariateStatistic percentileImpl) + throws MathIllegalArgumentException { + try { + percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME, + new Class[] {Double.TYPE}).invoke(percentileImpl, + new Object[] {Double.valueOf(50.0d)}); + } catch (NoSuchMethodException e1) { + throw new MathIllegalArgumentException( + LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD, + percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME); + } catch (IllegalAccessException e2) { + throw new MathIllegalArgumentException( + LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD, + SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName()); + } catch (InvocationTargetException e3) { + throw new IllegalArgumentException(e3.getCause()); + } + this.percentileImpl = percentileImpl; + } + + /** + * Returns the currently configured skewness implementation. + * + * @return the UnivariateStatistic implementing the skewness + * @since 1.2 + */ + public synchronized UnivariateStatistic getSkewnessImpl() { + return skewnessImpl; + } + + /** + *

        Sets the implementation for the skewness.

        + * + * @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; + } + + /** + *

        Sets the implementation for the variance.

        + * + * @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; + } + + /** + *

        Sets the implementation for the sum of squares.

        + * + * @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; + } + + /** + *

        Sets the implementation for the sum.

        + * + * @param sumImpl the UnivariateStatistic instance to use + * for computing the sum + * @since 1.2 + */ + public synchronized void setSumImpl(UnivariateStatistic sumImpl) { + this.sumImpl = sumImpl; + } + + /** + * Returns a copy of this DescriptiveStatistics instance with the same internal state. + * + * @return a copy of this + */ + public DescriptiveStatistics copy() { + DescriptiveStatistics result = new DescriptiveStatistics(); + // No try-catch or advertised exception because parms are guaranteed valid + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source DescriptiveStatistics to copy + * @param dest DescriptiveStatistics to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(DescriptiveStatistics source, DescriptiveStatistics dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + // Copy data and window size + dest.eDA = source.eDA.copy(); + dest.windowSize = source.windowSize; + + // Copy implementations + dest.maxImpl = source.maxImpl.copy(); + dest.meanImpl = source.meanImpl.copy(); + dest.minImpl = source.minImpl.copy(); + dest.sumImpl = source.sumImpl.copy(); + dest.varianceImpl = source.varianceImpl.copy(); + dest.sumsqImpl = source.sumsqImpl.copy(); + dest.geometricMeanImpl = source.geometricMeanImpl.copy(); + dest.kurtosisImpl = source.kurtosisImpl; + dest.skewnessImpl = source.skewnessImpl; + dest.percentileImpl = source.percentileImpl; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java new file mode 100644 index 000000000..b477ccbd6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java @@ -0,0 +1,635 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.GeometricMean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Mean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.VectorialCovariance; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Max; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Min; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Sum; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfLogs; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfSquares; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + *

        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.

        + * + *

        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 addValue is called. No configuration is + * necessary to use the default, commons-math provided implementations.

        + * + *

        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 getXxx + * methods where Xxx is a statistic return an array of double + * values, where for i = 0,...,n-1 the ith array element is the + * value of the given statistic for data range consisting of the ith element of + * each of the input n-tuples. For example, if addValue is called + * with actual parameters {0, 1, 2}, then {3, 4, 5} and finally {6, 7, 8}, + * getSum will return a three-element array with values + * {0+3+6, 1+4+7, 2+5+8}

        + * + *

        Note: This class is not thread-safe. Use + * {@link SynchronizedMultivariateSummaryStatistics} if concurrent access from multiple + * threads is required.

        + * + * @since 1.2 + */ +public class MultivariateSummaryStatistics + implements StatisticalMultivariateSummary, Serializable { + + /** Serialization UID */ + private static final long serialVersionUID = 2271900808994826718L; + + /** Dimension of the data. */ + private int k; + + /** Count of values that have been added */ + private long n = 0; + + /** Sum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] sumImpl; + + /** Sum of squares statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] sumSqImpl; + + /** Minimum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] minImpl; + + /** Maximum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] maxImpl; + + /** Sum of log statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] sumLogImpl; + + /** Geometric mean statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] geoMeanImpl; + + /** Mean statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic[] meanImpl; + + /** Covariance statistic implementation - cannot be reset. */ + private VectorialCovariance covarianceImpl; + + /** + * Construct a MultivariateSummaryStatistics instance + * @param k dimension of the data + * @param isCovarianceBiasCorrected if true, the unbiased sample + * covariance is computed, otherwise the biased population covariance + * is computed + */ + public MultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) { + this.k = k; + + sumImpl = new StorelessUnivariateStatistic[k]; + sumSqImpl = new StorelessUnivariateStatistic[k]; + minImpl = new StorelessUnivariateStatistic[k]; + maxImpl = new StorelessUnivariateStatistic[k]; + sumLogImpl = new StorelessUnivariateStatistic[k]; + geoMeanImpl = new StorelessUnivariateStatistic[k]; + meanImpl = new StorelessUnivariateStatistic[k]; + + for (int i = 0; i < k; ++i) { + sumImpl[i] = new Sum(); + sumSqImpl[i] = new SumOfSquares(); + minImpl[i] = new Min(); + maxImpl[i] = new Max(); + sumLogImpl[i] = new SumOfLogs(); + geoMeanImpl[i] = new GeometricMean(); + meanImpl[i] = new Mean(); + } + + covarianceImpl = + new VectorialCovariance(k, isCovarianceBiasCorrected); + + } + + /** + * Add an n-tuple to the data + * + * @param value the n-tuple to add + * @throws DimensionMismatchException if the length of the array + * does not match the one used at construction + */ + public void addValue(double[] value) throws DimensionMismatchException { + checkDimension(value.length); + for (int i = 0; i < k; ++i) { + double v = value[i]; + sumImpl[i].increment(v); + sumSqImpl[i].increment(v); + minImpl[i].increment(v); + maxImpl[i].increment(v); + sumLogImpl[i].increment(v); + geoMeanImpl[i].increment(v); + meanImpl[i].increment(v); + } + covarianceImpl.increment(value); + n++; + } + + /** + * Returns the dimension of the data + * @return The dimension of the data + */ + public int getDimension() { + return k; + } + + /** + * Returns the number of available values + * @return The number of available values + */ + public long getN() { + return n; + } + + /** + * Returns an array of the results of a statistic. + * @param stats univariate statistic array + * @return results array + */ + private double[] getResults(StorelessUnivariateStatistic[] stats) { + double[] results = new double[stats.length]; + for (int i = 0; i < results.length; ++i) { + results[i] = stats[i].getResult(); + } + return results; + } + + /** + * Returns an array whose ith entry is the sum of the + * ith 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 ith entry is the sum of squares of the + * ith 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 ith entry is the sum of logs of the + * ith 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 ith entry is the mean of the + * ith 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 ith entry is the standard deviation of the + * ith 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 ith entry is the maximum of the + * ith 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 ith entry is the minimum of the + * ith 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 ith entry is the geometric mean of the + * ith 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 object is a MultivariateSummaryStatistics + * instance and all statistics have the same values as this. + * @param object the object to test equality against. + * @return true if object equals this + */ + @Override + public boolean equals(Object object) { + if (object == this ) { + return true; + } + if (object instanceof MultivariateSummaryStatistics == false) { + return false; + } + MultivariateSummaryStatistics stat = (MultivariateSummaryStatistics) object; + return MathArrays.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) && + MathArrays.equalsIncludingNaN(stat.getMax(), getMax()) && + MathArrays.equalsIncludingNaN(stat.getMean(), getMean()) && + MathArrays.equalsIncludingNaN(stat.getMin(), getMin()) && + Precision.equalsIncludingNaN(stat.getN(), getN()) && + MathArrays.equalsIncludingNaN(stat.getSum(), getSum()) && + MathArrays.equalsIncludingNaN(stat.getSumSq(), getSumSq()) && + MathArrays.equalsIncludingNaN(stat.getSumLog(), getSumLog()) && + stat.getCovariance().equals( getCovariance()); + } + + /** + * Returns hash code based on values of statistics + * + * @return hash code + */ + @Override + public int hashCode() { + int result = 31 + MathUtils.hash(getGeometricMean()); + result = result * 31 + MathUtils.hash(getGeometricMean()); + result = result * 31 + MathUtils.hash(getMax()); + result = result * 31 + MathUtils.hash(getMean()); + result = result * 31 + MathUtils.hash(getMin()); + result = result * 31 + MathUtils.hash(getN()); + result = result * 31 + MathUtils.hash(getSum()); + result = result * 31 + MathUtils.hash(getSumSq()); + result = result * 31 + MathUtils.hash(getSumLog()); + result = result * 31 + getCovariance().hashCode(); + return result; + } + + // Getters and setters for statistics implementations + /** + * Sets statistics implementations. + * @param newImpl new implementations for statistics + * @param oldImpl old implementations for statistics + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e. if n > 0) + */ + private void setImpl(StorelessUnivariateStatistic[] newImpl, + StorelessUnivariateStatistic[] oldImpl) throws MathIllegalStateException, + DimensionMismatchException { + checkEmpty(); + checkDimension(newImpl.length); + System.arraycopy(newImpl, 0, oldImpl, 0, newImpl.length); + } + + /** + * Returns the currently configured Sum implementation + * + * @return the StorelessUnivariateStatistic implementing the sum + */ + public StorelessUnivariateStatistic[] getSumImpl() { + return sumImpl.clone(); + } + + /** + *

        Sets the implementation for the Sum.

        + *

        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.

        + * + * @param sumImpl the StorelessUnivariateStatistic instance to use + * for computing the Sum + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setSumImpl(StorelessUnivariateStatistic[] sumImpl) + throws MathIllegalStateException, DimensionMismatchException { + setImpl(sumImpl, this.sumImpl); + } + + /** + * Returns the currently configured sum of squares implementation + * + * @return the StorelessUnivariateStatistic implementing the sum of squares + */ + public StorelessUnivariateStatistic[] getSumsqImpl() { + return sumSqImpl.clone(); + } + + /** + *

        Sets the implementation for the sum of squares.

        + *

        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.

        + * + * @param sumsqImpl the StorelessUnivariateStatistic instance to use + * for computing the sum of squares + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl) + throws MathIllegalStateException, DimensionMismatchException { + setImpl(sumsqImpl, this.sumSqImpl); + } + + /** + * Returns the currently configured minimum implementation + * + * @return the StorelessUnivariateStatistic implementing the minimum + */ + public StorelessUnivariateStatistic[] getMinImpl() { + return minImpl.clone(); + } + + /** + *

        Sets the implementation for the minimum.

        + *

        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.

        + * + * @param minImpl the StorelessUnivariateStatistic instance to use + * for computing the minimum + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setMinImpl(StorelessUnivariateStatistic[] minImpl) + throws MathIllegalStateException, DimensionMismatchException { + setImpl(minImpl, this.minImpl); + } + + /** + * Returns the currently configured maximum implementation + * + * @return the StorelessUnivariateStatistic implementing the maximum + */ + public StorelessUnivariateStatistic[] getMaxImpl() { + return maxImpl.clone(); + } + + /** + *

        Sets the implementation for the maximum.

        + *

        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.

        + * + * @param maxImpl the StorelessUnivariateStatistic instance to use + * for computing the maximum + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setMaxImpl(StorelessUnivariateStatistic[] maxImpl) + throws MathIllegalStateException, DimensionMismatchException{ + setImpl(maxImpl, this.maxImpl); + } + + /** + * Returns the currently configured sum of logs implementation + * + * @return the StorelessUnivariateStatistic implementing the log sum + */ + public StorelessUnivariateStatistic[] getSumLogImpl() { + return sumLogImpl.clone(); + } + + /** + *

        Sets the implementation for the sum of logs.

        + *

        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.

        + * + * @param sumLogImpl the StorelessUnivariateStatistic instance to use + * for computing the log sum + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl) + throws MathIllegalStateException, DimensionMismatchException{ + setImpl(sumLogImpl, this.sumLogImpl); + } + + /** + * Returns the currently configured geometric mean implementation + * + * @return the StorelessUnivariateStatistic implementing the geometric mean + */ + public StorelessUnivariateStatistic[] getGeoMeanImpl() { + return geoMeanImpl.clone(); + } + + /** + *

        Sets the implementation for the geometric mean.

        + *

        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.

        + * + * @param geoMeanImpl the StorelessUnivariateStatistic instance to use + * for computing the geometric mean + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl) + throws MathIllegalStateException, DimensionMismatchException { + setImpl(geoMeanImpl, this.geoMeanImpl); + } + + /** + * Returns the currently configured mean implementation + * + * @return the StorelessUnivariateStatistic implementing the mean + */ + public StorelessUnivariateStatistic[] getMeanImpl() { + return meanImpl.clone(); + } + + /** + *

        Sets the implementation for the mean.

        + *

        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.

        + * + * @param meanImpl the StorelessUnivariateStatistic instance to use + * for computing the mean + * @throws DimensionMismatchException if the array dimension + * does not match the one used at construction + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setMeanImpl(StorelessUnivariateStatistic[] meanImpl) + throws MathIllegalStateException, DimensionMismatchException{ + setImpl(meanImpl, this.meanImpl); + } + + /** + * Throws MathIllegalStateException if the statistic is not empty. + * @throws MathIllegalStateException if n > 0. + */ + private void checkEmpty() throws MathIllegalStateException { + if (n > 0) { + throw new MathIllegalStateException( + LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, n); + } + } + + /** + * Throws DimensionMismatchException if dimension != k. + * @param dimension dimension to check + * @throws DimensionMismatchException if dimension != k + */ + private void checkDimension(int dimension) throws DimensionMismatchException { + if (dimension != k) { + throw new DimensionMismatchException(dimension, k); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java new file mode 100644 index 000000000..a6329d903 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Reporting interface for basic multivariate statistics. + * + * @since 1.2 + */ +public interface StatisticalMultivariateSummary { + + /** + * Returns the dimension of the data + * @return The dimension of the data + */ + int getDimension(); + + /** + * Returns an array whose ith entry is the + * mean of the ith 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 ith entry is the + * standard deviation of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component standard deviations + */ + double[] getStandardDeviation(); + + /** + * Returns an array whose ith entry is the + * maximum of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component maxima + */ + double[] getMax(); + + /** + * Returns an array whose ith entry is the + * minimum of the ith 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 ith entry is the + * geometric mean of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component geometric means + */ + double[] getGeometricMean(); + + /** + * Returns an array whose ith entry is the + * sum of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component sums + */ + double[] getSum(); + + /** + * Returns an array whose ith entry is the + * sum of squares of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component sums of squares + */ + double[] getSumSq(); + + /** + * Returns an array whose ith entry is the + * sum of logs of the ith entries of the arrays + * that correspond to each multivariate sample + * + * @return the array of component log sums + */ + double[] getSumLog(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java new file mode 100644 index 000000000..20518292d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +/** + * Reporting interface for basic univariate statistics. + * + */ +public interface StatisticalSummary { + + /** + * Returns the + * arithmetic mean 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java new file mode 100644 index 000000000..8e90a8ace --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Value object representing the results of a univariate statistical summary. + * + */ +public class StatisticalSummaryValues implements Serializable, + StatisticalSummary { + + /** Serialization id */ + private static final long serialVersionUID = -5108854841843722536L; + + /** The sample mean */ + private final double mean; + + /** The sample variance */ + private final double variance; + + /** The number of observations in the sample */ + private final long n; + + /** The maximum value */ + private final double max; + + /** The minimum value */ + private final double min; + + /** The sum of the sample values */ + private final double sum; + + /** + * Constructor + * + * @param mean the sample mean + * @param variance the sample variance + * @param n the number of observations in the sample + * @param max the maximum value + * @param min the minimum value + * @param sum the sum of the values + */ + public StatisticalSummaryValues(double mean, double variance, long n, + double max, double min, double sum) { + super(); + this.mean = mean; + this.variance = variance; + this.n = n; + this.max = max; + this.min = min; + this.sum = sum; + } + + /** + * @return Returns the max. + */ + public double getMax() { + return max; + } + + /** + * @return Returns the mean. + */ + public double getMean() { + return mean; + } + + /** + * @return Returns the min. + */ + public double getMin() { + return min; + } + + /** + * @return Returns the number of values. + */ + public long getN() { + return n; + } + + /** + * @return Returns the sum. + */ + public double getSum() { + return sum; + } + + /** + * @return Returns the standard deviation + */ + public double getStandardDeviation() { + return FastMath.sqrt(variance); + } + + /** + * @return Returns the variance. + */ + public double getVariance() { + return variance; + } + + /** + * Returns true iff object is a + * StatisticalSummaryValues instance and all statistics have + * the same values as this. + * + * @param object the object to test equality against. + * @return true if object equals this + */ + @Override + public boolean equals(Object object) { + if (object == this ) { + return true; + } + if (object instanceof StatisticalSummaryValues == false) { + return false; + } + StatisticalSummaryValues stat = (StatisticalSummaryValues) object; + return Precision.equalsIncludingNaN(stat.getMax(), getMax()) && + Precision.equalsIncludingNaN(stat.getMean(), getMean()) && + Precision.equalsIncludingNaN(stat.getMin(), getMin()) && + Precision.equalsIncludingNaN(stat.getN(), getN()) && + Precision.equalsIncludingNaN(stat.getSum(), getSum()) && + Precision.equalsIncludingNaN(stat.getVariance(), getVariance()); + } + + /** + * Returns hash code based on values of statistics + * + * @return hash code + */ + @Override + public int hashCode() { + int result = 31 + MathUtils.hash(getMax()); + result = result * 31 + MathUtils.hash(getMean()); + result = result * 31 + MathUtils.hash(getMin()); + result = result * 31 + MathUtils.hash(getN()); + result = result * 31 + MathUtils.hash(getSum()); + result = result * 31 + MathUtils.hash(getVariance()); + return result; + } + + /** + * Generates a text report displaying values of statistics. + * Each statistic is displayed on a separate line. + * + * @return String with line feeds displaying statistics + */ + @Override + public String toString() { + StringBuffer outBuffer = new StringBuffer(); + String endl = "\n"; + outBuffer.append("StatisticalSummaryValues:").append(endl); + outBuffer.append("n: ").append(getN()).append(endl); + outBuffer.append("min: ").append(getMin()).append(endl); + outBuffer.append("max: ").append(getMax()).append(endl); + outBuffer.append("mean: ").append(getMean()).append(endl); + outBuffer.append("std dev: ").append(getStandardDeviation()) + .append(endl); + outBuffer.append("variance: ").append(getVariance()).append(endl); + outBuffer.append("sum: ").append(getSum()).append(endl); + return outBuffer.toString(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java new file mode 100644 index 000000000..f7b51829e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Extends the definition of {@link UnivariateStatistic} with + * {@link #increment} and {@link #incrementAll(double[])} methods for adding + * values and updating internal state. + *

        + * 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.

        + * + */ +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 incrementally to the dataset. + * + * @param values array holding the new values to add + * @throws MathIllegalArgumentException if the array is null + */ + void incrementAll(double[] values) throws MathIllegalArgumentException; + + /** + * Updates the internal state of the statistic to reflect addition of + * the values in the designated portion of the values array. Does not + * clear the statistic first -- i.e., the values are added + * incrementally to the dataset. + * + * @param values array holding the new values to add + * @param start the array index of the first value to add + * @param length the number of elements to add + * @throws MathIllegalArgumentException if the array is null or the index + */ + void incrementAll(double[] values, int start, int length) throws MathIllegalArgumentException; + + /** + * Returns the current value of the Statistic. + * @return value of the statistic, Double.NaN 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java new file mode 100644 index 000000000..ed2539cd1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java @@ -0,0 +1,765 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.GeometricMean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Mean; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.SecondMoment; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Max; +import com.fr.third.org.apache.commons.math3.stat.descriptive.rank.Min; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Sum; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfLogs; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfSquares; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + *

        + * 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. + *

        + *

        + * 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 addValue + * is called. No configuration is necessary to use the default, commons-math + * provided implementations. + *

        + *

        + * Note: This class is not thread-safe. Use + * {@link SynchronizedSummaryStatistics} if concurrent access from multiple + * threads is required. + *

        + */ +public class SummaryStatistics implements StatisticalSummary, Serializable { + + /** Serialization UID */ + private static final long serialVersionUID = -2021321786743555871L; + + /** count of values that have been added */ + private long n = 0; + + /** SecondMoment is used to compute the mean and variance */ + private SecondMoment secondMoment = new SecondMoment(); + + /** sum of values that have been added */ + private Sum sum = new Sum(); + + /** sum of the square of each value that has been added */ + private SumOfSquares sumsq = new SumOfSquares(); + + /** min of values that have been added */ + private Min min = new Min(); + + /** max of values that have been added */ + private Max max = new Max(); + + /** sumLog of values that have been added */ + private SumOfLogs sumLog = new SumOfLogs(); + + /** geoMean of values that have been added */ + private GeometricMean geoMean = new GeometricMean(sumLog); + + /** mean of values that have been added */ + private Mean mean = new Mean(secondMoment); + + /** variance of values that have been added */ + private Variance variance = new Variance(secondMoment); + + /** Sum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic sumImpl = sum; + + /** Sum of squares statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic sumsqImpl = sumsq; + + /** Minimum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic minImpl = min; + + /** Maximum statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic maxImpl = max; + + /** Sum of log statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic sumLogImpl = sumLog; + + /** Geometric mean statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic geoMeanImpl = geoMean; + + /** Mean statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic meanImpl = mean; + + /** Variance statistic implementation - can be reset by setter. */ + private StorelessUnivariateStatistic varianceImpl = variance; + + /** + * Construct a SummaryStatistics instance + */ + public SummaryStatistics() { + } + + /** + * A copy constructor. Creates a deep-copy of the {@code original}. + * + * @param original the {@code SummaryStatistics} instance to copy + * @throws NullArgumentException if original is null + */ + public SummaryStatistics(SummaryStatistics original) throws NullArgumentException { + copy(original, this); + } + + /** + * Return a {@link StatisticalSummaryValues} instance reporting current + * statistics. + * @return Current values of statistics + */ + public StatisticalSummary getSummary() { + return new StatisticalSummaryValues(getMean(), getVariance(), getN(), + getMax(), getMin(), getSum()); + } + + /** + * Add a value to the data + * @param value the value to add + */ + public void addValue(double value) { + sumImpl.increment(value); + sumsqImpl.increment(value); + minImpl.increment(value); + maxImpl.increment(value); + sumLogImpl.increment(value); + secondMoment.increment(value); + // If mean, variance or geomean have been overridden, + // need to increment these + if (meanImpl != mean) { + meanImpl.increment(value); + } + if (varianceImpl != variance) { + varianceImpl.increment(value); + } + if (geoMeanImpl != geoMean) { + geoMeanImpl.increment(value); + } + n++; + } + + /** + * Returns the number of available values + * @return The number of available values + */ + public long getN() { + return n; + } + + /** + * Returns the sum of the values that have been added + * @return The sum or Double.NaN 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. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return The sum of squares + */ + public double getSumsq() { + return sumsqImpl.getResult(); + } + + /** + * Returns the mean of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return the mean + */ + public double getMean() { + return meanImpl.getResult(); + } + + /** + * Returns the standard deviation of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return the standard deviation + */ + public double getStandardDeviation() { + double stdDev = Double.NaN; + if (getN() > 0) { + if (getN() > 1) { + stdDev = FastMath.sqrt(getVariance()); + } else { + stdDev = 0.0; + } + } + return stdDev; + } + + /** + * Returns the quadratic mean, a.k.a. + * + * root-mean-square of the available values + * @return The quadratic mean or {@code Double.NaN} if no values + * have been added. + */ + public double getQuadraticMean() { + final long size = getN(); + return size > 0 ? FastMath.sqrt(getSumsq() / size) : Double.NaN; + } + + /** + * Returns the (sample) variance of the available values. + * + *

        This method returns the bias-corrected sample variance (using {@code n - 1} in + * the denominator). Use {@link #getPopulationVariance()} for the non-bias-corrected + * population variance.

        + * + *

        Double.NaN is returned if no values have been added.

        + * + * @return the variance + */ + public double getVariance() { + return varianceImpl.getResult(); + } + + /** + * Returns the + * population variance of the values that have been added. + * + *

        Double.NaN is returned if no values have been added.

        + * + * @return the population variance + */ + public double getPopulationVariance() { + Variance populationVariance = new Variance(secondMoment); + populationVariance.setBiasCorrected(false); + return populationVariance.getResult(); + } + + /** + * Returns the maximum of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return the maximum + */ + public double getMax() { + return maxImpl.getResult(); + } + + /** + * Returns the minimum of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return the minimum + */ + public double getMin() { + return minImpl.getResult(); + } + + /** + * Returns the geometric mean of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @return the geometric mean + */ + public double getGeometricMean() { + return geoMeanImpl.getResult(); + } + + /** + * Returns the sum of the logs of the values that have been added. + *

        + * Double.NaN is returned if no values have been added. + *

        + * @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. + *

        + * Returns Double.NaN if no data values have been added and + * returns 0 if there is just one value in the data set.

        + *

        + * @return second central moment statistic + * @since 2.0 + */ + public double getSecondMoment() { + return secondMoment.getResult(); + } + + /** + * Generates a text report displaying summary statistics from values that + * have been added. + * @return String with line feeds displaying statistics + * @since 1.2 + */ + @Override + public String toString() { + StringBuilder outBuffer = new StringBuilder(); + String endl = "\n"; + outBuffer.append("SummaryStatistics:").append(endl); + outBuffer.append("n: ").append(getN()).append(endl); + outBuffer.append("min: ").append(getMin()).append(endl); + outBuffer.append("max: ").append(getMax()).append(endl); + outBuffer.append("sum: ").append(getSum()).append(endl); + outBuffer.append("mean: ").append(getMean()).append(endl); + outBuffer.append("geometric mean: ").append(getGeometricMean()) + .append(endl); + outBuffer.append("variance: ").append(getVariance()).append(endl); + outBuffer.append("population variance: ").append(getPopulationVariance()).append(endl); + outBuffer.append("second moment: ").append(getSecondMoment()).append(endl); + outBuffer.append("sum of squares: ").append(getSumsq()).append(endl); + outBuffer.append("standard deviation: ").append(getStandardDeviation()) + .append(endl); + outBuffer.append("sum of logs: ").append(getSumOfLogs()).append(endl); + return outBuffer.toString(); + } + + /** + * Resets all statistics and storage + */ + public void clear() { + this.n = 0; + minImpl.clear(); + maxImpl.clear(); + sumImpl.clear(); + sumLogImpl.clear(); + sumsqImpl.clear(); + geoMeanImpl.clear(); + secondMoment.clear(); + if (meanImpl != mean) { + meanImpl.clear(); + } + if (varianceImpl != variance) { + varianceImpl.clear(); + } + } + + /** + * Returns true iff object is a + * SummaryStatistics instance and all statistics have the + * same values as this. + * @param object the object to test equality against. + * @return true if object equals this + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof SummaryStatistics == false) { + return false; + } + SummaryStatistics stat = (SummaryStatistics)object; + return Precision.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) && + Precision.equalsIncludingNaN(stat.getMax(), getMax()) && + Precision.equalsIncludingNaN(stat.getMean(), getMean()) && + Precision.equalsIncludingNaN(stat.getMin(), getMin()) && + Precision.equalsIncludingNaN(stat.getN(), getN()) && + Precision.equalsIncludingNaN(stat.getSum(), getSum()) && + Precision.equalsIncludingNaN(stat.getSumsq(), getSumsq()) && + Precision.equalsIncludingNaN(stat.getVariance(), getVariance()); + } + + /** + * Returns hash code based on values of statistics + * @return hash code + */ + @Override + public int hashCode() { + int result = 31 + MathUtils.hash(getGeometricMean()); + result = result * 31 + MathUtils.hash(getGeometricMean()); + result = result * 31 + MathUtils.hash(getMax()); + result = result * 31 + MathUtils.hash(getMean()); + result = result * 31 + MathUtils.hash(getMin()); + result = result * 31 + MathUtils.hash(getN()); + result = result * 31 + MathUtils.hash(getSum()); + result = result * 31 + MathUtils.hash(getSumsq()); + result = result * 31 + MathUtils.hash(getVariance()); + return result; + } + + // Getters and setters for statistics implementations + /** + * Returns the currently configured Sum implementation + * @return the StorelessUnivariateStatistic implementing the sum + * @since 1.2 + */ + public StorelessUnivariateStatistic getSumImpl() { + return sumImpl; + } + + /** + *

        + * Sets the implementation for the Sum. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param sumImpl the StorelessUnivariateStatistic instance to use for + * computing the Sum + * @throws MathIllegalStateException if data has already been added (i.e if n >0) + * @since 1.2 + */ + public void setSumImpl(StorelessUnivariateStatistic sumImpl) + throws MathIllegalStateException { + checkEmpty(); + this.sumImpl = sumImpl; + } + + /** + * Returns the currently configured sum of squares implementation + * @return the StorelessUnivariateStatistic implementing the sum of squares + * @since 1.2 + */ + public StorelessUnivariateStatistic getSumsqImpl() { + return sumsqImpl; + } + + /** + *

        + * Sets the implementation for the sum of squares. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param sumsqImpl the StorelessUnivariateStatistic instance to use for + * computing the sum of squares + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl) + throws MathIllegalStateException { + checkEmpty(); + this.sumsqImpl = sumsqImpl; + } + + /** + * Returns the currently configured minimum implementation + * @return the StorelessUnivariateStatistic implementing the minimum + * @since 1.2 + */ + public StorelessUnivariateStatistic getMinImpl() { + return minImpl; + } + + /** + *

        + * Sets the implementation for the minimum. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param minImpl the StorelessUnivariateStatistic instance to use for + * computing the minimum + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setMinImpl(StorelessUnivariateStatistic minImpl) + throws MathIllegalStateException { + checkEmpty(); + this.minImpl = minImpl; + } + + /** + * Returns the currently configured maximum implementation + * @return the StorelessUnivariateStatistic implementing the maximum + * @since 1.2 + */ + public StorelessUnivariateStatistic getMaxImpl() { + return maxImpl; + } + + /** + *

        + * Sets the implementation for the maximum. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param maxImpl the StorelessUnivariateStatistic instance to use for + * computing the maximum + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setMaxImpl(StorelessUnivariateStatistic maxImpl) + throws MathIllegalStateException { + checkEmpty(); + this.maxImpl = maxImpl; + } + + /** + * Returns the currently configured sum of logs implementation + * @return the StorelessUnivariateStatistic implementing the log sum + * @since 1.2 + */ + public StorelessUnivariateStatistic getSumLogImpl() { + return sumLogImpl; + } + + /** + *

        + * Sets the implementation for the sum of logs. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param sumLogImpl the StorelessUnivariateStatistic instance to use for + * computing the log sum + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl) + throws MathIllegalStateException { + checkEmpty(); + this.sumLogImpl = sumLogImpl; + geoMean.setSumLogImpl(sumLogImpl); + } + + /** + * Returns the currently configured geometric mean implementation + * @return the StorelessUnivariateStatistic implementing the geometric mean + * @since 1.2 + */ + public StorelessUnivariateStatistic getGeoMeanImpl() { + return geoMeanImpl; + } + + /** + *

        + * Sets the implementation for the geometric mean. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param geoMeanImpl the StorelessUnivariateStatistic instance to use for + * computing the geometric mean + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl) + throws MathIllegalStateException { + checkEmpty(); + this.geoMeanImpl = geoMeanImpl; + } + + /** + * Returns the currently configured mean implementation + * @return the StorelessUnivariateStatistic implementing the mean + * @since 1.2 + */ + public StorelessUnivariateStatistic getMeanImpl() { + return meanImpl; + } + + /** + *

        + * Sets the implementation for the mean. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param meanImpl the StorelessUnivariateStatistic instance to use for + * computing the mean + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setMeanImpl(StorelessUnivariateStatistic meanImpl) + throws MathIllegalStateException { + checkEmpty(); + this.meanImpl = meanImpl; + } + + /** + * Returns the currently configured variance implementation + * @return the StorelessUnivariateStatistic implementing the variance + * @since 1.2 + */ + public StorelessUnivariateStatistic getVarianceImpl() { + return varianceImpl; + } + + /** + *

        + * Sets the implementation for the variance. + *

        + *

        + * This method cannot be activated after data has been added - i.e., + * after {@link #addValue(double) addValue} has been used to add data. + * If it is activated after data has been added, an IllegalStateException + * will be thrown. + *

        + * @param varianceImpl the StorelessUnivariateStatistic instance to use for + * computing the variance + * @throws MathIllegalStateException if data has already been added (i.e if n > 0) + * @since 1.2 + */ + public void setVarianceImpl(StorelessUnivariateStatistic varianceImpl) + throws MathIllegalStateException { + checkEmpty(); + this.varianceImpl = varianceImpl; + } + + /** + * Throws IllegalStateException if n > 0. + * @throws MathIllegalStateException if data has been added + */ + private void checkEmpty() throws MathIllegalStateException { + if (n > 0) { + throw new MathIllegalStateException( + LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, n); + } + } + + /** + * Returns a copy of this SummaryStatistics instance with the same internal state. + * + * @return a copy of this + */ + public SummaryStatistics copy() { + SummaryStatistics result = new SummaryStatistics(); + // No try-catch or advertised exception because arguments are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source SummaryStatistics to copy + * @param dest SummaryStatistics to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SummaryStatistics source, SummaryStatistics dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.maxImpl = source.maxImpl.copy(); + dest.minImpl = source.minImpl.copy(); + dest.sumImpl = source.sumImpl.copy(); + dest.sumLogImpl = source.sumLogImpl.copy(); + dest.sumsqImpl = source.sumsqImpl.copy(); + dest.secondMoment = source.secondMoment.copy(); + dest.n = source.n; + + // Keep commons-math supplied statistics with embedded moments in synch + if (source.getVarianceImpl() instanceof Variance) { + dest.varianceImpl = new Variance(dest.secondMoment); + } else { + dest.varianceImpl = source.varianceImpl.copy(); + } + if (source.meanImpl instanceof Mean) { + dest.meanImpl = new Mean(dest.secondMoment); + } else { + dest.meanImpl = source.meanImpl.copy(); + } + if (source.getGeoMeanImpl() instanceof GeometricMean) { + dest.geoMeanImpl = new GeometricMean((SumOfLogs) dest.sumLogImpl); + } else { + dest.geoMeanImpl = source.geoMeanImpl.copy(); + } + + // Make sure that if stat == statImpl in source, same + // holds in dest; otherwise copy stat + if (source.geoMean == source.geoMeanImpl) { + dest.geoMean = (GeometricMean) dest.geoMeanImpl; + } else { + GeometricMean.copy(source.geoMean, dest.geoMean); + } + if (source.max == source.maxImpl) { + dest.max = (Max) dest.maxImpl; + } else { + Max.copy(source.max, dest.max); + } + if (source.mean == source.meanImpl) { + dest.mean = (Mean) dest.meanImpl; + } else { + Mean.copy(source.mean, dest.mean); + } + if (source.min == source.minImpl) { + dest.min = (Min) dest.minImpl; + } else { + Min.copy(source.min, dest.min); + } + if (source.sum == source.sumImpl) { + dest.sum = (Sum) dest.sumImpl; + } else { + Sum.copy(source.sum, dest.sum); + } + if (source.variance == source.varianceImpl) { + dest.variance = (Variance) dest.varianceImpl; + } else { + Variance.copy(source.variance, dest.variance); + } + if (source.sumLog == source.sumLogImpl) { + dest.sumLog = (SumOfLogs) dest.sumLogImpl; + } else { + SumOfLogs.copy(source.sumLog, dest.sumLog); + } + if (source.sumsq == source.sumsqImpl) { + dest.sumsq = (SumOfSquares) dest.sumsqImpl; + } else { + SumOfSquares.copy(source.sumsq, dest.sumsq); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java new file mode 100644 index 000000000..b3163528f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of + * {@link DescriptiveStatistics} that + * is safe to use in a multithreaded environment. Multiple threads can safely + * operate on a single instance without causing runtime exceptions due to race + * conditions. In effect, this implementation makes modification and access + * methods atomic operations for a single instance. That is to say, as one + * thread is computing a statistic from the instance, no other thread can modify + * the instance nor compute another statistic. + * + * @since 1.2 + */ +public class SynchronizedDescriptiveStatistics extends DescriptiveStatistics { + + /** Serialization UID */ + private static final long serialVersionUID = 1L; + + /** + * Construct an instance with infinite window + */ + public SynchronizedDescriptiveStatistics() { + // no try-catch or advertized IAE because arg is valid + this(INFINITE_WINDOW); + } + + /** + * Construct an instance with finite window + * @param window the finite window size. + * @throws MathIllegalArgumentException if window size is less than 1 but + * not equal to {@link #INFINITE_WINDOW} + */ + public SynchronizedDescriptiveStatistics(int window) throws MathIllegalArgumentException { + super(window); + } + + /** + * A copy constructor. Creates a deep-copy of the {@code original}. + * + * @param original the {@code SynchronizedDescriptiveStatistics} instance to copy + * @throws NullArgumentException if original is null + */ + public SynchronizedDescriptiveStatistics(SynchronizedDescriptiveStatistics original) + throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void addValue(double v) { + super.addValue(v); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double apply(UnivariateStatistic stat) { + return super.apply(stat); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void clear() { + super.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getElement(int index) { + return super.getElement(index); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized long getN() { + return super.getN(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getStandardDeviation() { + return super.getStandardDeviation(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getQuadraticMean() { + return super.getQuadraticMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getValues() { + return super.getValues(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized int getWindowSize() { + return super.getWindowSize(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setWindowSize(int windowSize) throws MathIllegalArgumentException { + super.setWindowSize(windowSize); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized String toString() { + return super.toString(); + } + + /** + * Returns a copy of this SynchronizedDescriptiveStatistics instance with the + * same internal state. + * + * @return a copy of this + */ + @Override + public synchronized SynchronizedDescriptiveStatistics copy() { + SynchronizedDescriptiveStatistics result = + new SynchronizedDescriptiveStatistics(); + // No try-catch or advertised exception because arguments are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + *

        Acquires synchronization lock on source, then dest before copying.

        + * + * @param source SynchronizedDescriptiveStatistics to copy + * @param dest SynchronizedDescriptiveStatistics to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SynchronizedDescriptiveStatistics source, + SynchronizedDescriptiveStatistics dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + synchronized (source) { + synchronized (dest) { + DescriptiveStatistics.copy(source, dest); + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java new file mode 100644 index 000000000..e4eaca3b7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Implementation of + * {@link MultivariateSummaryStatistics} that + * is safe to use in a multithreaded environment. Multiple threads can safely + * operate on a single instance without causing runtime exceptions due to race + * conditions. In effect, this implementation makes modification and access + * methods atomic operations for a single instance. That is to say, as one + * thread is computing a statistic from the instance, no other thread can modify + * the instance nor compute another statistic. + * @since 1.2 + */ +public class SynchronizedMultivariateSummaryStatistics + extends MultivariateSummaryStatistics { + + /** Serialization UID */ + private static final long serialVersionUID = 7099834153347155363L; + + /** + * Construct a SynchronizedMultivariateSummaryStatistics instance + * @param k dimension of the data + * @param isCovarianceBiasCorrected if true, the unbiased sample + * covariance is computed, otherwise the biased population covariance + * is computed + */ + public SynchronizedMultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) { + super(k, isCovarianceBiasCorrected); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void addValue(double[] value) throws DimensionMismatchException { + super.addValue(value); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized int getDimension() { + return super.getDimension(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized long getN() { + return super.getN(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getSum() { + return super.getSum(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getSumSq() { + return super.getSumSq(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getSumLog() { + return super.getSumLog(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getMean() { + return super.getMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getStandardDeviation() { + return super.getStandardDeviation(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized RealMatrix getCovariance() { + return super.getCovariance(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getMax() { + return super.getMax(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getMin() { + return super.getMin(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double[] getGeometricMean() { + return super.getGeometricMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized String toString() { + return super.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void clear() { + super.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean equals(Object object) { + return super.equals(object); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized int hashCode() { + return super.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getSumImpl() { + return super.getSumImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumImpl(StorelessUnivariateStatistic[] sumImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setSumImpl(sumImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getSumsqImpl() { + return super.getSumsqImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setSumsqImpl(sumsqImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getMinImpl() { + return super.getMinImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMinImpl(StorelessUnivariateStatistic[] minImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setMinImpl(minImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getMaxImpl() { + return super.getMaxImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMaxImpl(StorelessUnivariateStatistic[] maxImpl) + throws DimensionMismatchException, MathIllegalStateException{ + super.setMaxImpl(maxImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getSumLogImpl() { + return super.getSumLogImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setSumLogImpl(sumLogImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getGeoMeanImpl() { + return super.getGeoMeanImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setGeoMeanImpl(geoMeanImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic[] getMeanImpl() { + return super.getMeanImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMeanImpl(StorelessUnivariateStatistic[] meanImpl) + throws DimensionMismatchException, MathIllegalStateException { + super.setMeanImpl(meanImpl); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java new file mode 100644 index 000000000..14b59cb84 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of + * {@link SummaryStatistics} that + * is safe to use in a multithreaded environment. Multiple threads can safely + * operate on a single instance without causing runtime exceptions due to race + * conditions. In effect, this implementation makes modification and access + * methods atomic operations for a single instance. That is to say, as one + * thread is computing a statistic from the instance, no other thread can modify + * the instance nor compute another statistic. + * + * @since 1.2 + */ +public class SynchronizedSummaryStatistics extends SummaryStatistics { + + /** Serialization UID */ + private static final long serialVersionUID = 1909861009042253704L; + + /** + * Construct a SynchronizedSummaryStatistics instance + */ + public SynchronizedSummaryStatistics() { + super(); + } + + /** + * A copy constructor. Creates a deep-copy of the {@code original}. + * + * @param original the {@code SynchronizedSummaryStatistics} instance to copy + * @throws NullArgumentException if original is null + */ + public SynchronizedSummaryStatistics(SynchronizedSummaryStatistics original) + throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StatisticalSummary getSummary() { + return super.getSummary(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void addValue(double value) { + super.addValue(value); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized long getN() { + return super.getN(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getSum() { + return super.getSum(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getSumsq() { + return super.getSumsq(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getMean() { + return super.getMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getStandardDeviation() { + return super.getStandardDeviation(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getQuadraticMean() { + return super.getQuadraticMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getVariance() { + return super.getVariance(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getPopulationVariance() { + return super.getPopulationVariance(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getMax() { + return super.getMax(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getMin() { + return super.getMin(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized double getGeometricMean() { + return super.getGeometricMean(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized String toString() { + return super.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void clear() { + super.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean equals(Object object) { + return super.equals(object); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized int hashCode() { + return super.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getSumImpl() { + return super.getSumImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumImpl(StorelessUnivariateStatistic sumImpl) + throws MathIllegalStateException { + super.setSumImpl(sumImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getSumsqImpl() { + return super.getSumsqImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl) + throws MathIllegalStateException { + super.setSumsqImpl(sumsqImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getMinImpl() { + return super.getMinImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMinImpl(StorelessUnivariateStatistic minImpl) + throws MathIllegalStateException { + super.setMinImpl(minImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getMaxImpl() { + return super.getMaxImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMaxImpl(StorelessUnivariateStatistic maxImpl) + throws MathIllegalStateException { + super.setMaxImpl(maxImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getSumLogImpl() { + return super.getSumLogImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl) + throws MathIllegalStateException { + super.setSumLogImpl(sumLogImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getGeoMeanImpl() { + return super.getGeoMeanImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl) + throws MathIllegalStateException { + super.setGeoMeanImpl(geoMeanImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getMeanImpl() { + return super.getMeanImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setMeanImpl(StorelessUnivariateStatistic meanImpl) + throws MathIllegalStateException { + super.setMeanImpl(meanImpl); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized StorelessUnivariateStatistic getVarianceImpl() { + return super.getVarianceImpl(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setVarianceImpl(StorelessUnivariateStatistic varianceImpl) + throws MathIllegalStateException { + super.setVarianceImpl(varianceImpl); + } + + /** + * Returns a copy of this SynchronizedSummaryStatistics instance with the + * same internal state. + * + * @return a copy of this + */ + @Override + public synchronized SynchronizedSummaryStatistics copy() { + SynchronizedSummaryStatistics result = + new SynchronizedSummaryStatistics(); + // No try-catch or advertised exception because arguments are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + *

        Acquires synchronization lock on source, then dest before copying.

        + * + * @param source SynchronizedSummaryStatistics to copy + * @param dest SynchronizedSummaryStatistics to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SynchronizedSummaryStatistics source, + SynchronizedSummaryStatistics dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + synchronized (source) { + synchronized (dest) { + SummaryStatistics.copy(source, dest); + } + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java new file mode 100644 index 000000000..20c0a187e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + + +/** + * Base interface implemented by all statistics. + * + */ +public interface UnivariateStatistic extends MathArrays.Function { + /** + * Returns the result of evaluating the statistic over the input array. + * + * @param values input array + * @return the value of the statistic applied to the input array + * @throws MathIllegalArgumentException if values is null + */ + double evaluate(double[] values) throws MathIllegalArgumentException; + + /** + * Returns the result of evaluating the statistic over the specified entries + * in the input array. + * + * @param values the input array + * @param begin the index of the first element to include + * @param length the number of elements to include + * @return the value of the statistic applied to the included array entries + * @throws MathIllegalArgumentException if values is null or the indices are invalid + */ + double evaluate(double[] values, int begin, int length) throws MathIllegalArgumentException; + + /** + * Returns a copy of the statistic with the same internal state. + * + * @return a copy of the statistic + */ + UnivariateStatistic copy(); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java new file mode 100644 index 000000000..fc6c5c271 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Weighted evaluation for statistics. + * + * @since 2.1 + */ +public interface WeightedEvaluation { + + /** + * Returns the result of evaluating the statistic over the input array, + * using the supplied weights. + * + * @param values input array + * @param weights array of weights + * @return the value of the weighted statistic applied to the input array + * @throws MathIllegalArgumentException if either array is null, lengths + * do not match, weights contain NaN, negative or infinite values, or + * weights does not include at least on positive value + */ + double evaluate(double[] values, double[] weights) throws MathIllegalArgumentException; + + /** + * Returns the result of evaluating the statistic over the specified entries + * in the input array, using corresponding entries in the supplied weights array. + * + * @param values the input array + * @param weights array of weights + * @param begin the index of the first element to include + * @param length the number of elements to include + * @return the value of the weighted statistic applied to the included array entries + * @throws MathIllegalArgumentException if either array is null, lengths + * do not match, indices are invalid, weights contain NaN, negative or + * infinite values, or weights does not include at least on positive value + */ + double evaluate(double[] values, double[] weights, int begin, int length) + throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java new file mode 100644 index 000000000..df10ffc48 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Computes the first moment (arithmetic mean). Uses the definitional formula: + *

        + * mean = sum(x_i) / n

        + *

        + * where n is the number of observations.

        + *

        + * To limit numeric errors, the value of the statistic is computed using the + * following recursive updating algorithm:

        + *

        + *

          + *
        1. Initialize m = the first value
        2. + *
        3. For each additional value, update using
          + * m = m + (new value - m) / (number of observations)
        4. + *

        + *

        + * Returns Double.NaN if the dataset is empty. Note that + * Double.NaN may also be returned if the input includes NaN and / or infinite + * values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +class FirstMoment extends AbstractStorelessUnivariateStatistic + implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 6112755307178490473L; + + + /** Count of values that have been added */ + protected long n; + + /** First moment of values that have been added */ + protected double m1; + + /** + * Deviation of most recently added value from previous first moment. + * Retained to prevent repeated computation in higher order moments. + */ + protected double dev; + + /** + * Deviation of most recently added value from previous first moment, + * normalized by previous sample size. Retained to prevent repeated + * computation in higher order moments + */ + protected double nDev; + + /** + * Create a FirstMoment instance + */ + FirstMoment() { + n = 0; + m1 = Double.NaN; + dev = Double.NaN; + nDev = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code FirstMoment} identical + * to the {@code original} + * + * @param original the {@code FirstMoment} instance to copy + * @throws NullArgumentException if original is null + */ + FirstMoment(FirstMoment original) throws NullArgumentException { + super(); + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (n == 0) { + m1 = 0.0; + } + n++; + double n0 = n; + dev = d - m1; + nDev = dev / n0; + m1 += nDev; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + m1 = Double.NaN; + n = 0; + dev = Double.NaN; + nDev = Double.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return m1; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public FirstMoment copy() { + FirstMoment result = new FirstMoment(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source FirstMoment to copy + * @param dest FirstMoment to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(FirstMoment source, FirstMoment dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.m1 = source.m1; + dest.dev = source.dev; + dest.nDev = source.nDev; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java new file mode 100644 index 000000000..6906bf9e8 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Computes a statistic related to the Fourth Central Moment. Specifically, + * what is computed is the sum of + *

        + * (x_i - xbar) ^ 4,

        + *

        + * where the x_i are the + * sample observations and xbar is the sample mean.

        + *

        + * The following recursive updating formula is used:

        + *

        + * Let

          + *
        • dev = (current obs - previous mean)
        • + *
        • m2 = previous value of {@link SecondMoment}
        • + *
        • m2 = previous value of {@link ThirdMoment}
        • + *
        • n = number of observations (including current obs)
        • + *
        + * Then

        + *

        + * new value = old value - 4 * (dev/n) * m3 + 6 * (dev/n)^2 * m2 +
        + * [n^2 - 3 * (n-1)] * dev^4 * (n-1) / n^3

        + *

        + * Returns Double.NaN if no data values have been added and + * returns 0 if there is just one value in the data set. Note that + * Double.NaN may also be returned if the input includes NaN and / or infinite + * values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +class FourthMoment extends ThirdMoment implements Serializable{ + + /** Serializable version identifier */ + private static final long serialVersionUID = 4763990447117157611L; + + /** fourth moment of values that have been added */ + private double m4; + + /** + * Create a FourthMoment instance + */ + FourthMoment() { + super(); + m4 = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code FourthMoment} identical + * to the {@code original} + * + * @param original the {@code FourthMoment} instance to copy + * @throws NullArgumentException if original is null + */ + FourthMoment(FourthMoment original) throws NullArgumentException { + super(); + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (n < 1) { + m4 = 0.0; + m3 = 0.0; + m2 = 0.0; + m1 = 0.0; + } + + double prevM3 = m3; + double prevM2 = m2; + + super.increment(d); + + double n0 = n; + + m4 = m4 - 4.0 * nDev * prevM3 + 6.0 * nDevSq * prevM2 + + ((n0 * n0) - 3 * (n0 -1)) * (nDevSq * nDevSq * (n0 - 1) * n0); + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return m4; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + m4 = Double.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public FourthMoment copy() { + FourthMoment result = new FourthMoment(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source FourthMoment to copy + * @param dest FourthMoment to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(FourthMoment source, FourthMoment dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + ThirdMoment.copy(source, dest); + dest.m4 = source.m4; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java new file mode 100644 index 000000000..b4090d006 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.SumOfLogs; + +/** + * Returns the + * geometric mean of the available values. + *

        + * Uses a {@link SumOfLogs} instance to compute sum of logs and returns + * exp( 1/n (sum of logs) ). Therefore,

        + *
          + *
        • If any of values are < 0, the result is NaN.
        • + *
        • If all values are non-negative and less than + * Double.POSITIVE_INFINITY, but at least one value is 0, the + * result is 0.
        • + *
        • If both Double.POSITIVE_INFINITY and + * Double.NEGATIVE_INFINITY are among the values, the result is + * NaN.
        • + *

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + * + */ +public class GeometricMean extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -8178734905303459453L; + + /** Wrapped SumOfLogs instance */ + private StorelessUnivariateStatistic sumOfLogs; + + /** + * Create a GeometricMean instance + */ + public GeometricMean() { + sumOfLogs = new SumOfLogs(); + } + + /** + * Copy constructor, creates a new {@code GeometricMean} identical + * to the {@code original} + * + * @param original the {@code GeometricMean} instance to copy + * @throws NullArgumentException if original is null + */ + public GeometricMean(GeometricMean original) throws NullArgumentException { + super(); + copy(original, this); + } + + /** + * Create a GeometricMean instance using the given SumOfLogs instance + * @param sumOfLogs sum of logs instance to use for computation + */ + public GeometricMean(SumOfLogs sumOfLogs) { + this.sumOfLogs = sumOfLogs; + } + + /** + * {@inheritDoc} + */ + @Override + public GeometricMean copy() { + GeometricMean result = new GeometricMean(); + // no try-catch or advertised exception because args guaranteed non-null + copy(this, result); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + sumOfLogs.increment(d); + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + if (sumOfLogs.getN() > 0) { + return FastMath.exp(sumOfLogs.getResult() / sumOfLogs.getN()); + } else { + return Double.NaN; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + sumOfLogs.clear(); + } + + /** + * Returns the geometric mean of the entries in the specified portion + * of the input array. + *

        + * See {@link GeometricMean} for details on the computing algorithm.

        + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @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 MathIllegalArgumentException if the input array is null or the array + * index parameters are not valid + */ + @Override + public double evaluate( + final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + return FastMath.exp( + sumOfLogs.evaluate(values, begin, length) / length); + } + + /** + * {@inheritDoc} + */ + public long getN() { + return sumOfLogs.getN(); + } + + /** + *

        Sets the implementation for the sum of logs.

        + *

        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.

        + * + * @param sumLogImpl the StorelessUnivariateStatistic instance to use + * for computing the log sum + * @throws MathIllegalStateException if data has already been added + * (i.e if n > 0) + */ + public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl) + throws MathIllegalStateException { + checkEmpty(); + this.sumOfLogs = sumLogImpl; + } + + /** + * Returns the currently configured sum of logs implementation + * + * @return the StorelessUnivariateStatistic implementing the log sum + */ + public StorelessUnivariateStatistic getSumLogImpl() { + return sumOfLogs; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source GeometricMean to copy + * @param dest GeometricMean to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(GeometricMean source, GeometricMean dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.sumOfLogs = source.sumOfLogs.copy(); + } + + + /** + * Throws MathIllegalStateException if n > 0. + * @throws MathIllegalStateException if data has been added to this statistic + */ + private void checkEmpty() throws MathIllegalStateException { + if (getN() > 0) { + throw new MathIllegalStateException( + LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, + getN()); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java new file mode 100644 index 000000000..756d4b551 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + + +/** + * Computes the Kurtosis of the available values. + *

        + * We use the following (unbiased) formula to define kurtosis:

        + *

        + * 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)] + *

        + * where n is the number of values, mean is the {@link Mean} and std is the + * {@link StandardDeviation}

        + *

        + * Note that this statistic is undefined for n < 4. Double.Nan + * is returned when there is not sufficient data to compute the statistic. + * Note that Double.NaN may also be returned if the input includes NaN + * and / or infinite values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +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. + *

        + * Statistics based on (constructed from) external moments cannot + * be incremented or cleared.

        + */ + protected boolean incMoment; + + /** + * Construct a Kurtosis + */ + public Kurtosis() { + incMoment = true; + moment = new FourthMoment(); + } + + /** + * Construct a Kurtosis from an external moment + * + * @param m4 external Moment + */ + public Kurtosis(final FourthMoment m4) { + incMoment = false; + this.moment = m4; + } + + /** + * Copy constructor, creates a new {@code Kurtosis} identical + * to the {@code original} + * + * @param original the {@code Kurtosis} instance to copy + * @throws NullArgumentException if original is null + */ + public Kurtosis(Kurtosis original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + *

        Note that when {@link #Kurtosis(FourthMoment)} is used to + * create a Variance, this method does nothing. In that case, the + * FourthMoment should be incremented directly.

        + */ + @Override + public void increment(final double d) { + if (incMoment) { + moment.increment(d); + } + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + double kurtosis = Double.NaN; + if (moment.getN() > 3) { + double variance = moment.m2 / (moment.n - 1); + if (moment.n <= 3 || variance < 10E-20) { + kurtosis = 0.0; + } else { + double n = moment.n; + kurtosis = + (n * (n + 1) * moment.getResult() - + 3 * moment.m2 * moment.m2 * (n - 1)) / + ((n - 1) * (n -2) * (n -3) * variance * variance); + } + } + return kurtosis; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + if (incMoment) { + moment.clear(); + } + } + + /** + * {@inheritDoc} + */ + public long getN() { + return moment.getN(); + } + + /* UnvariateStatistic Approach */ + + /** + * Returns the kurtosis of the entries in the specified portion of the + * input array. + *

        + * See {@link Kurtosis} for details on the computing algorithm.

        + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the kurtosis of the values or Double.NaN if length is less than 4 + * @throws MathIllegalArgumentException if the input array is null or the array + * index parameters are not valid + */ + @Override + public double evaluate(final double[] values,final int begin, final int length) + throws MathIllegalArgumentException { + // Initialize the kurtosis + double kurt = Double.NaN; + + if (test(values, begin, length) && length > 3) { + + // Compute the mean and standard deviation + Variance variance = new Variance(); + variance.incrementAll(values, begin, length); + double mean = variance.moment.m1; + double stdDev = FastMath.sqrt(variance.getResult()); + + // Sum the ^4 of the distance from the mean divided by the + // standard deviation + double accum3 = 0.0; + for (int i = begin; i < begin + length; i++) { + accum3 += FastMath.pow(values[i] - mean, 4.0); + } + accum3 /= FastMath.pow(stdDev, 4.0d); + + // Get N + double n0 = length; + + double coefficientOne = + (n0 * (n0 + 1)) / ((n0 - 1) * (n0 - 2) * (n0 - 3)); + double termTwo = + (3 * FastMath.pow(n0 - 1, 2.0)) / ((n0 - 2) * (n0 - 3)); + + // Calculate kurtosis + kurt = (coefficientOne * accum3) - termTwo; + } + return kurt; + } + + /** + * {@inheritDoc} + */ + @Override + public Kurtosis copy() { + Kurtosis result = new Kurtosis(); + // No try-catch because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Kurtosis to copy + * @param dest Kurtosis to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Kurtosis source, Kurtosis dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.moment = source.moment.copy(); + dest.incMoment = source.incMoment; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Mean.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Mean.java new file mode 100644 index 000000000..4f35b0ac5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Mean.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.WeightedEvaluation; +import com.fr.third.org.apache.commons.math3.stat.descriptive.summary.Sum; + +/** + *

        Computes the arithmetic mean of a set of values. Uses the definitional + * formula:

        + *

        + * mean = sum(x_i) / n + *

        + *

        where n is the number of observations. + *

        + *

        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:

        + *
          + *
        1. Initialize m = the first value
        2. + *
        3. For each additional value, update using
          + * m = m + (new value - m) / (number of observations)
        4. + *
        + *

        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.

        + *

        + * Returns Double.NaN if the dataset is empty. Note that + * Double.NaN may also be returned if the input includes NaN and / or infinite + * values. + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally. + * + */ +public class Mean extends AbstractStorelessUnivariateStatistic + implements Serializable, WeightedEvaluation { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1296043746617791564L; + + /** First moment on which this statistic is based. */ + protected FirstMoment moment; + + /** + * Determines whether or not this statistic can be incremented or cleared. + *

        + * Statistics based on (constructed from) external moments cannot + * be incremented or cleared.

        + */ + protected boolean incMoment; + + /** Constructs a Mean. */ + public Mean() { + incMoment = true; + moment = new FirstMoment(); + } + + /** + * Constructs a Mean with an External Moment. + * + * @param m1 the moment + */ + public Mean(final FirstMoment m1) { + this.moment = m1; + incMoment = false; + } + + /** + * Copy constructor, creates a new {@code Mean} identical + * to the {@code original} + * + * @param original the {@code Mean} instance to copy + * @throws NullArgumentException if original is null + */ + public Mean(Mean original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + *

        Note that when {@link #Mean(FirstMoment)} is used to + * create a Mean, this method does nothing. In that case, the + * FirstMoment should be incremented directly.

        + */ + @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 Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * See {@link Mean} for details on the computing algorithm.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the mean of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values,final int begin, final int length) + throws MathIllegalArgumentException { + if (test(values, begin, length)) { + Sum sum = new Sum(); + double sampleSize = length; + + // Compute initial estimate using definitional formula + double xbar = sum.evaluate(values, begin, length) / sampleSize; + + // Compute correction factor in second pass + double correction = 0; + for (int i = begin; i < begin + length; i++) { + correction += values[i] - xbar; + } + return xbar + (correction/sampleSize); + } + return Double.NaN; + } + + /** + * Returns the weighted arithmetic mean of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws IllegalArgumentException if either array is null.

        + *

        + * 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.

        + *

        + * Throws IllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *
        • the start and length arguments do not determine a valid array
        • + *

        + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the mean of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, + final int begin, final int length) throws MathIllegalArgumentException { + if (test(values, weights, begin, length)) { + Sum sum = new Sum(); + + // Compute initial estimate using definitional formula + double sumw = sum.evaluate(weights,begin,length); + double xbarw = sum.evaluate(values, weights, begin, length) / sumw; + + // Compute correction factor in second pass + double correction = 0; + for (int i = begin; i < begin + length; i++) { + correction += weights[i] * (values[i] - xbarw); + } + return xbarw + (correction/sumw); + } + return Double.NaN; + } + + /** + * Returns the weighted arithmetic mean of the entries in the input array. + *

        + * Throws MathIllegalArgumentException if either array is null.

        + *

        + * 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.

        + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *

        + * + * @param values the input array + * @param weights the weights array + * @return the mean of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights) + throws MathIllegalArgumentException { + return evaluate(values, weights, 0, values.length); + } + + /** + * {@inheritDoc} + */ + @Override + public Mean copy() { + Mean result = new Mean(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Mean to copy + * @param dest Mean to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Mean source, Mean dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.incMoment = source.incMoment; + dest.moment = source.moment.copy(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java new file mode 100644 index 000000000..7fc772d2a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Computes a statistic related to the Second Central Moment. Specifically, + * what is computed is the sum of squared deviations from the sample mean. + *

        + * The following recursive updating formula is used:

        + *

        + * Let

          + *
        • dev = (current obs - previous mean)
        • + *
        • n = number of observations (including current obs)
        • + *
        + * Then

        + *

        + * new value = old value + dev^2 * (n -1) / n.

        + *

        + * Returns Double.NaN if no data values have been added and + * returns 0 if there is just one value in the data set. + * Note that Double.NaN may also be returned if the input includes NaN + * and / or infinite values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class SecondMoment extends FirstMoment implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 3942403127395076445L; + + /** second moment of values that have been added */ + protected double m2; + + /** + * Create a SecondMoment instance + */ + public SecondMoment() { + super(); + m2 = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code SecondMoment} identical + * to the {@code original} + * + * @param original the {@code SecondMoment} instance to copy + * @throws NullArgumentException if original is null + */ + public SecondMoment(SecondMoment original) + throws NullArgumentException { + super(original); + this.m2 = original.m2; + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (n < 1) { + m1 = m2 = 0.0; + } + super.increment(d); + m2 += ((double) n - 1) * dev * nDev; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + m2 = Double.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return m2; + } + + /** + * {@inheritDoc} + */ + @Override + public SecondMoment copy() { + SecondMoment result = new SecondMoment(); + // no try-catch or advertised NAE because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source SecondMoment to copy + * @param dest SecondMoment to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SecondMoment source, SecondMoment dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + FirstMoment.copy(source, dest); + dest.m2 = source.m2; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java new file mode 100644 index 000000000..bceda8cbc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java @@ -0,0 +1,369 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractUnivariateStatistic; + +/** + *

        Computes the semivariance of a set of values with respect to a given cutoff value. + * We define the downside semivariance of a set of values x + * against the cutoff value cutoff to be
        + * Σ (x[i] - target)2 / df
        + * where the sum is taken over all i such that x[i] < cutoff + * and df is the length of x (non-bias-corrected) or + * one less than this number (bias corrected). The upside semivariance + * is defined similarly, with the sum taken over values of x that + * exceed the cutoff value.

        + * + *

        The cutoff value defaults to the mean, bias correction defaults to true + * 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)}.

        + * + *

        If the input array is null, evaluate methods throw + * IllegalArgumentException. If the array has length 1, 0 + * is returned, regardless of the value of the cutoff. + * + *

        Note that this class is not intended to be threadsafe. 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.

        + * + * @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) biasCorrected + * property and default (Downside) varianceDirection property. + */ + public SemiVariance() { + } + + /** + * Constructs a SemiVariance with the specified biasCorrected + * property and default (Downside) varianceDirection 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 Direction property + * and default (true) biasCorrected 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 isBiasCorrected + * property and the specified Direction property. + * + * @param corrected setting for bias correction - true means + * bias will be corrected and is equivalent to using the argumentless + * constructor + * + * @param direction setting for the direction of the SemiVariance + * to calculate + */ + public SemiVariance(final boolean corrected, final Direction direction) { + this.biasCorrected = corrected; + this.varianceDirection = direction; + } + + + /** + * Copy constructor, creates a new {@code SemiVariance} identical + * to the {@code original} + * + * @param original the {@code SemiVariance} instance to copy + * @throws NullArgumentException if original is null + */ + public SemiVariance(final SemiVariance original) throws NullArgumentException { + copy(original, this); + } + + + /** + * {@inheritDoc} + */ + @Override + public SemiVariance copy() { + SemiVariance result = new SemiVariance(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source SemiVariance to copy + * @param dest SemiVariance to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(final SemiVariance source, SemiVariance dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.biasCorrected = source.biasCorrected; + dest.varianceDirection = source.varianceDirection; + } + + /** + *

        Returns the {@link SemiVariance} of the designated values against the mean, using + * instance properties varianceDirection and biasCorrection.

        + * + *

        Returns NaN if the array is empty and throws + * IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param start index of the first array element to include + * @param length the number of elements to include + * @return the SemiVariance + * @throws MathIllegalArgumentException if the parameters are not valid + * + */ + @Override + public double evaluate(final double[] values, final int start, final int length) + throws MathIllegalArgumentException { + double m = (new Mean()).evaluate(values, start, length); + return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length); + } + + + /** + * This method calculates {@link SemiVariance} for the entire array against the mean, using + * the current value of the biasCorrection instance property. + * + * @param values the input array + * @param direction the {@link Direction} of the semivariance + * @return the SemiVariance + * @throws MathIllegalArgumentException if values is null + * + */ + public double evaluate(final double[] values, Direction direction) + throws MathIllegalArgumentException { + double m = (new Mean()).evaluate(values); + return evaluate (values, m, direction, biasCorrected, 0, values.length); + } + + /** + *

        Returns the {@link SemiVariance} of the designated values against the cutoff, using + * instance properties variancDirection and biasCorrection.

        + * + *

        Returns NaN if the array is empty and throws + * MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param cutoff the reference point + * @return the SemiVariance + * @throws MathIllegalArgumentException if values is null + */ + public double evaluate(final double[] values, final double cutoff) + throws MathIllegalArgumentException { + return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length); + } + + /** + *

        Returns the {@link SemiVariance} of the designated values against the cutoff in the + * given direction, using the current value of the biasCorrection instance property.

        + * + *

        Returns NaN if the array is empty and throws + * MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param cutoff the reference point + * @param direction the {@link Direction} of the semivariance + * @return the SemiVariance + * @throws MathIllegalArgumentException if values is null + */ + public double evaluate(final double[] values, final double cutoff, final Direction direction) + throws MathIllegalArgumentException { + return evaluate(values, cutoff, direction, biasCorrected, 0, values.length); + } + + + /** + *

        Returns the {@link SemiVariance} of the designated values against the cutoff + * in the given direction with the provided bias correction.

        + * + *

        Returns NaN if the array is empty and throws + * IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param cutoff the reference point + * @param direction the {@link Direction} of the semivariance + * @param corrected the BiasCorrection flag + * @param start index of the first array element to include + * @param length the number of elements to include + * @return the SemiVariance + * @throws MathIllegalArgumentException if the parameters are not valid + * + */ + public double evaluate (final double[] values, final double cutoff, final Direction direction, + final boolean corrected, final int start, final int length) throws MathIllegalArgumentException { + + test(values, start, length); + if (values.length == 0) { + return Double.NaN; + } else { + if (values.length == 1) { + return 0.0; + } else { + final boolean booleanDirection = direction.getDirection(); + + double dev = 0.0; + double sumsq = 0.0; + for (int i = start; i < length; i++) { + if ((values[i] > cutoff) == booleanDirection) { + dev = values[i] - cutoff; + sumsq += dev * dev; + } + } + + if (corrected) { + return sumsq / (length - 1.0); + } else { + return sumsq / length; + } + } + } + } + + /** + * Returns true iff biasCorrected property is set to true. + * + * @return the value of biasCorrected. + */ + public boolean isBiasCorrected() { + return biasCorrected; + } + + /** + * Sets the biasCorrected property. + * + * @param biasCorrected new biasCorrected property value + */ + public void setBiasCorrected(boolean biasCorrected) { + this.biasCorrected = biasCorrected; + } + + /** + * Returns the varianceDirection property. + * + * @return the varianceDirection + */ + public Direction getVarianceDirection () { + return varianceDirection; + } + + /** + * Sets the variance direction + * + * @param varianceDirection the direction of the semivariance + */ + public void setVarianceDirection(Direction varianceDirection) { + this.varianceDirection = varianceDirection; + } + + /** + * The direction of the semivariance - either upside or downside. The direction + * is represented by boolean, with true corresponding to UPSIDE semivariance. + */ + public enum Direction { + /** + * The UPSIDE Direction is used to specify that the observations above the + * cutoff point will be used to calculate SemiVariance + */ + UPSIDE (true), + + /** + * The DOWNSIDE Direction is used to specify that the observations below + * the cutoff point will be used to calculate SemiVariance + */ + DOWNSIDE (false); + + /** + * boolean value UPSIDE <-> true + */ + private boolean direction; + + /** + * Create a Direction with the given value. + * + * @param b boolean value representing the Direction. True corresponds to UPSIDE. + */ + Direction (boolean b) { + direction = b; + } + + /** + * Returns the value of this Direction. True corresponds to UPSIDE. + * + * @return true if direction is UPSIDE; false otherwise + */ + boolean getDirection () { + return direction; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Skewness.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Skewness.java new file mode 100644 index 000000000..b7d73b4cf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Skewness.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Computes the skewness of the available values. + *

        + * We use the following (unbiased) formula to define skewness:

        + *

        + * skewness = [n / (n -1) (n - 2)] sum[(x_i - mean)^3] / std^3

        + *

        + * where n is the number of values, mean is the {@link Mean} and std is the + * {@link StandardDeviation}

        + *

        + * Note that this statistic is undefined for n < 3. Double.Nan + * is returned when there is not sufficient data to compute the statistic. + * Double.NaN may also be returned if the input includes NaN and / or + * infinite values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +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. + *

        + * Statistics based on (constructed from) external moments cannot + * be incremented or cleared.

        + */ + protected boolean incMoment; + + /** + * Constructs a Skewness + */ + public Skewness() { + incMoment = true; + moment = new ThirdMoment(); + } + + /** + * Constructs a Skewness with an external moment + * @param m3 external moment + */ + public Skewness(final ThirdMoment m3) { + incMoment = false; + this.moment = m3; + } + + /** + * Copy constructor, creates a new {@code Skewness} identical + * to the {@code original} + * + * @param original the {@code Skewness} instance to copy + * @throws NullArgumentException if original is null + */ + public Skewness(Skewness original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + *

        Note that when {@link #Skewness(ThirdMoment)} is used to + * create a Skewness, this method does nothing. In that case, the + * ThirdMoment should be incremented directly.

        + */ + @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. + *

        + * See {@link Skewness} for the definition used in the computation.

        + * + * @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. + *

        + * See {@link Skewness} for the definition used in the computation.

        + *

        + * Throws IllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin the index of the first array element to include + * @param length the number of elements to include + * @return the skewness of the values or Double.NaN if length is less than + * 3 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values,final int begin, + final int length) throws MathIllegalArgumentException { + + // Initialize the skewness + double skew = Double.NaN; + + if (test(values, begin, length) && length > 2 ){ + Mean mean = new Mean(); + // Get the mean and the standard deviation + double m = mean.evaluate(values, begin, length); + + // Calc the std, this is implemented here instead + // of using the standardDeviation method eliminate + // a duplicate pass to get the mean + double accum = 0.0; + double accum2 = 0.0; + for (int i = begin; i < begin + length; i++) { + final double d = values[i] - m; + accum += d * d; + accum2 += d; + } + final double variance = (accum - (accum2 * accum2 / length)) / (length - 1); + + double accum3 = 0.0; + for (int i = begin; i < begin + length; i++) { + final double d = values[i] - m; + accum3 += d * d * d; + } + accum3 /= variance * FastMath.sqrt(variance); + + // Get N + double n0 = length; + + // Calculate skewness + skew = (n0 / ((n0 - 1) * (n0 - 2))) * accum3; + } + return skew; + } + + /** + * {@inheritDoc} + */ + @Override + public Skewness copy() { + Skewness result = new Skewness(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Skewness to copy + * @param dest Skewness to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Skewness source, Skewness dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.moment = new ThirdMoment(source.moment.copy()); + dest.incMoment = source.incMoment; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java new file mode 100644 index 000000000..18f1ddb29 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Computes the sample standard deviation. The standard deviation + * is the positive square root of the variance. This implementation wraps a + * {@link Variance} instance. The isBiasCorrected 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. + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +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 isBiasCorrected property to true. + */ + public StandardDeviation() { + variance = new Variance(); + } + + /** + * Constructs a StandardDeviation from an external second moment. + * + * @param m2 the external moment + */ + public StandardDeviation(final SecondMoment m2) { + variance = new Variance(m2); + } + + /** + * Copy constructor, creates a new {@code StandardDeviation} identical + * to the {@code original} + * + * @param original the {@code StandardDeviation} instance to copy + * @throws NullArgumentException if original is null + */ + public StandardDeviation(StandardDeviation original) throws NullArgumentException { + copy(original, this); + } + + /** + * Contructs a StandardDeviation with the specified value for the + * isBiasCorrected property. If this property is set to + * true, 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 + * isBiasCorrected property and the supplied external moment. + * If isBiasCorrected is set to true, 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 + * Double.NaN if the array is empty. + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @return the standard deviation of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null + */ + @Override + public double evaluate(final double[] values) throws MathIllegalArgumentException { + return FastMath.sqrt(variance.evaluate(values)); + } + + /** + * Returns the Standard Deviation of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the standard deviation of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + return FastMath.sqrt(variance.evaluate(values, begin, length)); + } + + /** + * Returns the Standard Deviation of the entries in the specified portion of + * the input array, using the precomputed mean value. Returns + * Double.NaN if the designated subarray is empty. + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * 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.

        + *

        + * Throws IllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the standard deviation of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public double evaluate(final double[] values, final double mean, + final int begin, final int length) throws MathIllegalArgumentException { + return FastMath.sqrt(variance.evaluate(values, mean, begin, length)); + } + + /** + * Returns the Standard Deviation of the entries in the input array, using + * the precomputed mean value. Returns + * Double.NaN if the designated subarray is empty. + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * 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.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @return the standard deviation of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null + */ + public double evaluate(final double[] values, final double mean) + throws MathIllegalArgumentException { + return FastMath.sqrt(variance.evaluate(values, mean)); + } + + /** + * @return Returns the isBiasCorrected. + */ + public boolean isBiasCorrected() { + return variance.isBiasCorrected(); + } + + /** + * @param isBiasCorrected The isBiasCorrected to set. + */ + public void setBiasCorrected(boolean isBiasCorrected) { + variance.setBiasCorrected(isBiasCorrected); + } + + /** + * {@inheritDoc} + */ + @Override + public StandardDeviation copy() { + StandardDeviation result = new StandardDeviation(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source StandardDeviation to copy + * @param dest StandardDeviation to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(StandardDeviation source, StandardDeviation dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.variance = source.variance.copy(); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java new file mode 100644 index 000000000..e8eab75a7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + + +/** + * Computes a statistic related to the Third Central Moment. Specifically, + * what is computed is the sum of cubed deviations from the sample mean. + *

        + * The following recursive updating formula is used:

        + *

        + * Let

          + *
        • dev = (current obs - previous mean)
        • + *
        • m2 = previous value of {@link SecondMoment}
        • + *
        • n = number of observations (including current obs)
        • + *
        + * Then

        + *

        + * new value = old value - 3 * (dev/n) * m2 + (n-1) * (n -2) * (dev^3/n^2)

        + *

        + * Returns Double.NaN if no data values have been added and + * returns 0 if there is just one value in the data set. + * Note that Double.NaN may also be returned if the input includes NaN + * and / or infinite values.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +class ThirdMoment extends SecondMoment implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -7818711964045118679L; + + /** third moment of values that have been added */ + protected double m3; + + /** + * Square of deviation of most recently added value from previous first + * moment, normalized by previous sample size. Retained to prevent + * repeated computation in higher order moments. nDevSq = nDev * nDev. + */ + protected double nDevSq; + + /** + * Create a FourthMoment instance + */ + ThirdMoment() { + super(); + m3 = Double.NaN; + nDevSq = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code ThirdMoment} identical + * to the {@code original} + * + * @param original the {@code ThirdMoment} instance to copy + * @throws NullArgumentException if orginal is null + */ + ThirdMoment(ThirdMoment original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (n < 1) { + m3 = m2 = m1 = 0.0; + } + + double prevM2 = m2; + super.increment(d); + nDevSq = nDev * nDev; + double n0 = n; + m3 = m3 - 3.0 * nDev * prevM2 + (n0 - 1) * (n0 - 2) * nDevSq * dev; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return m3; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + m3 = Double.NaN; + nDevSq = Double.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public ThirdMoment copy() { + ThirdMoment result = new ThirdMoment(); + // No try-catch or advertised exception because args are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source ThirdMoment to copy + * @param dest ThirdMoment to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(ThirdMoment source, ThirdMoment dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + SecondMoment.copy(source, dest); + dest.m3 = source.m3; + dest.nDevSq = source.nDevSq; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Variance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Variance.java new file mode 100644 index 000000000..ee73d1643 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/Variance.java @@ -0,0 +1,627 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.WeightedEvaluation; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Computes the variance of the available values. By default, the unbiased + * "sample variance" definitional formula is used: + *

        + * variance = sum((x_i - mean)^2) / (n - 1)

        + *

        + * where mean is the {@link Mean} and n is the number + * of sample observations.

        + *

        + * The definitional formula does not have good numerical properties, so + * this implementation does not compute the statistic using the definitional + * formula.

          + *
        • The getResult method computes the variance using + * updating formulas based on West's algorithm, as described in + * Chan, T. F. and + * J. G. Lewis 1979, Communications of the ACM, + * vol. 22 no. 9, pp. 526-531.
        • + *
        • The evaluate 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, Algorithms for Computing the Sample Variance, + * American Statistician, vol. 37, no. 3 (1983) pp. 242-247.
        + * Note that adding values using increment or + * incrementAll and then executing getResult will + * sometimes give a different, less accurate, result than executing + * evaluate with the full array of values. The former approach + * should only be used when the full array of values is not available.

        + *

        + * The "population variance" ( sum((x_i - mean)^2) / n ) can also + * be computed using this statistic. The isBiasCorrected + * property determines whether the "population" or "sample" value is + * returned by the evaluate and getResult methods. + * To compute population variances, set this property to false. + *

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Variance extends AbstractStorelessUnivariateStatistic implements Serializable, WeightedEvaluation { + + /** Serializable version identifier */ + private static final long serialVersionUID = -9111962718267217978L; + + /** SecondMoment is used in incremental calculation of Variance*/ + protected SecondMoment moment = null; + + /** + * Whether or not {@link #increment(double)} should increment + * the internal second moment. When a Variance is constructed with an + * external SecondMoment as a constructor parameter, this property is + * set to false and increments must be applied to the second moment + * directly. + */ + protected boolean incMoment = true; + + /** + * Whether or not bias correction is applied when computing the + * value of the statistic. True means that bias is corrected. See + * {@link Variance} for details on the formula. + */ + private boolean isBiasCorrected = true; + + /** + * Constructs a Variance with default (true) isBiasCorrected + * property. + */ + public Variance() { + moment = new SecondMoment(); + } + + /** + * Constructs a Variance based on an external second moment. + * When this constructor is used, the statistic may only be + * incremented via the moment, i.e., {@link #increment(double)} + * does nothing; whereas {@code m2.increment(value)} increments + * both {@code m2} and the Variance instance constructed from it. + * + * @param m2 the SecondMoment (Third or Fourth moments work + * here as well.) + */ + public Variance(final SecondMoment m2) { + incMoment = false; + this.moment = m2; + } + + /** + * Constructs a Variance with the specified isBiasCorrected + * 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 isBiasCorrected + * property and the supplied external second moment. + * + * @param isBiasCorrected setting for bias correction - true means + * bias will be corrected + * @param m2 the SecondMoment (Third or Fourth moments work + * here as well.) + */ + public Variance(boolean isBiasCorrected, SecondMoment m2) { + incMoment = false; + this.moment = m2; + this.isBiasCorrected = isBiasCorrected; + } + + /** + * Copy constructor, creates a new {@code Variance} identical + * to the {@code original} + * + * @param original the {@code Variance} instance to copy + * @throws NullArgumentException if original is null + */ + public Variance(Variance original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + *

        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 + * evaluate leverages the fact that is has the full + * list of values together to execute a two-pass algorithm. + * See {@link Variance}.

        + * + *

        Note also that when {@link #Variance(SecondMoment)} is used to + * create a Variance, this method does nothing. In that case, the + * SecondMoment should be incremented directly.

        + */ + @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 + * Double.NaN if the array is empty. + *

        + * See {@link Variance} for details on the computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null + */ + @Override + public double evaluate(final double[] values) throws MathIllegalArgumentException { + if (values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + return evaluate(values, 0, values.length); + } + + /** + * Returns the variance of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. Note that Double.NaN may also be returned if the input + * includes NaN and / or infinite values. + *

        + * See {@link Variance} for details on the computing algorithm.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Does not change the internal state of the statistic.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + + double var = Double.NaN; + + if (test(values, begin, length)) { + clear(); + if (length == 1) { + var = 0.0; + } else if (length > 1) { + Mean mean = new Mean(); + double m = mean.evaluate(values, begin, length); + var = evaluate(values, m, begin, length); + } + } + return var; + } + + /** + *

        Returns the weighted variance of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty.

        + *

        + * Uses the formula

        +     *   Σ(weights[i]*(values[i] - weightedMean)2)/(Σ(weights[i]) - 1)
        +     * 
        + * where weightedMean is the weighted mean

        + *

        + * 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

        +     *   evaluate(values, MathArrays.normalizeArray(weights, values.length)); 
        +     * 
        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws IllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *
        • the start and length arguments do not determine a valid array
        • + *

        + *

        + * Does not change the internal state of the statistic.

        + *

        + * Throws MathIllegalArgumentException if either array is null.

        + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the weighted variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, + final int begin, final int length) throws MathIllegalArgumentException { + + double var = Double.NaN; + + if (test(values, weights,begin, length)) { + clear(); + if (length == 1) { + var = 0.0; + } else if (length > 1) { + Mean mean = new Mean(); + double m = mean.evaluate(values, weights, begin, length); + var = evaluate(values, weights, m, begin, length); + } + } + return var; + } + + /** + *

        + * Returns the weighted variance of the entries in the the input array.

        + *

        + * Uses the formula

        +     *   Σ(weights[i]*(values[i] - weightedMean)2)/(Σ(weights[i]) - 1)
        +     * 
        + * where weightedMean is the weighted mean

        + *

        + * 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

        +     *   evaluate(values, MathArrays.normalizeArray(weights, values.length)); 
        +     * 
        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *

        + *

        + * Does not change the internal state of the statistic.

        + *

        + * Throws MathIllegalArgumentException if either array is null.

        + * + * @param values the input array + * @param weights the weights array + * @return the weighted variance of the values + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights) + throws MathIllegalArgumentException { + return evaluate(values, weights, 0, values.length); + } + + /** + * Returns the variance of the entries in the specified portion of + * the input array, using the precomputed mean value. Returns + * Double.NaN if the designated subarray is empty. + *

        + * See {@link Variance} for details on the computing algorithm.

        + *

        + * 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.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + public double evaluate(final double[] values, final double mean, + final int begin, final int length) throws MathIllegalArgumentException { + + double var = Double.NaN; + + if (test(values, begin, length)) { + if (length == 1) { + var = 0.0; + } else if (length > 1) { + double accum = 0.0; + double dev = 0.0; + double accum2 = 0.0; + for (int i = begin; i < begin + length; i++) { + dev = values[i] - mean; + accum += dev * dev; + accum2 += dev; + } + double len = length; + if (isBiasCorrected) { + var = (accum - (accum2 * accum2 / len)) / (len - 1.0); + } else { + var = (accum - (accum2 * accum2 / len)) / len; + } + } + } + return var; + } + + /** + * Returns the variance of the entries in the input array, using the + * precomputed mean value. Returns Double.NaN if the array + * is empty. + *

        + * See {@link Variance} for details on the computing algorithm.

        + *

        + * If isBiasCorrected is true 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 isBiasCorrected to false before + * invoking this method.

        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param mean the precomputed mean value + * @return the variance of the values or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if the array is null + */ + public double evaluate(final double[] values, final double mean) throws MathIllegalArgumentException { + return evaluate(values, mean, 0, values.length); + } + + /** + * Returns the weighted variance of the entries in the specified portion of + * the input array, using the precomputed weighted mean value. Returns + * Double.NaN if the designated subarray is empty. + *

        + * Uses the formula

        +     *   Σ(weights[i]*(values[i] - mean)2)/(Σ(weights[i]) - 1)
        +     * 

        + *

        + * 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.

        + *

        + * 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

        +     *   evaluate(values, MathArrays.normalizeArray(weights, values.length), mean); 
        +     * 
        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *
        • the start and length arguments do not determine a valid array
        • + *

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param weights the weights array + * @param mean the precomputed weighted mean value + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, + final double mean, final int begin, final int length) + throws MathIllegalArgumentException { + + double var = Double.NaN; + + if (test(values, weights, begin, length)) { + if (length == 1) { + var = 0.0; + } else if (length > 1) { + double accum = 0.0; + double dev = 0.0; + double accum2 = 0.0; + for (int i = begin; i < begin + length; i++) { + dev = values[i] - mean; + accum += weights[i] * (dev * dev); + accum2 += weights[i] * dev; + } + + double sumWts = 0; + for (int i = begin; i < begin + length; i++) { + sumWts += weights[i]; + } + + if (isBiasCorrected) { + var = (accum - (accum2 * accum2 / sumWts)) / (sumWts - 1.0); + } else { + var = (accum - (accum2 * accum2 / sumWts)) / sumWts; + } + } + } + return var; + } + + /** + *

        Returns the weighted variance of the values in the input array, using + * the precomputed weighted mean value.

        + *

        + * Uses the formula

        +     *   Σ(weights[i]*(values[i] - mean)2)/(Σ(weights[i]) - 1)
        +     * 

        + *

        + * 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.

        + *

        + * 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

        +     *   evaluate(values, MathArrays.normalizeArray(weights, values.length), mean); 
        +     * 
        + *

        + * Returns 0 for a single-value (i.e. length = 1) sample.

        + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *

        + *

        + * Does not change the internal state of the statistic.

        + * + * @param values the input array + * @param weights the weights array + * @param mean the precomputed weighted mean value + * @return the variance of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, final double mean) + throws MathIllegalArgumentException { + return evaluate(values, weights, mean, 0, values.length); + } + + /** + * @return Returns the isBiasCorrected. + */ + public boolean isBiasCorrected() { + return isBiasCorrected; + } + + /** + * @param biasCorrected The isBiasCorrected to set. + */ + public void setBiasCorrected(boolean biasCorrected) { + this.isBiasCorrected = biasCorrected; + } + + /** + * {@inheritDoc} + */ + @Override + public Variance copy() { + Variance result = new Variance(); + // No try-catch or advertised exception because parameters are guaranteed non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Variance to copy + * @param dest Variance to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Variance source, Variance dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.moment = source.moment.copy(); + dest.isBiasCorrected = source.isBiasCorrected; + dest.incMoment = source.incMoment; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java new file mode 100644 index 000000000..820cc6a17 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; + +/** + * Returns the covariance matrix of the available vectors. + * @since 1.2 + */ +public class VectorialCovariance implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 4118372414238930270L; + + /** Sums for each component. */ + private final double[] sums; + + /** Sums of products for each component. */ + private final double[] productsSums; + + /** Indicator for bias correction. */ + private final boolean isBiasCorrected; + + /** Number of vectors in the sample. */ + private long n; + + /** Constructs a VectorialCovariance. + * @param dimension vectors dimension + * @param isBiasCorrected if true, computed the unbiased sample covariance, + * otherwise computes the biased population covariance + */ + public VectorialCovariance(int dimension, boolean isBiasCorrected) { + sums = new double[dimension]; + productsSums = new double[dimension * (dimension + 1) / 2]; + n = 0; + this.isBiasCorrected = isBiasCorrected; + } + + /** + * Add a new vector to the sample. + * @param v vector to add + * @throws DimensionMismatchException if the vector does not have the right dimension + */ + public void increment(double[] v) throws DimensionMismatchException { + if (v.length != sums.length) { + throw new DimensionMismatchException(v.length, sums.length); + } + int k = 0; + for (int i = 0; i < v.length; ++i) { + sums[i] += v[i]; + for (int j = 0; j <= i; ++j) { + productsSums[k++] += v[i] * v[j]; + } + } + n++; + } + + /** + * Get the covariance matrix. + * @return covariance matrix + */ + public RealMatrix getResult() { + + int dimension = sums.length; + RealMatrix result = MatrixUtils.createRealMatrix(dimension, dimension); + + if (n > 1) { + double c = 1.0 / (n * (isBiasCorrected ? (n - 1) : n)); + int k = 0; + for (int i = 0; i < dimension; ++i) { + for (int j = 0; j <= i; ++j) { + double e = c * (n * productsSums[k++] - sums[i] * sums[j]); + result.setEntry(i, j, e); + result.setEntry(j, i, e); + } + } + } + + return result; + + } + + /** + * Get the number of vectors in the sample. + * @return number of vectors in the sample + */ + public long getN() { + return n; + } + + /** + * Clears the internal state of the Statistic + */ + public void clear() { + n = 0; + Arrays.fill(sums, 0.0); + Arrays.fill(productsSums, 0.0); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (isBiasCorrected ? 1231 : 1237); + result = prime * result + (int) (n ^ (n >>> 32)); + result = prime * result + Arrays.hashCode(productsSums); + result = prime * result + Arrays.hashCode(sums); + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof VectorialCovariance)) { + return false; + } + VectorialCovariance other = (VectorialCovariance) obj; + if (isBiasCorrected != other.isBiasCorrected) { + return false; + } + if (n != other.n) { + return false; + } + if (!Arrays.equals(productsSums, other.productsSums)) { + return false; + } + if (!Arrays.equals(sums, other.sums)) { + return false; + } + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java new file mode 100644 index 000000000..0729b4e52 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** + * Returns the arithmetic mean of the available vectors. + * @since 1.2 + */ +public class VectorialMean implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 8223009086481006892L; + + /** Means for each component. */ + private final Mean[] means; + + /** Constructs a VectorialMean. + * @param dimension vectors dimension + */ + public VectorialMean(int dimension) { + means = new Mean[dimension]; + for (int i = 0; i < dimension; ++i) { + means[i] = new Mean(); + } + } + + /** + * Add a new vector to the sample. + * @param v vector to add + * @throws DimensionMismatchException if the vector does not have the right dimension + */ + public void increment(double[] v) throws DimensionMismatchException { + if (v.length != means.length) { + throw new DimensionMismatchException(v.length, means.length); + } + for (int i = 0; i < v.length; ++i) { + means[i].increment(v[i]); + } + } + + /** + * Get the mean vector. + * @return mean vector + */ + public double[] getResult() { + double[] result = new double[means.length]; + for (int i = 0; i < result.length; ++i) { + result[i] = means[i].getResult(); + } + return result; + } + + /** + * Get the number of vectors in the sample. + * @return number of vectors in the sample + */ + public long getN() { + return (means.length == 0) ? 0 : means[0].getN(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(means); + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof VectorialMean)) { + return false; + } + VectorialMean other = (VectorialMean) obj; + if (!Arrays.equals(means, other.means)) { + return false; + } + return true; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/package-info.java new file mode 100644 index 000000000..de8d578c6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/moment/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Summary statistics based on moments. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.moment; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/package-info.java new file mode 100644 index 000000000..7be618798 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/package-info.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Generic univariate summary statistic objects. + * + *

        UnivariateStatistic API Usage Examples:

        + * + *

        UnivariateStatistic:

        + * /∗ evaluation approach ∗/
        + * double[] values = new double[] { 1, 2, 3, 4, 5 };
        + * UnivariateStatistic stat = new Mean();
        + * out.println("mean = " + stat.evaluate(values));
        + *
        + * + *

        StorelessUnivariateStatistic:

        + * /∗ incremental approach ∗/
        + * double[] values = new double[] { 1, 2, 3, 4, 5 };
        + * StorelessUnivariateStatistic stat = new Mean();
        + * out.println("mean before adding a value is NaN = " + stat.getResult());
        + * for (int i = 0; i < values.length; i++) {
        + *     stat.increment(values[i]);
        + *     out.println("current mean = " + stat2.getResult());
        + * }
        + * stat.clear();
        + * out.println("mean after clear is NaN = " + stat.getResult()); + *
        + * + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Max.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Max.java new file mode 100644 index 000000000..521a23800 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Max.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Returns the maximum of the available values. + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.POSITIVE_INFINITY, + * the result is Double.POSITIVE_INFINITY.
        • + *

        +*

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Max extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -5593383832225844641L; + + /** Number of values that have been added */ + private long n; + + /** Current value of the statistic */ + private double value; + + /** + * Create a Max instance + */ + public Max() { + n = 0; + value = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code Max} identical + * to the {@code original} + * + * @param original the {@code Max} instance to copy + * @throws NullArgumentException if original is null + */ + public Max(Max original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (d > value || Double.isNaN(value)) { + value = d; + } + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = Double.NaN; + n = 0; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * Returns the maximum of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null or + * the array index parameters are not valid.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.POSITIVE_INFINITY, + * the result is Double.POSITIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the maximum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + double max = Double.NaN; + if (test(values, begin, length)) { + max = values[begin]; + for (int i = begin; i < begin + length; i++) { + if (!Double.isNaN(values[i])) { + max = (max > values[i]) ? max : values[i]; + } + } + } + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public Max copy() { + Max result = new Max(); + // No try-catch or advertised exception because args are non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Max to copy + * @param dest Max to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Max source, Max dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Median.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Median.java new file mode 100644 index 000000000..36f0eeac2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Median.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaNStrategy; +import com.fr.third.org.apache.commons.math3.util.KthSelector; + + +/** + * Returns the median of the available values. This is the same as the 50th percentile. + * See {@link Percentile} for a description of the algorithm used. + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Median extends Percentile implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -3961477041290915687L; + + /** Fixed quantile. */ + private static final double FIXED_QUANTILE_50 = 50.0; + + /** + * Default constructor. + */ + public Median() { + // No try-catch or advertised exception - arg is valid + super(FIXED_QUANTILE_50); + } + + /** + * Copy constructor, creates a new {@code Median} identical + * to the {@code original} + * + * @param original the {@code Median} instance to copy + * @throws NullArgumentException if original is null + */ + public Median(Median original) throws NullArgumentException { + super(original); + } + + /** + * Constructs a Median with the specific {@link EstimationType}, {@link NaNStrategy} and {@link PivotingStrategy}. + * + * @param estimationType one of the percentile {@link EstimationType estimation types} + * @param nanStrategy one of {@link NaNStrategy} to handle with NaNs + * @param kthSelector {@link KthSelector} to use for pivoting during search + * @throws MathIllegalArgumentException if p is not within (0,100] + * @throws NullArgumentException if type or NaNStrategy passed is null + */ + private Median(final EstimationType estimationType, final NaNStrategy nanStrategy, + final KthSelector kthSelector) + throws MathIllegalArgumentException { + super(FIXED_QUANTILE_50, estimationType, nanStrategy, kthSelector); + } + + /** {@inheritDoc} */ + @Override + public Median withEstimationType(final EstimationType newEstimationType) { + return new Median(newEstimationType, getNaNStrategy(), getKthSelector()); + } + + /** {@inheritDoc} */ + @Override + public Median withNaNStrategy(final NaNStrategy newNaNStrategy) { + return new Median(getEstimationType(), newNaNStrategy, getKthSelector()); + } + + /** {@inheritDoc} */ + @Override + public Median withKthSelector(final KthSelector newKthSelector) { + return new Median(getEstimationType(), getNaNStrategy(), newKthSelector); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Min.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Min.java new file mode 100644 index 000000000..69a61e18e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Min.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Returns the minimum of the available values. + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.NEGATIVE_INFINITY, + * the result is Double.NEGATIVE_INFINITY.
        • + *

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Min extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -2941995784909003131L; + + /**Number of values that have been added */ + private long n; + + /**Current value of the statistic */ + private double value; + + /** + * Create a Min instance + */ + public Min() { + n = 0; + value = Double.NaN; + } + + /** + * Copy constructor, creates a new {@code Min} identical + * to the {@code original} + * + * @param original the {@code Min} instance to copy + * @throws NullArgumentException if original is null + */ + public Min(Min original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + if (d < value || Double.isNaN(value)) { + value = d; + } + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = Double.NaN; + n = 0; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * Returns the minimum of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null or + * the array index parameters are not valid.

        + *

        + *

          + *
        • The result is NaN iff all values are NaN + * (i.e. NaN values have no impact on the value of the statistic).
        • + *
        • If any of the values equals Double.NEGATIVE_INFINITY, + * the result is Double.NEGATIVE_INFINITY.
        • + *

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the minimum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values,final int begin, final int length) + throws MathIllegalArgumentException { + double min = Double.NaN; + if (test(values, begin, length)) { + min = values[begin]; + for (int i = begin; i < begin + length; i++) { + if (!Double.isNaN(values[i])) { + min = (min < values[i]) ? min : values[i]; + } + } + } + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public Min copy() { + Min result = new Min(); + // No try-catch or advertised exception - args are non-null + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Min to copy + * @param dest Min to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Min source, Min dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java new file mode 100644 index 000000000..ecf53a480 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java @@ -0,0 +1,997 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.analysis.interpolation.LinearInterpolator; +import com.fr.third.org.apache.commons.math3.analysis.interpolation.NevilleInterpolator; +import com.fr.third.org.apache.commons.math3.analysis.interpolation.UnivariateInterpolator; +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic; + +/** + * A {@link StorelessUnivariateStatistic} estimating percentiles using the + * P2 + * Algorithm as explained by Raj + * Jain and Imrich Chlamtac in + * P2 Algorithm + * for Dynamic Calculation of Quantiles and Histogram Without Storing + * Observations. + *

        + * Note: This implementation is not synchronized and produces an approximate + * result. For small samples, where data can be stored and processed in memory, + * {@link Percentile} should be used.

        + * + */ +public class PSquarePercentile extends AbstractStorelessUnivariateStatistic + implements StorelessUnivariateStatistic, Serializable { + + /** + * The maximum array size used for psquare algorithm + */ + private static final int PSQUARE_CONSTANT = 5; + + /** + * A Default quantile needed in case if user prefers to use default no + * argument constructor. + */ + private static final double DEFAULT_QUANTILE_DESIRED = 50d; + + /** + * Serial ID + */ + private static final long serialVersionUID = 2283912083175715479L; + + /** + * A decimal formatter for print convenience + */ + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat( + "00.00"); + + /** + * Initial list of 5 numbers corresponding to 5 markers. NOTE:watch + * out for the add methods that are overloaded + */ + private final List initialFive = new FixedCapacityList( + PSQUARE_CONSTANT); + + /** + * The quantile needed should be in range of 0-1. The constructor + * {@link #PSquarePercentile(double)} ensures that passed in percentile is + * divided by 100. + */ + private final double quantile; + + /** + * lastObservation is the last observation value/input sample. No need to + * serialize + */ + private transient double lastObservation; + + /** + * Markers is the marker collection object which comes to effect + * only after 5 values are inserted + */ + private PSquareMarkers markers = null; + + /** + * Computed p value (i,e percentile value of data set hither to received) + */ + private double pValue = Double.NaN; + + /** + * Counter to count the values/observations accepted into this data set + */ + private long countOfObservations; + + /** + * Constructs a PSquarePercentile with the specific percentile value. + * @param p the percentile + * @throws OutOfRangeException if p is not greater than 0 and less + * than or equal to 100 + */ + public PSquarePercentile(final double p) { + if (p > 100 || p < 0) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE, + p, 0, 100); + } + this.quantile = p / 100d;// always set it within (0,1] + } + + /** + * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED + * default quantile} needed + */ + PSquarePercentile() { + this(DEFAULT_QUANTILE_DESIRED); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + double result = getResult(); + result = Double.isNaN(result) ? 37 : result; + final double markersHash = markers == null ? 0 : markers.hashCode(); + final double[] toHash = {result, quantile, markersHash, countOfObservations}; + return Arrays.hashCode(toHash); + } + + /** + * Returns true iff {@code o} is a {@code PSquarePercentile} returning the + * same values as this for {@code getResult()} and {@code getN()} and also + * having equal markers + * + * @param o object to compare + * @return true if {@code o} is a {@code PSquarePercentile} with + * equivalent internal state + */ + @Override + public boolean equals(Object o) { + boolean result = false; + if (this == o) { + result = true; + } else if (o != null && o instanceof PSquarePercentile) { + PSquarePercentile that = (PSquarePercentile) o; + boolean isNotNull = markers != null && that.markers != null; + boolean isNull = markers == null && that.markers == null; + result = isNotNull ? markers.equals(that.markers) : isNull; + // markers as in the case of first + // five observations + result = result && getN() == that.getN(); + } + return result; + } + + /** + * {@inheritDoc}The internal state updated due to the new value in this + * context is basically of the marker positions and computation of the + * approximate quantile. + * + * @param observation the observation currently being added. + */ + @Override + public void increment(final double observation) { + // Increment counter + countOfObservations++; + + // Store last observation + this.lastObservation = observation; + + // 0. Use Brute force for <5 + if (markers == null) { + if (initialFive.add(observation)) { + Collections.sort(initialFive); + pValue = + initialFive + .get((int) (quantile * (initialFive.size() - 1))); + return; + } + // 1. Initialize once after 5th observation + markers = newMarkers(initialFive, quantile); + } + // 2. process a Data Point and return pValue + pValue = markers.processDataPoint(observation); + } + + /** + * Returns a string containing the last observation, the current estimate + * of the quantile and all markers. + * + * @return string representation of state data + */ + @Override + public String toString() { + + if (markers == null) { + return String.format("obs=%s pValue=%s", + DECIMAL_FORMAT.format(lastObservation), + DECIMAL_FORMAT.format(pValue)); + } else { + return String.format("obs=%s markers=%s", + DECIMAL_FORMAT.format(lastObservation), markers.toString()); + } + } + + /** + * {@inheritDoc} + */ + public long getN() { + return countOfObservations; + } + + /** + * {@inheritDoc} + */ + @Override + public StorelessUnivariateStatistic copy() { + // multiply quantile by 100 now as anyway constructor divides it by 100 + PSquarePercentile copy = new PSquarePercentile(100d * quantile); + + if (markers != null) { + copy.markers = (PSquareMarkers) markers.clone(); + } + copy.countOfObservations = countOfObservations; + copy.pValue = pValue; + copy.initialFive.clear(); + copy.initialFive.addAll(initialFive); + return copy; + } + + /** + * Returns the quantile estimated by this statistic in the range [0.0-1.0] + * + * @return quantile estimated by {@link #getResult()} + */ + public double quantile() { + return quantile; + } + + /** + * {@inheritDoc}. This basically clears all the markers, the + * initialFive list and sets countOfObservations to 0. + */ + @Override + public void clear() { + markers = null; + initialFive.clear(); + countOfObservations = 0L; + pValue = Double.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + if (Double.compare(quantile, 1d) == 0) { + pValue = maximum(); + } else if (Double.compare(quantile, 0d) == 0) { + pValue = minimum(); + } + return pValue; + } + + /** + * @return maximum in the data set added to this statistic + */ + private double maximum() { + double val = Double.NaN; + if (markers != null) { + val = markers.height(PSQUARE_CONSTANT); + } else if (!initialFive.isEmpty()) { + val = initialFive.get(initialFive.size() - 1); + } + return val; + } + + /** + * @return minimum in the data set added to this statistic + */ + private double minimum() { + double val = Double.NaN; + if (markers != null) { + val = markers.height(1); + } else if (!initialFive.isEmpty()) { + val = initialFive.get(0); + } + return val; + } + + /** + * Markers is an encapsulation of the five markers/buckets as indicated in + * the original works. + */ + private static class Markers implements PSquareMarkers, Serializable { + /** + * Serial version id + */ + private static final long serialVersionUID = 1L; + + /** Low marker index */ + private static final int LOW = 2; + + /** High marker index */ + private static final int HIGH = 4; + + /** + * Array of 5+1 Markers (The first marker is dummy just so we + * can match the rest of indexes [1-5] indicated in the original works + * which follows unit based index) + */ + private final Marker[] markerArray; + + /** + * Kth cell belonging to [1-5] of the markerArray. No need for + * this to be serialized + */ + private transient int k = -1; + + /** + * Constructor + * + * @param theMarkerArray marker array to be used + */ + private Markers(final Marker[] theMarkerArray) { + MathUtils.checkNotNull(theMarkerArray); + markerArray = theMarkerArray; + for (int i = 1; i < PSQUARE_CONSTANT; i++) { + markerArray[i].previous(markerArray[i - 1]) + .next(markerArray[i + 1]).index(i); + } + markerArray[0].previous(markerArray[0]).next(markerArray[1]) + .index(0); + markerArray[5].previous(markerArray[4]).next(markerArray[5]) + .index(5); + } + + /** + * Constructor + * + * @param initialFive elements required to build Marker + * @param p quantile required to be computed + */ + private Markers(final List initialFive, final double p) { + this(createMarkerArray(initialFive, p)); + } + + /** + * Creates a marker array using initial five elements and a quantile + * + * @param initialFive list of initial five elements + * @param p the pth quantile + * @return Marker array + */ + private static Marker[] createMarkerArray( + final List initialFive, final double p) { + final int countObserved = + initialFive == null ? -1 : initialFive.size(); + if (countObserved < PSQUARE_CONSTANT) { + throw new InsufficientDataException( + LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, + countObserved, PSQUARE_CONSTANT); + } + Collections.sort(initialFive); + return new Marker[] { + new Marker(),// Null Marker + new Marker(initialFive.get(0), 1, 0, 1), + new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2), + new Marker(initialFive.get(2), 1 + 4 * p, p, 3), + new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4), + new Marker(initialFive.get(4), 5, 1, 5) }; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Arrays.deepHashCode(markerArray); + } + + /** + * {@inheritDoc}.This equals method basically checks for marker array to + * be deep equals. + * + * @param o is the other object + * @return true if the object compares with this object are equivalent + */ + @Override + public boolean equals(Object o) { + boolean result = false; + if (this == o) { + result = true; + } else if (o != null && o instanceof Markers) { + Markers that = (Markers) o; + result = Arrays.deepEquals(markerArray, that.markerArray); + } + return result; + } + + /** + * Process a data point + * + * @param inputDataPoint is the data point passed + * @return computed percentile + */ + public double processDataPoint(final double inputDataPoint) { + + // 1. Find cell and update minima and maxima + final int kthCell = findCellAndUpdateMinMax(inputDataPoint); + + // 2. Increment positions + incrementPositions(1, kthCell + 1, 5); + + // 2a. Update desired position with increments + updateDesiredPositions(); + + // 3. Adjust heights of m[2-4] if necessary + adjustHeightsOfMarkers(); + + // 4. Return percentile + return getPercentileValue(); + } + + /** + * Returns the percentile computed thus far. + * + * @return height of mid point marker + */ + public double getPercentileValue() { + return height(3); + } + + /** + * Finds the cell where the input observation / value fits. + * + * @param observation the input value to be checked for + * @return kth cell (of the markers ranging from 1-5) where observed + * sample fits + */ + private int findCellAndUpdateMinMax(final double observation) { + k = -1; + if (observation < height(1)) { + markerArray[1].markerHeight = observation; + k = 1; + } else if (observation < height(2)) { + k = 1; + } else if (observation < height(3)) { + k = 2; + } else if (observation < height(4)) { + k = 3; + } else if (observation <= height(5)) { + k = 4; + } else { + markerArray[5].markerHeight = observation; + k = 4; + } + return k; + } + + /** + * Adjust marker heights by setting quantile estimates to middle markers. + */ + private void adjustHeightsOfMarkers() { + for (int i = LOW; i <= HIGH; i++) { + estimate(i); + } + } + + /** + * {@inheritDoc} + */ + public double estimate(final int index) { + if (index < LOW || index > HIGH) { + throw new OutOfRangeException(index, LOW, HIGH); + } + return markerArray[index].estimate(); + } + + /** + * Increment positions by d. Refer to algorithm paper for the + * definition of d. + * + * @param d The increment value for the position + * @param startIndex start index of the marker array + * @param endIndex end index of the marker array + */ + private void incrementPositions(final int d, final int startIndex, + final int endIndex) { + for (int i = startIndex; i <= endIndex; i++) { + markerArray[i].incrementPosition(d); + } + } + + /** + * Desired positions incremented by bucket width. The bucket width is + * basically the desired increments. + */ + private void updateDesiredPositions() { + for (int i = 1; i < markerArray.length; i++) { + markerArray[i].updateDesiredPosition(); + } + } + + /** + * Sets previous and next markers after default read is done. + * + * @param anInputStream the input stream to be deserialized + * @throws ClassNotFoundException thrown when a desired class not found + * @throws IOException thrown due to any io errors + */ + private void readObject(ObjectInputStream anInputStream) + throws ClassNotFoundException, IOException { + // always perform the default de-serialization first + anInputStream.defaultReadObject(); + // Build links + for (int i = 1; i < PSQUARE_CONSTANT; i++) { + markerArray[i].previous(markerArray[i - 1]) + .next(markerArray[i + 1]).index(i); + } + markerArray[0].previous(markerArray[0]).next(markerArray[1]) + .index(0); + markerArray[5].previous(markerArray[4]).next(markerArray[5]) + .index(5); + } + + /** + * Return marker height given index + * + * @param markerIndex index of marker within (1,6) + * @return marker height + */ + public double height(final int markerIndex) { + if (markerIndex >= markerArray.length || markerIndex <= 0) { + throw new OutOfRangeException(markerIndex, 1, + markerArray.length); + } + return markerArray[markerIndex].markerHeight; + } + + /** + * {@inheritDoc}.Clone Markers + * + * @return cloned object + */ + @Override + public Object clone() { + return new Markers(new Marker[] { new Marker(), + (Marker) markerArray[1].clone(), + (Marker) markerArray[2].clone(), + (Marker) markerArray[3].clone(), + (Marker) markerArray[4].clone(), + (Marker) markerArray[5].clone() }); + + } + + /** + * Returns string representation of the Marker array. + * + * @return Markers as a string + */ + @Override + public String toString() { + return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]", + markerArray[1].toString(), markerArray[2].toString(), + markerArray[3].toString(), markerArray[4].toString(), + markerArray[5].toString()); + } + + } + + /** + * The class modeling the attributes of the marker of the P-square algorithm + */ + private static class Marker implements Serializable, Cloneable { + + /** + * Serial Version ID + */ + private static final long serialVersionUID = -3575879478288538431L; + + /** + * The marker index which is just a serial number for the marker in the + * marker array of 5+1. + */ + private int index; + + /** + * The integral marker position. Refer to the variable n in the original + * works. + */ + private double intMarkerPosition; + + /** + * Desired marker position. Refer to the variable n' in the original + * works. + */ + private double desiredMarkerPosition; + + /** + * Marker height or the quantile. Refer to the variable q in the + * original works. + */ + private double markerHeight; + + /** + * Desired marker increment. Refer to the variable dn' in the original + * works. + */ + private double desiredMarkerIncrement; + + /** + * Next and previous markers for easy linked navigation in loops. this + * is not serialized as they can be rebuilt during deserialization. + */ + private transient Marker next; + + /** + * The previous marker links + */ + private transient Marker previous; + + /** + * Nonlinear interpolator + */ + private final UnivariateInterpolator nonLinear = + new NevilleInterpolator(); + + /** + * Linear interpolator which is not serializable + */ + private transient UnivariateInterpolator linear = + new LinearInterpolator(); + + /** + * Default constructor + */ + private Marker() { + this.next = this.previous = this; + } + + /** + * Constructor of the marker with parameters + * + * @param heightOfMarker represent the quantile value + * @param makerPositionDesired represent the desired marker position + * @param markerPositionIncrement represent increments for position + * @param markerPositionNumber represent the position number of marker + */ + private Marker(double heightOfMarker, double makerPositionDesired, + double markerPositionIncrement, double markerPositionNumber) { + this(); + this.markerHeight = heightOfMarker; + this.desiredMarkerPosition = makerPositionDesired; + this.desiredMarkerIncrement = markerPositionIncrement; + this.intMarkerPosition = markerPositionNumber; + } + + /** + * Sets the previous marker. + * + * @param previousMarker the previous marker to the current marker in + * the array of markers + * @return this instance + */ + private Marker previous(final Marker previousMarker) { + MathUtils.checkNotNull(previousMarker); + this.previous = previousMarker; + return this; + } + + /** + * Sets the next marker. + * + * @param nextMarker the next marker to the current marker in the array + * of markers + * @return this instance + */ + private Marker next(final Marker nextMarker) { + MathUtils.checkNotNull(nextMarker); + this.next = nextMarker; + return this; + } + + /** + * Sets the index of the marker. + * + * @param indexOfMarker the array index of the marker in marker array + * @return this instance + */ + private Marker index(final int indexOfMarker) { + this.index = indexOfMarker; + return this; + } + + /** + * Update desired Position with increment. + */ + private void updateDesiredPosition() { + desiredMarkerPosition += desiredMarkerIncrement; + } + + /** + * Increment Position by d. + * + * @param d a delta value to increment + */ + private void incrementPosition(final int d) { + intMarkerPosition += d; + } + + /** + * Difference between desired and actual position + * + * @return difference between desired and actual position + */ + private double difference() { + return desiredMarkerPosition - intMarkerPosition; + } + + /** + * Estimate the quantile for the current marker. + * + * @return estimated quantile + */ + private double estimate() { + final double di = difference(); + final boolean isNextHigher = + next.intMarkerPosition - intMarkerPosition > 1; + final boolean isPreviousLower = + previous.intMarkerPosition - intMarkerPosition < -1; + + if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) { + final int d = di >= 0 ? 1 : -1; + final double[] xval = + new double[] { previous.intMarkerPosition, + intMarkerPosition, next.intMarkerPosition }; + final double[] yval = + new double[] { previous.markerHeight, markerHeight, + next.markerHeight }; + final double xD = intMarkerPosition + d; + + UnivariateFunction univariateFunction = + nonLinear.interpolate(xval, yval); + markerHeight = univariateFunction.value(xD); + + // If parabolic estimate is bad then turn linear + if (isEstimateBad(yval, markerHeight)) { + int delta = xD - xval[1] > 0 ? 1 : -1; + final double[] xBad = + new double[] { xval[1], xval[1 + delta] }; + final double[] yBad = + new double[] { yval[1], yval[1 + delta] }; + MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1 + univariateFunction = linear.interpolate(xBad, yBad); + markerHeight = univariateFunction.value(xD); + } + incrementPosition(d); + } + return markerHeight; + } + + /** + * Check if parabolic/nonlinear estimate is bad by checking if the + * ordinate found is beyond the y[0] and y[2]. + * + * @param y the array to get the bounds + * @param yD the estimate + * @return true if yD is a bad estimate + */ + private boolean isEstimateBad(final double[] y, final double yD) { + return yD <= y[0] || yD >= y[2]; + } + + /** + * {@inheritDoc}This equals method checks for marker attributes and + * as well checks if navigation pointers (next and previous) are the same + * between this and passed in object + * + * @param o Other object + * @return true if this equals passed in other object o + */ + @Override + public boolean equals(Object o) { + boolean result = false; + if (this == o) { + result = true; + } else if (o != null && o instanceof Marker) { + Marker that = (Marker) o; + + result = Double.compare(markerHeight, that.markerHeight) == 0; + result = + result && + Double.compare(intMarkerPosition, + that.intMarkerPosition) == 0; + result = + result && + Double.compare(desiredMarkerPosition, + that.desiredMarkerPosition) == 0; + result = + result && + Double.compare(desiredMarkerIncrement, + that.desiredMarkerIncrement) == 0; + + result = result && next.index == that.next.index; + result = result && previous.index == that.previous.index; + } + return result; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition, + desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index}); + } + + /** + * Read Object to deserialize. + * + * @param anInstream Stream Object data + * @throws IOException thrown for IO Errors + * @throws ClassNotFoundException thrown for class not being found + */ + private void readObject(ObjectInputStream anInstream) + throws ClassNotFoundException, IOException { + anInstream.defaultReadObject(); + previous=next=this; + linear = new LinearInterpolator(); + } + + /** + * Clone this instance. + * + * @return cloned marker + */ + @Override + public Object clone() { + return new Marker(markerHeight, desiredMarkerPosition, + desiredMarkerIncrement, intMarkerPosition); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return String.format( + "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d", + (double) index, Precision.round(intMarkerPosition, 0), + Precision.round(desiredMarkerPosition, 2), + Precision.round(markerHeight, 2), + Precision.round(desiredMarkerIncrement, 2), previous.index, + next.index); + } + } + + /** + * A simple fixed capacity list that has an upper bound to growth. + * Once its capacity is reached, {@code add} is a no-op, returning + * {@code false}. + * + * @param + */ + private static class FixedCapacityList extends ArrayList implements + Serializable { + /** + * Serialization Version Id + */ + private static final long serialVersionUID = 2283952083075725479L; + /** + * Capacity of the list + */ + private final int capacity; + + /** + * This constructor constructs the list with given capacity and as well + * as stores the capacity + * + * @param fixedCapacity the capacity to be fixed for this list + */ + FixedCapacityList(final int fixedCapacity) { + super(fixedCapacity); + this.capacity = fixedCapacity; + } + + /** + * {@inheritDoc} In addition it checks if the {@link #size()} returns a + * size that is within capacity and if true it adds; otherwise the list + * contents are unchanged and {@code false} is returned. + * + * @return true if addition is successful and false otherwise + */ + @Override + public boolean add(final E e) { + return size() < capacity ? super.add(e) : false; + } + + /** + * {@inheritDoc} In addition it checks if the sum of Collection size and + * this instance's {@link #size()} returns a value that is within + * capacity and if true it adds the collection; otherwise the list + * contents are unchanged and {@code false} is returned. + * + * @return true if addition is successful and false otherwise + */ + @Override + public boolean addAll(Collection collection) { + boolean isCollectionLess = + collection != null && + collection.size() + size() <= capacity; + return isCollectionLess ? super.addAll(collection) : false; + } + } + + /** + * A creation method to build Markers + * + * @param initialFive list of initial five elements + * @param p the quantile desired + * @return an instance of PSquareMarkers + */ + public static PSquareMarkers newMarkers(final List initialFive, + final double p) { + return new Markers(initialFive, p); + } + + /** + * An interface that encapsulates abstractions of the + * P-square algorithm markers as is explained in the original works. This + * interface is exposed with protected access to help in testability. + */ + protected interface PSquareMarkers extends Cloneable { + /** + * Returns Percentile value computed thus far. + * + * @return percentile + */ + double getPercentileValue(); + + /** + * A clone function to clone the current instance. It's created as an + * interface method as well for convenience though Cloneable is just a + * marker interface. + * + * @return clone of this instance + */ + Object clone(); + + /** + * Returns the marker height (or percentile) of a given marker index. + * + * @param markerIndex is the index of marker in the marker array + * @return percentile value of the marker index passed + * @throws OutOfRangeException in case the index is not within [1-5] + */ + double height(final int markerIndex); + + /** + * Process a data point by moving the marker heights based on estimator. + * + * @param inputDataPoint is the data point passed + * @return computed percentile + */ + double processDataPoint(final double inputDataPoint); + + /** + * An Estimate of the percentile value of a given Marker + * + * @param index the marker's index in the array of markers + * @return percentile estimate + * @throws OutOfRangeException in case if index is not within [1-5] + */ + double estimate(final int index); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Percentile.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Percentile.java new file mode 100644 index 000000000..6de7aa9be --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/Percentile.java @@ -0,0 +1,1072 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.BitSet; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.util.MedianOf3PivotingStrategy; +import com.fr.third.org.apache.commons.math3.util.PivotingStrategyInterface; +import com.fr.third.org.apache.commons.math3.util.Precision; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaNStrategy; +import com.fr.third.org.apache.commons.math3.util.KthSelector; + +/** + * Provides percentile computation. + *

        + * 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: + *

          + *
        1. Let n be the length of the (sorted) array and + * 0 < p <= 100 be the desired percentile.
        2. + *
        3. If n = 1 return the unique array element (regardless of + * the value of p); otherwise
        4. + *
        5. Compute the estimated percentile position + * pos = p * (n + 1) / 100 and the difference, d + * between pos and floor(pos) (i.e. the fractional + * part of pos).
        6. + *
        7. If pos < 1 return the smallest element in the array.
        8. + *
        9. Else if pos >= n return the largest element in the array.
        10. + *
        11. Else let lower be the element in position + * floor(pos) in the array and let upper be the + * next element in the array. Return lower + d * (upper - lower) + *
        12. + *

        + *

        + * 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 Arrays.sort(double[]) is the one determined + * by {@link java.lang.Double#compareTo(Double)}. This ordering makes + * Double.NaN larger than any other value (including + * Double.POSITIVE_INFINITY). Therefore, for example, the median + * (50th percentile) of + * {0, 1, 2, 3, 4, Double.NaN} evaluates to 2.5.

        + *

        + * Since percentile estimation usually involves interpolation between array + * elements, arrays containing NaN or infinite values will often + * result in NaN or infinite values returned.

        + *

        + * Further, to include different estimation types such as R1, R2 as mentioned in + * Quantile page(wikipedia), + * a type specific NaN handling strategy is used to closely match with the + * typically observed results from popular tools like R(R1-R9), Excel(R7).

        + *

        + * Since 2.2, Percentile uses only selection instead of complete sorting + * and caches selection algorithm state between calls to the various + * {@code evaluate} methods. This greatly improves efficiency, both for a single + * percentile and multiple percentile computations. To maximize performance when + * multiple percentiles are computed based on the same data, users should set the + * data array once using either one of the {@link #evaluate(double[], double)} or + * {@link #setData(double[])} methods and thereafter {@link #evaluate(double)} + * with just the percentile provided. + *

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Percentile extends AbstractUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -8091216485095130416L; + + /** Maximum number of partitioning pivots cached (each level double the number of pivots). */ + private static final int MAX_CACHED_LEVELS = 10; + + /** Maximum number of cached pivots in the pivots cached array */ + private static final int PIVOTS_HEAP_LENGTH = 0x1 << MAX_CACHED_LEVELS - 1; + + /** Default KthSelector used with default pivoting strategy */ + private final KthSelector kthSelector; + + /** Any of the {@link EstimationType}s such as {@link EstimationType#LEGACY CM} can be used. */ + private final EstimationType estimationType; + + /** NaN Handling of the input as defined by {@link NaNStrategy} */ + private final NaNStrategy nanStrategy; + + /** Determines what percentile is computed when evaluate() is activated + * with no quantile argument */ + private double quantile; + + /** Cached pivots. */ + private int[] cachedPivots; + + /** + * Constructs a Percentile with the following defaults. + *
          + *
        • default quantile: 50.0, can be reset with {@link #setQuantile(double)}
        • + *
        • default estimation type: {@link EstimationType#LEGACY}, + * can be reset with {@link #withEstimationType(EstimationType)}
        • + *
        • default NaN strategy: {@link NaNStrategy#REMOVED}, + * can be reset with {@link #withNaNStrategy(NaNStrategy)}
        • + *
        • a KthSelector that makes use of {@link MedianOf3PivotingStrategy}, + * can be reset with {@link #withKthSelector(KthSelector)}
        • + *
        + */ + public Percentile() { + // No try-catch or advertised exception here - arg is valid + this(50.0); + } + + /** + * Constructs a Percentile with the specific quantile value and the following + *
          + *
        • default method type: {@link EstimationType#LEGACY}
        • + *
        • default NaN strategy: {@link NaNStrategy#REMOVED}
        • + *
        • a Kth Selector : {@link KthSelector}
        • + *
        + * @param quantile the quantile + * @throws MathIllegalArgumentException if p is not greater than 0 and less + * than or equal to 100 + */ + public Percentile(final double quantile) throws MathIllegalArgumentException { + this(quantile, EstimationType.LEGACY, NaNStrategy.REMOVED, + new KthSelector(new MedianOf3PivotingStrategy())); + } + + /** + * Copy constructor, creates a new {@code Percentile} identical + * to the {@code original} + * + * @param original the {@code Percentile} instance to copy + * @throws NullArgumentException if original is null + */ + public Percentile(final Percentile original) throws NullArgumentException { + + MathUtils.checkNotNull(original); + estimationType = original.getEstimationType(); + nanStrategy = original.getNaNStrategy(); + kthSelector = original.getKthSelector(); + + setData(original.getDataRef()); + if (original.cachedPivots != null) { + System.arraycopy(original.cachedPivots, 0, cachedPivots, 0, original.cachedPivots.length); + } + setQuantile(original.quantile); + + } + + /** + * Constructs a Percentile with the specific quantile value, + * {@link EstimationType}, {@link NaNStrategy} and {@link KthSelector}. + * + * @param quantile the quantile to be computed + * @param estimationType one of the percentile {@link EstimationType estimation types} + * @param nanStrategy one of {@link NaNStrategy} to handle with NaNs + * @param kthSelector a {@link KthSelector} to use for pivoting during search + * @throws MathIllegalArgumentException if p is not within (0,100] + * @throws NullArgumentException if type or NaNStrategy passed is null + */ + protected Percentile(final double quantile, + final EstimationType estimationType, + final NaNStrategy nanStrategy, + final KthSelector kthSelector) + throws MathIllegalArgumentException { + setQuantile(quantile); + cachedPivots = null; + MathUtils.checkNotNull(estimationType); + MathUtils.checkNotNull(nanStrategy); + MathUtils.checkNotNull(kthSelector); + this.estimationType = estimationType; + this.nanStrategy = nanStrategy; + this.kthSelector = kthSelector; + } + + /** {@inheritDoc} */ + @Override + public void setData(final double[] values) { + if (values == null) { + cachedPivots = null; + } else { + cachedPivots = new int[PIVOTS_HEAP_LENGTH]; + Arrays.fill(cachedPivots, -1); + } + super.setData(values); + } + + /** {@inheritDoc} */ + @Override + public void setData(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + if (values == null) { + cachedPivots = null; + } else { + cachedPivots = new int[PIVOTS_HEAP_LENGTH]; + Arrays.fill(cachedPivots, -1); + } + super.setData(values, begin, length); + } + + /** + * Returns the result of evaluating the statistic over the stored data. + *

        + * The stored array is the one which was set by previous calls to + * {@link #setData(double[])} + *

        + * @param p the percentile value to compute + * @return the value of the statistic applied to the stored data + * @throws MathIllegalArgumentException if p is not a valid quantile value + * (p must be greater than 0 and less than or equal to 100) + */ + public double evaluate(final double p) throws MathIllegalArgumentException { + return evaluate(getDataRef(), p); + } + + /** + * Returns an estimate of the pth percentile of the values + * in the values array. + *

        + * Calls to this method do not modify the internal quantile + * state of this statistic.

        + *

        + *

          + *
        • Returns Double.NaN if values has length + * 0
        • + *
        • Returns (for any value of p) values[0] + * if values has length 1
        • + *
        • Throws MathIllegalArgumentException if values + * is null or p is not a valid quantile value (p must be greater than 0 + * and less than or equal to 100)
        • + *

        + *

        + * See {@link Percentile} for a description of the percentile estimation + * algorithm used.

        + * + * @param values input array of values + * @param p the percentile value to compute + * @return the percentile value or Double.NaN if the array is empty + * @throws MathIllegalArgumentException if values is null + * or p is invalid + */ + public double evaluate(final double[] values, final double p) + throws MathIllegalArgumentException { + test(values, 0, 0); + return evaluate(values, 0, values.length, p); + } + + /** + * Returns an estimate of the quantileth percentile of the + * designated values in the values array. The quantile + * estimated is determined by the quantile property. + *

        + *

          + *
        • Returns Double.NaN if length = 0
        • + *
        • Returns (for any value of quantile) + * values[begin] if length = 1
        • + *
        • Throws MathIllegalArgumentException if values + * is null, or start or length is invalid
        • + *

        + *

        + * See {@link Percentile} for a description of the percentile estimation + * algorithm used.

        + * + * @param values the input array + * @param start index of the first array element to include + * @param length the number of elements to include + * @return the percentile value + * @throws MathIllegalArgumentException if the parameters are not valid + * + */ + @Override + public double evaluate(final double[] values, final int start, final int length) + throws MathIllegalArgumentException { + return evaluate(values, start, length, quantile); + } + + /** + * Returns an estimate of the pth percentile of the values + * in the values array, starting with the element in (0-based) + * position begin in the array and including length + * values. + *

        + * Calls to this method do not modify the internal quantile + * state of this statistic.

        + *

        + *

          + *
        • Returns Double.NaN if length = 0
        • + *
        • Returns (for any value of p) values[begin] + * if length = 1
        • + *
        • Throws MathIllegalArgumentException if values + * is null , begin or length is invalid, or + * p is not a valid quantile value (p must be greater than 0 + * and less than or equal to 100)
        • + *

        + *

        + * See {@link Percentile} for a description of the percentile estimation + * algorithm used.

        + * + * @param values array of input values + * @param p the percentile to compute + * @param begin the first (0-based) element to include in the computation + * @param length the number of array elements to include + * @return the percentile value + * @throws MathIllegalArgumentException if the parameters are not valid or the + * input array is null + */ + public double evaluate(final double[] values, final int begin, + final int length, final double p) + throws MathIllegalArgumentException { + + test(values, begin, length); + if (p > 100 || p <= 0) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p, 0, 100); + } + if (length == 0) { + return Double.NaN; + } + if (length == 1) { + return values[begin]; // always return single value for n = 1 + } + + final double[] work = getWorkArray(values, begin, length); + final int[] pivotsHeap = getPivots(values); + return work.length == 0 ? Double.NaN : + estimationType.evaluate(work, pivotsHeap, p, kthSelector); + } + + /** Select a pivot index as the median of three + *

        + * Note: With the effect of allowing {@link KthSelector} to be set on + * {@link Percentile} instances(thus indirectly {@link PivotingStrategy}) + * this method wont take effect any more and hence is unsupported. + * @param work data array + * @param begin index of the first element of the slice + * @param end index after the last element of the slice + * @return the index of the median element chosen between the + * first, the middle and the last element of the array slice + * @deprecated Please refrain from using this method (as it wont take effect) + * and instead use {@link Percentile#withKthSelector(newKthSelector)} if + * required. + * + */ + @Deprecated + int medianOf3(final double[] work, final int begin, final int end) { + return new MedianOf3PivotingStrategy().pivotIndex(work, begin, end); + //throw new MathUnsupportedOperationException(); + } + + /** + * Returns the value of the quantile field (determines what percentile is + * computed when evaluate() is called with no quantile argument). + * + * @return quantile set while construction or {@link #setQuantile(double)} + */ + public double getQuantile() { + return quantile; + } + + /** + * Sets the value of the quantile field (determines what percentile is + * computed when evaluate() is called with no quantile argument). + * + * @param p a value between 0 < p <= 100 + * @throws MathIllegalArgumentException if p is not greater than 0 and less + * than or equal to 100 + */ + public void setQuantile(final double p) throws MathIllegalArgumentException { + if (p <= 0 || p > 100) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p, 0, 100); + } + quantile = p; + } + + /** + * {@inheritDoc} + */ + @Override + public Percentile copy() { + return new Percentile(this); + } + + /** + * Copies source to dest. + * @param source Percentile to copy + * @param dest Percentile to copy to + * @exception MathUnsupportedOperationException always thrown since 3.4 + * @deprecated as of 3.4 this method does not work anymore, as it fails to + * copy internal states between instances configured with different + * {@link EstimationType estimation type}, {@link NaNStrategy NaN handling strategies} + * and {@link KthSelector kthSelector}, it therefore always + * throw {@link MathUnsupportedOperationException} + */ + @Deprecated + public static void copy(final Percentile source, final Percentile dest) + throws MathUnsupportedOperationException { + throw new MathUnsupportedOperationException(); + } + + /** + * Get the work array to operate. Makes use of prior {@code storedData} if + * it exists or else do a check on NaNs and copy a subset of the array + * defined by begin and length parameters. The set {@link #nanStrategy} will + * be used to either retain/remove/replace any NaNs present before returning + * the resultant array. + * + * @param values the array of numbers + * @param begin index to start reading the array + * @param length the length of array to be read from the begin index + * @return work array sliced from values in the range [begin,begin+length) + * @throws MathIllegalArgumentException if values or indices are invalid + */ + protected double[] getWorkArray(final double[] values, final int begin, final int length) { + final double[] work; + if (values == getDataRef()) { + work = getDataRef(); + } else { + switch (nanStrategy) { + case MAXIMAL:// Replace NaNs with +INFs + work = replaceAndSlice(values, begin, length, Double.NaN, Double.POSITIVE_INFINITY); + break; + case MINIMAL:// Replace NaNs with -INFs + work = replaceAndSlice(values, begin, length, Double.NaN, Double.NEGATIVE_INFINITY); + break; + case REMOVED:// Drop NaNs from data + work = removeAndSlice(values, begin, length, Double.NaN); + break; + case FAILED:// just throw exception as NaN is un-acceptable + work = copyOf(values, begin, length); + MathArrays.checkNotNaN(work); + break; + default: //FIXED + work = copyOf(values,begin,length); + break; + } + } + return work; + } + + /** + * Make a copy of the array for the slice defined by array part from + * [begin, begin+length) + * @param values the input array + * @param begin start index of the array to include + * @param length number of elements to include from begin + * @return copy of a slice of the original array + */ + private static double[] copyOf(final double[] values, final int begin, final int length) { + MathArrays.verifyValues(values, begin, length); + return MathArrays.copyOfRange(values, begin, begin + length); + } + + /** + * Replace every occurrence of a given value with a replacement value in a + * copied slice of array defined by array part from [begin, begin+length). + * @param values the input array + * @param begin start index of the array to include + * @param length number of elements to include from begin + * @param original the value to be replaced with + * @param replacement the value to be used for replacement + * @return the copy of sliced array with replaced values + */ + private static double[] replaceAndSlice(final double[] values, + final int begin, final int length, + final double original, + final double replacement) { + final double[] temp = copyOf(values, begin, length); + for(int i = 0; i < length; i++) { + temp[i] = Precision.equalsIncludingNaN(original, temp[i]) ? + replacement : temp[i]; + } + return temp; + } + + /** + * Remove the occurrence of a given value in a copied slice of array + * defined by the array part from [begin, begin+length). + * @param values the input array + * @param begin start index of the array to include + * @param length number of elements to include from begin + * @param removedValue the value to be removed from the sliced array + * @return the copy of the sliced array after removing the removedValue + */ + private static double[] removeAndSlice(final double[] values, + final int begin, final int length, + final double removedValue) { + MathArrays.verifyValues(values, begin, length); + final double[] temp; + //BitSet(length) to indicate where the removedValue is located + final BitSet bits = new BitSet(length); + for (int i = begin; i < begin+length; i++) { + if (Precision.equalsIncludingNaN(removedValue, values[i])) { + bits.set(i - begin); + } + } + //Check if empty then create a new copy + if (bits.isEmpty()) { + temp = copyOf(values, begin, length); // Nothing removed, just copy + } else if(bits.cardinality() == length){ + temp = new double[0]; // All removed, just empty + }else { // Some removable, so new + temp = new double[length - bits.cardinality()]; + int start = begin; //start index from source array (i.e values) + int dest = 0; //dest index in destination array(i.e temp) + int nextOne = -1; //nextOne is the index of bit set of next one + int bitSetPtr = 0; //bitSetPtr is start index pointer of bitset + while ((nextOne = bits.nextSetBit(bitSetPtr)) != -1) { + final int lengthToCopy = nextOne - bitSetPtr; + System.arraycopy(values, start, temp, dest, lengthToCopy); + dest += lengthToCopy; + start = begin + (bitSetPtr = bits.nextClearBit(nextOne)); + } + //Copy any residue past start index till begin+length + if (start < begin + length) { + System.arraycopy(values,start,temp,dest,begin + length - start); + } + } + return temp; + } + + /** + * Get pivots which is either cached or a newly created one + * + * @param values array containing the input numbers + * @return cached pivots or a newly created one + */ + private int[] getPivots(final double[] values) { + final int[] pivotsHeap; + if (values == getDataRef()) { + pivotsHeap = cachedPivots; + } else { + pivotsHeap = new int[PIVOTS_HEAP_LENGTH]; + Arrays.fill(pivotsHeap, -1); + } + return pivotsHeap; + } + + /** + * Get the estimation {@link EstimationType type} used for computation. + * + * @return the {@code estimationType} set + */ + public EstimationType getEstimationType() { + return estimationType; + } + + /** + * Build a new instance similar to the current one except for the + * {@link EstimationType estimation type}. + *

        + * This method is intended to be used as part of a fluent-type builder + * pattern. Building finely tune instances should be done as follows: + *

        + *
        +     *   Percentile customized = new Percentile(quantile).
        +     *                           withEstimationType(estimationType).
        +     *                           withNaNStrategy(nanStrategy).
        +     *                           withKthSelector(kthSelector);
        +     * 
        + *

        + * If any of the {@code withXxx} method is omitted, the default value for + * the corresponding customization parameter will be used. + *

        + * @param newEstimationType estimation type for the new instance + * @return a new instance, with changed estimation type + * @throws NullArgumentException when newEstimationType is null + */ + public Percentile withEstimationType(final EstimationType newEstimationType) { + return new Percentile(quantile, newEstimationType, nanStrategy, kthSelector); + } + + /** + * Get the {@link NaNStrategy NaN Handling} strategy used for computation. + * @return {@code NaN Handling} strategy set during construction + */ + public NaNStrategy getNaNStrategy() { + return nanStrategy; + } + + /** + * Build a new instance similar to the current one except for the + * {@link NaNStrategy NaN handling} strategy. + *

        + * This method is intended to be used as part of a fluent-type builder + * pattern. Building finely tune instances should be done as follows: + *

        + *
        +     *   Percentile customized = new Percentile(quantile).
        +     *                           withEstimationType(estimationType).
        +     *                           withNaNStrategy(nanStrategy).
        +     *                           withKthSelector(kthSelector);
        +     * 
        + *

        + * If any of the {@code withXxx} method is omitted, the default value for + * the corresponding customization parameter will be used. + *

        + * @param newNaNStrategy NaN strategy for the new instance + * @return a new instance, with changed NaN handling strategy + * @throws NullArgumentException when newNaNStrategy is null + */ + public Percentile withNaNStrategy(final NaNStrategy newNaNStrategy) { + return new Percentile(quantile, estimationType, newNaNStrategy, kthSelector); + } + + /** + * Get the {@link KthSelector kthSelector} used for computation. + * @return the {@code kthSelector} set + */ + public KthSelector getKthSelector() { + return kthSelector; + } + + /** + * Get the {@link PivotingStrategyInterface} used in KthSelector for computation. + * @return the pivoting strategy set + */ + public PivotingStrategyInterface getPivotingStrategy() { + return kthSelector.getPivotingStrategy(); + } + + /** + * Build a new instance similar to the current one except for the + * {@link KthSelector kthSelector} instance specifically set. + *

        + * This method is intended to be used as part of a fluent-type builder + * pattern. Building finely tune instances should be done as follows: + *

        + *
        +     *   Percentile customized = new Percentile(quantile).
        +     *                           withEstimationType(estimationType).
        +     *                           withNaNStrategy(nanStrategy).
        +     *                           withKthSelector(newKthSelector);
        +     * 
        + *

        + * If any of the {@code withXxx} method is omitted, the default value for + * the corresponding customization parameter will be used. + *

        + * @param newKthSelector KthSelector for the new instance + * @return a new instance, with changed KthSelector + * @throws NullArgumentException when newKthSelector is null + */ + public Percentile withKthSelector(final KthSelector newKthSelector) { + return new Percentile(quantile, estimationType, nanStrategy, + newKthSelector); + } + + /** + * An enum for various estimation strategies of a percentile referred in + * wikipedia on quantile + * with the names of enum matching those of types mentioned in + * wikipedia. + *

        + * Each enum corresponding to the specific type of estimation in wikipedia + * implements the respective formulae that specializes in the below aspects + *

          + *
        • An index method to calculate approximate index of the + * estimate
        • + *
        • An estimate method to estimate a value found at the earlier + * computed index
        • + *
        • A minLimit on the quantile for which first element of sorted + * input is returned as an estimate
        • + *
        • A maxLimit on the quantile for which last element of sorted + * input is returned as an estimate
        • + *
        + *

        + * Users can now create {@link Percentile} by explicitly passing this enum; + * such as by invoking {@link Percentile#withEstimationType(EstimationType)} + *

        + * References: + *

          + *
        1. + * Wikipedia on quantile + *
        2. + *
        3. + * + * Hyndman, R. J. and Fan, Y. (1996) Sample quantiles in statistical + * packages, American Statistician 50, 361–365
        4. + *
        5. + * + * R-Manual
        6. + *
        + * + */ + public enum EstimationType { + /** + * This is the default type used in the {@link Percentile}.This method + * has the following formulae for index and estimates
        + * \( \begin{align} + * &index = (N+1)p\ \\ + * &estimate = x_{\lceil h\,-\,1/2 \rceil} \\ + * &minLimit = 0 \\ + * &maxLimit = 1 \\ + * \end{align}\) + */ + LEGACY("Legacy Apache Commons Math") { + /** + * {@inheritDoc}.This method in particular makes use of existing + * Apache Commons Math style of picking up the index. + */ + @Override + protected double index(final double p, final int length) { + final double minLimit = 0d; + final double maxLimit = 1d; + return Double.compare(p, minLimit) == 0 ? 0 : + Double.compare(p, maxLimit) == 0 ? + length : p * (length + 1); + } + }, + /** + * The method R_1 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= Np + 1/2\, \\ + * &estimate= x_{\lceil h\,-\,1/2 \rceil} \\ + * &minLimit = 0 \\ + * \end{align}\) + */ + R_1("R-1") { + + @Override + protected double index(final double p, final int length) { + final double minLimit = 0d; + return Double.compare(p, minLimit) == 0 ? 0 : length * p + 0.5; + } + + /** + * {@inheritDoc}This method in particular for R_1 uses ceil(pos-0.5) + */ + @Override + protected double estimate(final double[] values, + final int[] pivotsHeap, final double pos, + final int length, final KthSelector selector) { + return super.estimate(values, pivotsHeap, FastMath.ceil(pos - 0.5), length, selector); + } + + }, + /** + * The method R_2 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= Np + 1/2\, \\ + * &estimate=\frac{x_{\lceil h\,-\,1/2 \rceil} + + * x_{\lfloor h\,+\,1/2 \rfloor}}{2} \\ + * &minLimit = 0 \\ + * &maxLimit = 1 \\ + * \end{align}\) + */ + R_2("R-2") { + + @Override + protected double index(final double p, final int length) { + final double minLimit = 0d; + final double maxLimit = 1d; + return Double.compare(p, maxLimit) == 0 ? length : + Double.compare(p, minLimit) == 0 ? 0 : length * p + 0.5; + } + + /** + * {@inheritDoc}This method in particular for R_2 averages the + * values at ceil(p+0.5) and floor(p-0.5). + */ + @Override + protected double estimate(final double[] values, + final int[] pivotsHeap, final double pos, + final int length, final KthSelector selector) { + final double low = + super.estimate(values, pivotsHeap, FastMath.ceil(pos - 0.5), length, selector); + final double high = + super.estimate(values, pivotsHeap,FastMath.floor(pos + 0.5), length, selector); + return (low + high) / 2; + } + + }, + /** + * The method R_3 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= Np \\ + * &estimate= x_{\lfloor h \rceil}\, \\ + * &minLimit = 0.5/N \\ + * \end{align}\) + */ + R_3("R-3") { + @Override + protected double index(final double p, final int length) { + final double minLimit = 1d/2 / length; + return Double.compare(p, minLimit) <= 0 ? + 0 : FastMath.rint(length * p); + } + + }, + /** + * The method R_4 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= Np\, \\ + * &estimate= x_{\lfloor h \rfloor} + (h - + * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = 1/N \\ + * &maxLimit = 1 \\ + * \end{align}\) + */ + R_4("R-4") { + @Override + protected double index(final double p, final int length) { + final double minLimit = 1d / length; + final double maxLimit = 1d; + return Double.compare(p, minLimit) < 0 ? 0 : + Double.compare(p, maxLimit) == 0 ? length : length * p; + } + + }, + /** + * The method R_5 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= Np + 1/2\\ + * &estimate= x_{\lfloor h \rfloor} + (h - + * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = 0.5/N \\ + * &maxLimit = (N-0.5)/N + * \end{align}\) + */ + R_5("R-5"){ + + @Override + protected double index(final double p, final int length) { + final double minLimit = 1d/2 / length; + final double maxLimit = (length - 0.5) / length; + return Double.compare(p, minLimit) < 0 ? 0 : + Double.compare(p, maxLimit) >= 0 ? + length : length * p + 0.5; + } + }, + /** + * The method R_6 has the following formulae for index and estimates
        + * \( \begin{align} + * &index= (N + 1)p \\ + * &estimate= x_{\lfloor h \rfloor} + (h - + * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = 1/(N+1) \\ + * &maxLimit = N/(N+1) \\ + * \end{align}\) + *

        + * Note: This method computes the index in a manner very close to + * the default Commons Math Percentile existing implementation. However + * the difference to be noted is in picking up the limits with which + * first element (p<1(N+1)) and last elements (p>N/(N+1))are done. + * While in default case; these are done with p=0 and p=1 respectively. + */ + R_6("R-6"){ + + @Override + protected double index(final double p, final int length) { + final double minLimit = 1d / (length + 1); + final double maxLimit = 1d * length / (length + 1); + return Double.compare(p, minLimit) < 0 ? 0 : + Double.compare(p, maxLimit) >= 0 ? + length : (length + 1) * p; + } + }, + + /** + * The method R_7 implements Microsoft Excel style computation has the + * following formulae for index and estimates.
        + * \( \begin{align} + * &index = (N-1)p + 1 \\ + * &estimate = x_{\lfloor h \rfloor} + (h - + * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = 0 \\ + * &maxLimit = 1 \\ + * \end{align}\) + */ + R_7("R-7") { + @Override + protected double index(final double p, final int length) { + final double minLimit = 0d; + final double maxLimit = 1d; + return Double.compare(p, minLimit) == 0 ? 0 : + Double.compare(p, maxLimit) == 0 ? + length : 1 + (length - 1) * p; + } + + }, + + /** + * The method R_8 has the following formulae for index and estimates
        + * \( \begin{align} + * &index = (N + 1/3)p + 1/3 \\ + * &estimate = x_{\lfloor h \rfloor} + (h - + \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = (2/3)/(N+1/3) \\ + * &maxLimit = (N-1/3)/(N+1/3) \\ + * \end{align}\) + *

        + * As per Ref [2,3] this approach is most recommended as it provides + * an approximate median-unbiased estimate regardless of distribution. + */ + R_8("R-8") { + @Override + protected double index(final double p, final int length) { + final double minLimit = 2 * (1d / 3) / (length + 1d / 3); + final double maxLimit = + (length - 1d / 3) / (length + 1d / 3); + return Double.compare(p, minLimit) < 0 ? 0 : + Double.compare(p, maxLimit) >= 0 ? length : + (length + 1d / 3) * p + 1d / 3; + } + }, + + /** + * The method R_9 has the following formulae for index and estimates
        + * \( \begin{align} + * &index = (N + 1/4)p + 3/8\\ + * &estimate = x_{\lfloor h \rfloor} + (h - + \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h + * \rfloor}) \\ + * &minLimit = (5/8)/(N+1/4) \\ + * &maxLimit = (N-3/8)/(N+1/4) \\ + * \end{align}\) + */ + R_9("R-9") { + @Override + protected double index(final double p, final int length) { + final double minLimit = 5d/8 / (length + 0.25); + final double maxLimit = (length - 3d/8) / (length + 0.25); + return Double.compare(p, minLimit) < 0 ? 0 : + Double.compare(p, maxLimit) >= 0 ? length : + (length + 0.25) * p + 3d/8; + } + + }, + ; + + /** Simple name such as R-1, R-2 corresponding to those in wikipedia. */ + private final String name; + + /** + * Constructor + * + * @param type name of estimation type as per wikipedia + */ + EstimationType(final String type) { + this.name = type; + } + + /** + * Finds the index of array that can be used as starting index to + * {@link #estimate(double[], int[], double, int, KthSelector) estimate} + * percentile. The calculation of index calculation is specific to each + * {@link EstimationType}. + * + * @param p the pth quantile + * @param length the total number of array elements in the work array + * @return a computed real valued index as explained in the wikipedia + */ + protected abstract double index(final double p, final int length); + + /** + * Estimation based on Kth selection. This may be overridden + * in specific enums to compute slightly different estimations. + * + * @param work array of numbers to be used for finding the percentile + * @param pos indicated positional index prior computed from calling + * {@link #index(double, int)} + * @param pivotsHeap an earlier populated cache if exists; will be used + * @param length size of array considered + * @param selector a {@link KthSelector} used for pivoting during search + * @return estimated percentile + */ + protected double estimate(final double[] work, final int[] pivotsHeap, + final double pos, final int length, + final KthSelector selector) { + + final double fpos = FastMath.floor(pos); + final int intPos = (int) fpos; + final double dif = pos - fpos; + + if (pos < 1) { + return selector.select(work, pivotsHeap, 0); + } + if (pos >= length) { + return selector.select(work, pivotsHeap, length - 1); + } + + final double lower = selector.select(work, pivotsHeap, intPos - 1); + final double upper = selector.select(work, pivotsHeap, intPos); + return lower + dif * (upper - lower); + } + + /** + * Evaluate method to compute the percentile for a given bounded array + * using earlier computed pivots heap.
        + * This basically calls the {@link #index(double, int) index} and then + * {@link #estimate(double[], int[], double, int, KthSelector) estimate} + * functions to return the estimated percentile value. + * + * @param work array of numbers to be used for finding the percentile + * @param pivotsHeap a prior cached heap which can speed up estimation + * @param p the pth quantile to be computed + * @param selector a {@link KthSelector} used for pivoting during search + * @return estimated percentile + * @throws OutOfRangeException if p is out of range + * @throws NullArgumentException if work array is null + */ + protected double evaluate(final double[] work, final int[] pivotsHeap, final double p, + final KthSelector selector) { + MathUtils.checkNotNull(work); + if (p > 100 || p <= 0) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, + p, 0, 100); + } + return estimate(work, pivotsHeap, index(p/100d, work.length), work.length, selector); + } + + /** + * Evaluate method to compute the percentile for a given bounded array. + * This basically calls the {@link #index(double, int) index} and then + * {@link #estimate(double[], int[], double, int, KthSelector) estimate} + * functions to return the estimated percentile value. Please + * note that this method does not make use of cached pivots. + * + * @param work array of numbers to be used for finding the percentile + * @param p the pth quantile to be computed + * @return estimated percentile + * @param selector a {@link KthSelector} used for pivoting during search + * @throws OutOfRangeException if length or p is out of range + * @throws NullArgumentException if work array is null + */ + public double evaluate(final double[] work, final double p, final KthSelector selector) { + return this.evaluate(work, null, p, selector); + } + + /** + * Gets the name of the enum + * + * @return the name + */ + String getName() { + return name; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/package-info.java new file mode 100644 index 000000000..8afea8e3b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/rank/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Summary statistics based on ranks. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.rank; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Product.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Product.java new file mode 100644 index 000000000..55a145857 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Product.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.summary; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; +import com.fr.third.org.apache.commons.math3.stat.descriptive.WeightedEvaluation; + +/** + * Returns the product of the available values. + *

        + * If there are no values in the dataset, then 1 is returned. + * If any of the values are + * NaN, then NaN is returned.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Product extends AbstractStorelessUnivariateStatistic implements Serializable, WeightedEvaluation { + + /** Serializable version identifier */ + private static final long serialVersionUID = 2824226005990582538L; + + /**The number of values that have been added */ + private long n; + + /** + * The current Running Product. + */ + private double value; + + /** + * Create a Product instance + */ + public Product() { + n = 0; + value = 1; + } + + /** + * Copy constructor, creates a new {@code Product} identical + * to the {@code original} + * + * @param original the {@code Product} instance to copy + * @throws NullArgumentException if original is null + */ + public Product(Product original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + value *= d; + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = 1; + n = 0; + } + + /** + * Returns the product of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the product of the values or 1 if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + double product = Double.NaN; + if (test(values, begin, length, true)) { + product = 1.0; + for (int i = begin; i < begin + length; i++) { + product *= values[i]; + } + } + return product; + } + + /** + *

        Returns the weighted product of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty.

        + * + *

        Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *
        • the start and length arguments do not determine a valid array
        • + *

        + * + *

        Uses the formula,

        +     *    weighted product = ∏values[i]weights[i]
        +     * 
        + * that is, the weights are applied as exponents when computing the weighted product.

        + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the product of the values or 1 if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, + final int begin, final int length) throws MathIllegalArgumentException { + double product = Double.NaN; + if (test(values, weights, begin, length, true)) { + product = 1.0; + for (int i = begin; i < begin + length; i++) { + product *= FastMath.pow(values[i], weights[i]); + } + } + return product; + } + + /** + *

        Returns the weighted product of the entries in the input array.

        + * + *

        Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *

        + * + *

        Uses the formula,

        +     *    weighted product = ∏values[i]weights[i]
        +     * 
        + * that is, the weights are applied as exponents when computing the weighted product.

        + * + * @param values the input array + * @param weights the weights array + * @return the product of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights) + throws MathIllegalArgumentException { + return evaluate(values, weights, 0, values.length); + } + + + /** + * {@inheritDoc} + */ + @Override + public Product copy() { + Product result = new Product(); + // No try-catch or advertised exception because args are valid + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Product to copy + * @param dest Product to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Product source, Product dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Sum.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Sum.java new file mode 100644 index 000000000..7e51b014e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/Sum.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.summary; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + + +/** + * Returns the sum of the available values. + *

        + * If there are no values in the dataset, then 0 is returned. + * If any of the values are + * NaN, then NaN is returned.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class Sum extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -8231831954703408316L; + + /** */ + private long n; + + /** + * The currently running sum. + */ + private double value; + + /** + * Create a Sum instance + */ + public Sum() { + n = 0; + value = 0; + } + + /** + * Copy constructor, creates a new {@code Sum} identical + * to the {@code original} + * + * @param original the {@code Sum} instance to copy + * @throws NullArgumentException if original is null + */ + public Sum(Sum original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + value += d; + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = 0; + n = 0; + } + + /** + * The sum of the entries in the specified portion of + * the input array, or 0 if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the values or 0 if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + double sum = Double.NaN; + if (test(values, begin, length, true)) { + sum = 0.0; + for (int i = begin; i < begin + length; i++) { + sum += values[i]; + } + } + return sum; + } + + /** + * The weighted sum of the entries in the specified portion of + * the input array, or 0 if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *
        • the start and length arguments do not determine a valid array
        • + *

        + *

        + * Uses the formula,

        +     *    weighted sum = Σ(values[i] * weights[i])
        +     * 

        + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the values or 0 if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights, + final int begin, final int length) throws MathIllegalArgumentException { + double sum = Double.NaN; + if (test(values, weights, begin, length, true)) { + sum = 0.0; + for (int i = begin; i < begin + length; i++) { + sum += values[i] * weights[i]; + } + } + return sum; + } + + /** + * The weighted sum of the entries in the the input array. + *

        + * Throws MathIllegalArgumentException if any of the following are true: + *

        • the values array is null
        • + *
        • the weights array is null
        • + *
        • the weights array does not have the same length as the values array
        • + *
        • the weights array contains one or more infinite values
        • + *
        • the weights array contains one or more NaN values
        • + *
        • the weights array contains negative values
        • + *

        + *

        + * Uses the formula,

        +     *    weighted sum = Σ(values[i] * weights[i])
        +     * 

        + * + * @param values the input array + * @param weights the weights array + * @return the sum of the values or Double.NaN if length = 0 + * @throws MathIllegalArgumentException if the parameters are not valid + * @since 2.1 + */ + public double evaluate(final double[] values, final double[] weights) + throws MathIllegalArgumentException { + return evaluate(values, weights, 0, values.length); + } + + /** + * {@inheritDoc} + */ + @Override + public Sum copy() { + Sum result = new Sum(); + // No try-catch or advertised exception because args are valid + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source Sum to copy + * @param dest Sum to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(Sum source, Sum dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java new file mode 100644 index 000000000..dbeaf6571 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.summary; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Returns the sum of the natural logs for this collection of values. + *

        + * Uses {@link FastMath#log(double)} to compute the logs. + * Therefore, + *

          + *
        • If any of values are < 0, the result is NaN.
        • + *
        • If all values are non-negative and less than + * Double.POSITIVE_INFINITY, but at least one value is 0, the + * result is Double.NEGATIVE_INFINITY.
        • + *
        • If both Double.POSITIVE_INFINITY and + * Double.NEGATIVE_INFINITY are among the values, the result is + * NaN.
        • + *

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class SumOfLogs extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -370076995648386763L; + + /**Number of values that have been added */ + private int n; + + /** + * The currently running value + */ + private double value; + + /** + * Create a SumOfLogs instance + */ + public SumOfLogs() { + value = 0d; + n = 0; + } + + /** + * Copy constructor, creates a new {@code SumOfLogs} identical + * to the {@code original} + * + * @param original the {@code SumOfLogs} instance to copy + * @throws NullArgumentException if original is null + */ + public SumOfLogs(SumOfLogs original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + value += FastMath.log(d); + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = 0d; + n = 0; + } + + /** + * Returns the sum of the natural logs of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + *

        + * See {@link SumOfLogs}.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the natural logs of the values or 0 if + * length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + double sumLog = Double.NaN; + if (test(values, begin, length, true)) { + sumLog = 0.0; + for (int i = begin; i < begin + length; i++) { + sumLog += FastMath.log(values[i]); + } + } + return sumLog; + } + + /** + * {@inheritDoc} + */ + @Override + public SumOfLogs copy() { + SumOfLogs result = new SumOfLogs(); + // No try-catch or advertised exception here because args are valid + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source SumOfLogs to copy + * @param dest SumOfLogs to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SumOfLogs source, SumOfLogs dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java new file mode 100644 index 000000000..81c011a9e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.summary; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.util.MathUtils; +import com.fr.third.org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic; + +/** + * Returns the sum of the squares of the available values. + *

        + * If there are no values in the dataset, then 0 is returned. + * If any of the values are + * NaN, then NaN is returned.

        + *

        + * Note that this implementation is not synchronized. If + * multiple threads access an instance of this class concurrently, and at least + * one of the threads invokes the increment() or + * clear() method, it must be synchronized externally.

        + * + */ +public class SumOfSquares extends AbstractStorelessUnivariateStatistic implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 1460986908574398008L; + + /** */ + private long n; + + /** + * The currently running sumSq + */ + private double value; + + /** + * Create a SumOfSquares instance + */ + public SumOfSquares() { + n = 0; + value = 0; + } + + /** + * Copy constructor, creates a new {@code SumOfSquares} identical + * to the {@code original} + * + * @param original the {@code SumOfSquares} instance to copy + * @throws NullArgumentException if original is null + */ + public SumOfSquares(SumOfSquares original) throws NullArgumentException { + copy(original, this); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment(final double d) { + value += d * d; + n++; + } + + /** + * {@inheritDoc} + */ + @Override + public double getResult() { + return value; + } + + /** + * {@inheritDoc} + */ + public long getN() { + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value = 0; + n = 0; + } + + /** + * Returns the sum of the squares of the entries in the specified portion of + * the input array, or Double.NaN if the designated subarray + * is empty. + *

        + * Throws MathIllegalArgumentException if the array is null.

        + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return the sum of the squares of the values or 0 if length = 0 + * @throws MathIllegalArgumentException if the array is null or the array index + * parameters are not valid + */ + @Override + public double evaluate(final double[] values,final int begin, final int length) + throws MathIllegalArgumentException { + double sumSq = Double.NaN; + if (test(values, begin, length, true)) { + sumSq = 0.0; + for (int i = begin; i < begin + length; i++) { + sumSq += values[i] * values[i]; + } + } + return sumSq; + } + + /** + * {@inheritDoc} + */ + @Override + public SumOfSquares copy() { + SumOfSquares result = new SumOfSquares(); + // no try-catch or advertised exception here because args are valid + copy(this, result); + return result; + } + + /** + * Copies source to dest. + *

        Neither source nor dest can be null.

        + * + * @param source SumOfSquares to copy + * @param dest SumOfSquares to copy to + * @throws NullArgumentException if either source or dest is null + */ + public static void copy(SumOfSquares source, SumOfSquares dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + dest.setData(source.getDataRef()); + dest.n = source.n; + dest.value = source.value; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/package-info.java new file mode 100644 index 000000000..f643d49e9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/descriptive/summary/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Other summary statistics. + */ +package com.fr.third.org.apache.commons.math3.stat.descriptive.summary; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java new file mode 100644 index 000000000..c50928018 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +/** + * Represents an alternative hypothesis for a hypothesis test. + * + * @since 3.3 + */ +public enum AlternativeHypothesis { + + /** + * Represents a two-sided test. H0: p=p0, H1: p ≠ p0 + */ + TWO_SIDED, + + /** + * Represents a right-sided test. H0: p ≤ p0, H1: p > p0. + */ + GREATER_THAN, + + /** + * Represents a left-sided test. H0: p ≥ p0, H1: p < p0. + */ + LESS_THAN +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/BinomialTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/BinomialTest.java new file mode 100644 index 000000000..9e0949ec1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/BinomialTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.BinomialDistribution; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Implements binomial test statistics. + *

        + * Exact test for the statistical significance of deviations from a + * theoretically expected distribution of observations into two categories. + * + * @see Binomial test (Wikipedia) + * @since 3.3 + */ +public class BinomialTest { + + /** + * Returns whether the null hypothesis can be rejected with the given confidence level. + *

        + * Preconditions: + *

          + *
        • Number of trials must be ≥ 0.
        • + *
        • Number of successes must be ≥ 0.
        • + *
        • Number of successes must be ≤ number of trials.
        • + *
        • Probability must be ≥ 0 and ≤ 1.
        • + *
        + * + * @param numberOfTrials number of trials performed + * @param numberOfSuccesses number of successes observed + * @param probability assumed probability of a single trial under the null hypothesis + * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with confidence {@code 1 - alpha} + * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative + * @throws OutOfRangeException if {@code probability} is not between 0 and 1 + * @throws MathIllegalArgumentException if {@code numberOfTrials} < {@code numberOfSuccesses} or + * if {@code alternateHypothesis} is null. + * @see AlternativeHypothesis + */ + public boolean binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, + AlternativeHypothesis alternativeHypothesis, double alpha) { + double pValue = binomialTest(numberOfTrials, numberOfSuccesses, probability, alternativeHypothesis); + return pValue < alpha; + } + + /** + * Returns the observed significance level, or + * p-value, + * associated with a Binomial test. + *

        + * The number returned is the smallest significance level at which one can reject the null hypothesis. + * The form of the hypothesis depends on {@code alternativeHypothesis}.

        + *

        + * The p-Value represents the likelihood of getting a result at least as extreme as the sample, + * given the provided {@code probability} of success on a single trial. For single-sided tests, + * this value can be directly derived from the Binomial distribution. For the two-sided test, + * the implementation works as follows: we start by looking at the most extreme cases + * (0 success and n success where n is the number of trials from the sample) and determine their likelihood. + * The lower value is added to the p-Value (if both values are equal, both are added). Then we continue with + * the next extreme value, until we added the value for the actual observed sample.

        + *

        + * Preconditions: + *

          + *
        • Number of trials must be ≥ 0.
        • + *
        • Number of successes must be ≥ 0.
        • + *
        • Number of successes must be ≤ number of trials.
        • + *
        • Probability must be ≥ 0 and ≤ 1.
        • + *

        + * + * @param numberOfTrials number of trials performed + * @param numberOfSuccesses number of successes observed + * @param probability assumed probability of a single trial under the null hypothesis + * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) + * @return p-value + * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative + * @throws OutOfRangeException if {@code probability} is not between 0 and 1 + * @throws MathIllegalArgumentException if {@code numberOfTrials} < {@code numberOfSuccesses} or + * if {@code alternateHypothesis} is null. + * @see AlternativeHypothesis + */ + public double binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, + AlternativeHypothesis alternativeHypothesis) { + if (numberOfTrials < 0) { + throw new NotPositiveException(numberOfTrials); + } + if (numberOfSuccesses < 0) { + throw new NotPositiveException(numberOfSuccesses); + } + if (probability < 0 || probability > 1) { + throw new OutOfRangeException(probability, 0, 1); + } + if (numberOfTrials < numberOfSuccesses) { + throw new MathIllegalArgumentException( + LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER, + numberOfTrials, numberOfSuccesses); + } + if (alternativeHypothesis == null) { + throw new NullArgumentException(); + } + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final BinomialDistribution distribution = new BinomialDistribution(null, numberOfTrials, probability); + switch (alternativeHypothesis) { + case GREATER_THAN: + return 1 - distribution.cumulativeProbability(numberOfSuccesses - 1); + case LESS_THAN: + return distribution.cumulativeProbability(numberOfSuccesses); + case TWO_SIDED: + int criticalValueLow = 0; + int criticalValueHigh = numberOfTrials; + double pTotal = 0; + + while (true) { + double pLow = distribution.probability(criticalValueLow); + double pHigh = distribution.probability(criticalValueHigh); + + if (pLow == pHigh) { + pTotal += 2 * pLow; + criticalValueLow++; + criticalValueHigh--; + } else if (pLow < pHigh) { + pTotal += pLow; + criticalValueLow++; + } else { + pTotal += pHigh; + criticalValueHigh--; + } + + if (criticalValueLow > numberOfSuccesses || criticalValueHigh < numberOfSuccesses) { + break; + } + } + return pTotal; + default: + throw new MathInternalError(LocalizedFormats. OUT_OF_RANGE_SIMPLE, alternativeHypothesis, + AlternativeHypothesis.TWO_SIDED, AlternativeHypothesis.LESS_THAN); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/ChiSquareTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/ChiSquareTest.java new file mode 100644 index 000000000..1f8f803ca --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/ChiSquareTest.java @@ -0,0 +1,602 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.ChiSquaredDistribution; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implements Chi-Square test statistics. + * + *

        This implementation handles both known and unknown distributions.

        + * + *

        Two samples tests can be used when the distribution is unknown a priori + * but provided by one sample, or when the hypothesis under test is that the two + * samples come from the same underlying distribution.

        + * + */ +public class ChiSquareTest { + + /** + * Construct a ChiSquareTest + */ + public ChiSquareTest() { + super(); + } + + /** + * Computes the + * Chi-Square statistic comparing observed and expected + * frequency counts. + *

        + * This statistic can be used to perform a Chi-Square test evaluating the null + * hypothesis that the observed counts follow the expected distribution.

        + *

        + * Preconditions:

          + *
        • Expected counts must all be positive. + *
        • + *
        • Observed counts must all be ≥ 0. + *
        • + *
        • The observed and expected arrays must have the same length and + * their common length must be at least 2. + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + *

        Note: This implementation rescales the + * expected array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @return chiSquare test statistic + * @throws NotPositiveException if observed has negative entries + * @throws NotStrictlyPositiveException if expected has entries that are + * not strictly positive + * @throws DimensionMismatchException if the arrays length is less than 2 + */ + public double chiSquare(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException { + + if (expected.length < 2) { + throw new DimensionMismatchException(expected.length, 2); + } + if (expected.length != observed.length) { + throw new DimensionMismatchException(expected.length, observed.length); + } + MathArrays.checkPositive(expected); + MathArrays.checkNonNegative(observed); + + double sumExpected = 0d; + double sumObserved = 0d; + for (int i = 0; i < observed.length; i++) { + sumExpected += expected[i]; + sumObserved += observed[i]; + } + double ratio = 1.0d; + boolean rescale = false; + if (FastMath.abs(sumExpected - sumObserved) > 10E-6) { + ratio = sumObserved / sumExpected; + rescale = true; + } + double sumSq = 0.0d; + for (int i = 0; i < observed.length; i++) { + if (rescale) { + final double dev = observed[i] - ratio * expected[i]; + sumSq += dev * dev / (ratio * expected[i]); + } else { + final double dev = observed[i] - expected[i]; + sumSq += dev * dev / expected[i]; + } + } + return sumSq; + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a + * + * Chi-square goodness of fit test comparing the observed + * frequency counts to those in the expected array. + *

        + * 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.

        + *

        + * Preconditions:

          + *
        • Expected counts must all be positive. + *
        • + *
        • Observed counts must all be ≥ 0. + *
        • + *
        • The observed and expected arrays must have the same length and + * their common length must be at least 2. + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + *

        Note: This implementation rescales the + * expected array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @return p-value + * @throws NotPositiveException if observed has negative entries + * @throws NotStrictlyPositiveException if expected has entries that are + * not strictly positive + * @throws DimensionMismatchException if the arrays length is less than 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double chiSquareTest(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = + new ChiSquaredDistribution(null, expected.length - 1.0); + return 1.0 - distribution.cumulativeProbability(chiSquare(expected, observed)); + } + + /** + * Performs a + * Chi-square goodness of fit test evaluating the null hypothesis that the + * observed counts conform to the frequency distribution described by the expected + * counts, with significance level alpha. Returns true iff the null + * hypothesis can be rejected with 100 * (1 - alpha) percent confidence. + *

        + * Example:
        + * To test the hypothesis that observed follows + * expected at the 99% level, use

        + * chiSquareTest(expected, observed, 0.01)

        + *

        + * Preconditions:

          + *
        • Expected counts must all be positive. + *
        • + *
        • Observed counts must all be ≥ 0. + *
        • + *
        • The observed and expected arrays must have the same length and + * their common length must be at least 2. + *
        • 0 < alpha < 0.5 + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + *

        Note: This implementation rescales the + * expected array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @param alpha significance level of the test + * @return true iff null hypothesis can be rejected with confidence + * 1 - alpha + * @throws NotPositiveException if observed has negative entries + * @throws NotStrictlyPositiveException if expected has entries that are + * not strictly positive + * @throws DimensionMismatchException if the arrays length is less than 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean chiSquareTest(final double[] expected, final long[] observed, + final double alpha) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, OutOfRangeException, MaxCountExceededException { + + if ((alpha <= 0) || (alpha > 0.5)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, + alpha, 0, 0.5); + } + return chiSquareTest(expected, observed) < alpha; + + } + + /** + * Computes the Chi-Square statistic associated with a + * + * chi-square test of independence based on the input counts + * array, viewed as a two-way table. + *

        + * The rows of the 2-way table are + * count[0], ... , count[count.length - 1]

        + *

        + * Preconditions:

          + *
        • All counts must be ≥ 0. + *
        • + *
        • The count array must be rectangular (i.e. all count[i] subarrays + * must have the same length). + *
        • + *
        • The 2-way table represented by counts must have at + * least 2 columns and at least 2 rows. + *
        • + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param counts array representation of 2-way table + * @return chiSquare test statistic + * @throws NullArgumentException if the array is null + * @throws DimensionMismatchException if the array is not rectangular + * @throws NotPositiveException if {@code counts} has negative entries + */ + public double chiSquare(final long[][] counts) + throws NullArgumentException, NotPositiveException, + DimensionMismatchException { + + checkArray(counts); + int nRows = counts.length; + int nCols = counts[0].length; + + // compute row, column and total sums + double[] rowSum = new double[nRows]; + double[] colSum = new double[nCols]; + double total = 0.0d; + for (int row = 0; row < nRows; row++) { + for (int col = 0; col < nCols; col++) { + rowSum[row] += counts[row][col]; + colSum[col] += counts[row][col]; + total += counts[row][col]; + } + } + + // compute expected counts and chi-square + double sumSq = 0.0d; + double expected = 0.0d; + for (int row = 0; row < nRows; row++) { + for (int col = 0; col < nCols; col++) { + expected = (rowSum[row] * colSum[col]) / total; + sumSq += ((counts[row][col] - expected) * + (counts[row][col] - expected)) / expected; + } + } + return sumSq; + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a + * + * chi-square test of independence based on the input counts + * array, viewed as a two-way table. + *

        + * The rows of the 2-way table are + * count[0], ... , count[count.length - 1]

        + *

        + * Preconditions:

          + *
        • All counts must be ≥ 0. + *
        • + *
        • The count array must be rectangular (i.e. all count[i] subarrays must have + * the same length). + *
        • + *
        • The 2-way table represented by counts must have at least 2 + * columns and at least 2 rows. + *
        • + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param counts array representation of 2-way table + * @return p-value + * @throws NullArgumentException if the array is null + * @throws DimensionMismatchException if the array is not rectangular + * @throws NotPositiveException if {@code counts} has negative entries + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double chiSquareTest(final long[][] counts) + throws NullArgumentException, DimensionMismatchException, + NotPositiveException, MaxCountExceededException { + + checkArray(counts); + double df = ((double) counts.length -1) * ((double) counts[0].length - 1); + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = new ChiSquaredDistribution(df); + return 1 - distribution.cumulativeProbability(chiSquare(counts)); + + } + + /** + * Performs a + * chi-square test of independence 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 alpha. + * Returns true iff the null hypothesis can be rejected with 100 * (1 - alpha) percent + * confidence. + *

        + * The rows of the 2-way table are + * count[0], ... , count[count.length - 1]

        + *

        + * Example:
        + * To test the null hypothesis that the counts in + * count[0], ... , count[count.length - 1] + * all correspond to the same underlying probability distribution at the 99% level, use

        + *

        chiSquareTest(counts, 0.01)

        + *

        + * Preconditions:

          + *
        • All counts must be ≥ 0. + *
        • + *
        • The count array must be rectangular (i.e. all count[i] subarrays must have the + * same length).
        • + *
        • The 2-way table represented by counts must have at least 2 columns and + * at least 2 rows.
        • + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param counts array representation of 2-way table + * @param alpha significance level of the test + * @return true iff null hypothesis can be rejected with confidence + * 1 - alpha + * @throws NullArgumentException if the array is null + * @throws DimensionMismatchException if the array is not rectangular + * @throws NotPositiveException if {@code counts} has any negative entries + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean chiSquareTest(final long[][] counts, final double alpha) + throws NullArgumentException, DimensionMismatchException, + NotPositiveException, OutOfRangeException, MaxCountExceededException { + + if ((alpha <= 0) || (alpha > 0.5)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, + alpha, 0, 0.5); + } + return chiSquareTest(counts) < alpha; + + } + + /** + *

        Computes a + * + * Chi-Square two sample test statistic comparing bin frequency counts + * in observed1 and observed2. The + * sums of frequency counts in the two samples are not required to be the + * same. The formula used to compute the test statistic is

        + * + * ∑[(K * observed1[i] - observed2[i]/K)2 / (observed1[i] + observed2[i])] + * where + *
        K = &sqrt;[&sum(observed2 / ∑(observed1)] + *

        + *

        This statistic can be used to perform a Chi-Square test evaluating the + * null hypothesis that both observed counts follow the same distribution.

        + *

        + * Preconditions:

          + *
        • Observed counts must be non-negative. + *
        • + *
        • Observed counts for a specific bin must not both be zero. + *
        • + *
        • Observed counts for a specific sample must not all be 0. + *
        • + *
        • The arrays observed1 and observed2 must have + * the same length and their common length must be at least 2. + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data set + * @return chiSquare test statistic + * @throws DimensionMismatchException the the length of the arrays does not match + * @throws NotPositiveException if any entries in observed1 or + * observed2 are negative + * @throws ZeroException if either all counts of observed1 or + * observed2 are zero, or if the count at some index is zero + * for both arrays + * @since 1.2 + */ + public double chiSquareDataSetsComparison(long[] observed1, long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException { + + // Make sure lengths are same + if (observed1.length < 2) { + throw new DimensionMismatchException(observed1.length, 2); + } + if (observed1.length != observed2.length) { + throw new DimensionMismatchException(observed1.length, observed2.length); + } + + // Ensure non-negative counts + MathArrays.checkNonNegative(observed1); + MathArrays.checkNonNegative(observed2); + + // Compute and compare count sums + long countSum1 = 0; + long countSum2 = 0; + boolean unequalCounts = false; + double weight = 0.0; + for (int i = 0; i < observed1.length; i++) { + countSum1 += observed1[i]; + countSum2 += observed2[i]; + } + // Ensure neither sample is uniformly 0 + if (countSum1 == 0 || countSum2 == 0) { + throw new ZeroException(); + } + // Compare and compute weight only if different + unequalCounts = countSum1 != countSum2; + if (unequalCounts) { + weight = FastMath.sqrt((double) countSum1 / (double) countSum2); + } + // Compute ChiSquare statistic + double sumSq = 0.0d; + double dev = 0.0d; + double obs1 = 0.0d; + double obs2 = 0.0d; + for (int i = 0; i < observed1.length; i++) { + if (observed1[i] == 0 && observed2[i] == 0) { + throw new ZeroException(LocalizedFormats.OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY, i); + } else { + obs1 = observed1[i]; + obs2 = observed2[i]; + if (unequalCounts) { // apply weights + dev = obs1/weight - obs2 * weight; + } else { + dev = obs1 - obs2; + } + sumSq += (dev * dev) / (obs1 + obs2); + } + } + return sumSq; + } + + /** + *

        Returns the observed significance level, or + * p-value, associated with a Chi-Square two sample test comparing + * bin frequency counts in observed1 and + * observed2. + *

        + *

        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. + *

        + *

        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. + *

        + * Preconditions:
          + *
        • Observed counts must be non-negative. + *
        • + *
        • Observed counts for a specific bin must not both be zero. + *
        • + *
        • Observed counts for a specific sample must not all be 0. + *
        • + *
        • The arrays observed1 and observed2 must + * have the same length and + * their common length must be at least 2. + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data set + * @return p-value + * @throws DimensionMismatchException the the length of the arrays does not match + * @throws NotPositiveException if any entries in observed1 or + * observed2 are negative + * @throws ZeroException if either all counts of observed1 or + * observed2 are zero, or if the count at the same index is zero + * for both arrays + * @throws MaxCountExceededException if an error occurs computing the p-value + * @since 1.2 + */ + public double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException, + MaxCountExceededException { + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = + new ChiSquaredDistribution(null, (double) observed1.length - 1); + return 1 - distribution.cumulativeProbability( + chiSquareDataSetsComparison(observed1, observed2)); + + } + + /** + *

        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 alpha. Returns true iff the null + * hypothesis can be rejected with 100 * (1 - alpha) percent confidence. + *

        + *

        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. + *

        + * Preconditions:
          + *
        • Observed counts must be non-negative. + *
        • + *
        • Observed counts for a specific bin must not both be zero. + *
        • + *
        • Observed counts for a specific sample must not all be 0. + *
        • + *
        • The arrays observed1 and observed2 must + * have the same length and their common length must be at least 2. + *
        • + *
        • 0 < alpha < 0.5 + *

        + * If any of the preconditions are not met, an + * IllegalArgumentException is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data set + * @param alpha significance level of the test + * @return true iff null hypothesis can be rejected with confidence + * 1 - alpha + * @throws DimensionMismatchException the the length of the arrays does not match + * @throws NotPositiveException if any entries in observed1 or + * observed2 are negative + * @throws ZeroException if either all counts of observed1 or + * observed2 are zero, or if the count at the same index is zero + * for both arrays + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs performing the test + * @since 1.2 + */ + public boolean chiSquareTestDataSetsComparison(final long[] observed1, + final long[] observed2, + final double alpha) + throws DimensionMismatchException, NotPositiveException, + ZeroException, OutOfRangeException, MaxCountExceededException { + + if (alpha <= 0 || + alpha > 0.5) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, + alpha, 0, 0.5); + } + return chiSquareTestDataSetsComparison(observed1, observed2) < alpha; + + } + + /** + * Checks to make sure that the input long[][] array is rectangular, + * has at least 2 rows and 2 columns, and has all non-negative entries. + * + * @param in input 2-way table to check + * @throws NullArgumentException if the array is null + * @throws DimensionMismatchException if the array is not valid + * @throws NotPositiveException if the array contains any negative entries + */ + private void checkArray(final long[][] in) + throws NullArgumentException, DimensionMismatchException, + NotPositiveException { + + if (in.length < 2) { + throw new DimensionMismatchException(in.length, 2); + } + + if (in[0].length < 2) { + throw new DimensionMismatchException(in[0].length, 2); + } + + MathArrays.checkRectangular(in); + MathArrays.checkNonNegative(in); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/GTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/GTest.java new file mode 100644 index 000000000..1278609c9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/GTest.java @@ -0,0 +1,538 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.ChiSquaredDistribution; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implements G Test + * statistics. + * + *

        This is known in statistical genetics as the McDonald-Kreitman test. + * The implementation handles both known and unknown distributions.

        + * + *

        Two samples tests can be used when the distribution is unknown a priori + * but provided by one sample, or when the hypothesis under test is that the two + * samples come from the same underlying distribution.

        + * + * @since 3.1 + */ +public class GTest { + + /** + * Computes the G statistic + * for Goodness of Fit comparing {@code observed} and {@code expected} + * frequency counts. + * + *

        This statistic can be used to perform a G test (Log-Likelihood Ratio + * Test) evaluating the null hypothesis that the observed counts follow the + * expected distribution.

        + * + *

        Preconditions:

          + *
        • Expected counts must all be positive.
        • + *
        • Observed counts must all be ≥ 0.
        • + *
        • The observed and expected arrays must have the same length and their + * common length must be at least 2.

        + * + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + *

        Note:This implementation rescales the + * {@code expected} array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @return G-Test statistic + * @throws NotPositiveException if {@code observed} has negative entries + * @throws NotStrictlyPositiveException if {@code expected} has entries that + * are not strictly positive + * @throws DimensionMismatchException if the array lengths do not match or + * are less than 2. + */ + public double g(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException { + + if (expected.length < 2) { + throw new DimensionMismatchException(expected.length, 2); + } + if (expected.length != observed.length) { + throw new DimensionMismatchException(expected.length, observed.length); + } + MathArrays.checkPositive(expected); + MathArrays.checkNonNegative(observed); + + double sumExpected = 0d; + double sumObserved = 0d; + for (int i = 0; i < observed.length; i++) { + sumExpected += expected[i]; + sumObserved += observed[i]; + } + double ratio = 1d; + boolean rescale = false; + if (FastMath.abs(sumExpected - sumObserved) > 10E-6) { + ratio = sumObserved / sumExpected; + rescale = true; + } + double sum = 0d; + for (int i = 0; i < observed.length; i++) { + final double dev = rescale ? + FastMath.log((double) observed[i] / (ratio * expected[i])) : + FastMath.log((double) observed[i] / expected[i]); + sum += ((double) observed[i]) * dev; + } + return 2d * sum; + } + + /** + * Returns the observed significance level, or p-value, + * associated with a G-Test for goodness of fit comparing the + * {@code observed} frequency counts to those in the {@code expected} array. + * + *

        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.

        + * + *

        The probability returned is the tail probability beyond + * {@link #g(double[], long[]) g(expected, observed)} + * in the ChiSquare distribution with degrees of freedom one less than the + * common length of {@code expected} and {@code observed}.

        + * + *

        Preconditions:

          + *
        • Expected counts must all be positive.
        • + *
        • Observed counts must all be ≥ 0.
        • + *
        • The observed and expected arrays must have the + * same length and their common length must be at least 2.
        • + *

        + * + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + *

        Note:This implementation rescales the + * {@code expected} array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @return p-value + * @throws NotPositiveException if {@code observed} has negative entries + * @throws NotStrictlyPositiveException if {@code expected} has entries that + * are not strictly positive + * @throws DimensionMismatchException if the array lengths do not match or + * are less than 2. + * @throws MaxCountExceededException if an error occurs computing the + * p-value. + */ + public double gTest(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = + new ChiSquaredDistribution(null, expected.length - 1.0); + return 1.0 - distribution.cumulativeProbability(g(expected, observed)); + } + + /** + * Returns the intrinsic (Hardy-Weinberg proportions) p-Value, as described + * in p64-69 of McDonald, J.H. 2009. Handbook of Biological Statistics + * (2nd ed.). Sparky House Publishing, Baltimore, Maryland. + * + *

        The probability returned is the tail probability beyond + * {@link #g(double[], long[]) g(expected, observed)} + * in the ChiSquare distribution with degrees of freedom two less than the + * common length of {@code expected} and {@code observed}.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @return p-value + * @throws NotPositiveException if {@code observed} has negative entries + * @throws NotStrictlyPositiveException {@code expected} has entries that are + * not strictly positive + * @throws DimensionMismatchException if the array lengths do not match or + * are less than 2. + * @throws MaxCountExceededException if an error occurs computing the + * p-value. + */ + public double gTestIntrinsic(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = + new ChiSquaredDistribution(null, expected.length - 2.0); + return 1.0 - distribution.cumulativeProbability(g(expected, observed)); + } + + /** + * Performs a G-Test (Log-Likelihood Ratio Test) for goodness of fit + * evaluating the null hypothesis that the observed counts conform to the + * frequency distribution described by the expected counts, with + * significance level {@code alpha}. Returns true iff the null + * hypothesis can be rejected with {@code 100 * (1 - alpha)} percent confidence. + * + *

        Example:
        To test the hypothesis that + * {@code observed} follows {@code expected} at the 99% level, + * use

        + * {@code gTest(expected, observed, 0.01)}

        + * + *

        Returns true iff {@link #gTest(double[], long[]) + * gTestGoodnessOfFitPValue(expected, observed)} < alpha

        + * + *

        Preconditions:

          + *
        • Expected counts must all be positive.
        • + *
        • Observed counts must all be ≥ 0.
        • + *
        • The observed and expected arrays must have the same length and their + * common length must be at least 2. + *
        • {@code 0 < alpha < 0.5}

        + * + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + *

        Note:This implementation rescales the + * {@code expected} array if necessary to ensure that the sum of the + * expected and observed counts are equal.

        + * + * @param observed array of observed frequency counts + * @param expected array of expected frequency counts + * @param alpha significance level of the test + * @return true iff null hypothesis can be rejected with confidence 1 - + * alpha + * @throws NotPositiveException if {@code observed} has negative entries + * @throws NotStrictlyPositiveException if {@code expected} has entries that + * are not strictly positive + * @throws DimensionMismatchException if the array lengths do not match or + * are less than 2. + * @throws MaxCountExceededException if an error occurs computing the + * p-value. + * @throws OutOfRangeException if alpha is not strictly greater than zero + * and less than or equal to 0.5 + */ + public boolean gTest(final double[] expected, final long[] observed, + final double alpha) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, OutOfRangeException, MaxCountExceededException { + + if ((alpha <= 0) || (alpha > 0.5)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, + alpha, 0, 0.5); + } + return gTest(expected, observed) < alpha; + } + + /** + * Calculates the Shannon + * entropy for 2 Dimensional Matrix. The value returned is the entropy + * of the vector formed by concatenating the rows (or columns) of {@code k} + * to form a vector. See {@link #entropy(long[])}. + * + * @param k 2 Dimensional Matrix of long values (for ex. the counts of a + * trials) + * @return Shannon Entropy of the given Matrix + * + */ + private double entropy(final long[][] k) { + double h = 0d; + double sum_k = 0d; + for (int i = 0; i < k.length; i++) { + for (int j = 0; j < k[i].length; j++) { + sum_k += (double) k[i][j]; + } + } + for (int i = 0; i < k.length; i++) { + for (int j = 0; j < k[i].length; j++) { + if (k[i][j] != 0) { + final double p_ij = (double) k[i][j] / sum_k; + h += p_ij * FastMath.log(p_ij); + } + } + } + return -h; + } + + /** + * Calculates the + * Shannon entropy for a vector. The values of {@code k} are taken to be + * incidence counts of the values of a random variable. What is returned is
        + * ∑pilog(pi
        + * where pi = k[i] / (sum of elements in k) + * + * @param k Vector (for ex. Row Sums of a trials) + * @return Shannon Entropy of the given Vector + * + */ + private double entropy(final long[] k) { + double h = 0d; + double sum_k = 0d; + for (int i = 0; i < k.length; i++) { + sum_k += (double) k[i]; + } + for (int i = 0; i < k.length; i++) { + if (k[i] != 0) { + final double p_i = (double) k[i] / sum_k; + h += p_i * FastMath.log(p_i); + } + } + return -h; + } + + /** + *

        Computes a G (Log-Likelihood Ratio) two sample test statistic for + * independence comparing frequency counts in + * {@code observed1} and {@code observed2}. The sums of frequency + * counts in the two samples are not required to be the same. The formula + * used to compute the test statistic is

        + * + *

        {@code 2 * totalSum * [H(rowSums) + H(colSums) - H(k)]}

        + * + *

        where {@code H} is the + * + * Shannon Entropy of the random variable formed by viewing the elements + * of the argument array as incidence counts;
        + * {@code k} is a matrix with rows {@code [observed1, observed2]};
        + * {@code rowSums, colSums} are the row/col sums of {@code k};
        + * and {@code totalSum} is the overall sum of all entries in {@code k}.

        + * + *

        This statistic can be used to perform a G test evaluating the null + * hypothesis that both observed counts are independent

        + * + *

        Preconditions:

          + *
        • Observed counts must be non-negative.
        • + *
        • Observed counts for a specific bin must not both be zero.
        • + *
        • Observed counts for a specific sample must not all be 0.
        • + *
        • The arrays {@code observed1} and {@code observed2} must have + * the same length and their common length must be at least 2.

        + * + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data + * set + * @return G-Test statistic + * @throws DimensionMismatchException the the lengths of the arrays do not + * match or their common length is less than 2 + * @throws NotPositiveException if any entry in {@code observed1} or + * {@code observed2} is negative + * @throws ZeroException if either all counts of + * {@code observed1} or {@code observed2} are zero, or if the count + * at the same index is zero for both arrays. + */ + public double gDataSetsComparison(final long[] observed1, final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException { + + // Make sure lengths are same + if (observed1.length < 2) { + throw new DimensionMismatchException(observed1.length, 2); + } + if (observed1.length != observed2.length) { + throw new DimensionMismatchException(observed1.length, observed2.length); + } + + // Ensure non-negative counts + MathArrays.checkNonNegative(observed1); + MathArrays.checkNonNegative(observed2); + + // Compute and compare count sums + long countSum1 = 0; + long countSum2 = 0; + + // Compute and compare count sums + final long[] collSums = new long[observed1.length]; + final long[][] k = new long[2][observed1.length]; + + for (int i = 0; i < observed1.length; i++) { + if (observed1[i] == 0 && observed2[i] == 0) { + throw new ZeroException(LocalizedFormats.OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY, i); + } else { + countSum1 += observed1[i]; + countSum2 += observed2[i]; + collSums[i] = observed1[i] + observed2[i]; + k[0][i] = observed1[i]; + k[1][i] = observed2[i]; + } + } + // Ensure neither sample is uniformly 0 + if (countSum1 == 0 || countSum2 == 0) { + throw new ZeroException(); + } + final long[] rowSums = {countSum1, countSum2}; + final double sum = (double) countSum1 + (double) countSum2; + return 2 * sum * (entropy(rowSums) + entropy(collSums) - entropy(k)); + } + + /** + * Calculates the root log-likelihood ratio for 2 state Datasets. See + * {@link #gDataSetsComparison(long[], long[] )}. + * + *

        Given two events A and B, let k11 be the number of times both events + * occur, k12 the incidence of B without A, k21 the count of A without B, + * and k22 the number of times neither A nor B occurs. What is returned + * by this method is

        + * + *

        {@code (sgn) sqrt(gValueDataSetsComparison({k11, k12}, {k21, k22})}

        + * + *

        where {@code sgn} is -1 if {@code k11 / (k11 + k12) < k21 / (k21 + k22))};
        + * 1 otherwise.

        + * + *

        Signed root LLR has two advantages over the basic LLR: a) it is positive + * where k11 is bigger than expected, negative where it is lower b) if there is + * no difference it is asymptotically normally distributed. This allows one + * to talk about "number of standard deviations" which is a more common frame + * of reference than the chi^2 distribution.

        + * + * @param k11 number of times the two events occurred together (AB) + * @param k12 number of times the second event occurred WITHOUT the + * first event (notA,B) + * @param k21 number of times the first event occurred WITHOUT the + * second event (A, notB) + * @param k22 number of times something else occurred (i.e. was neither + * of these events (notA, notB) + * @return root log-likelihood ratio + * + */ + public double rootLogLikelihoodRatio(final long k11, long k12, + final long k21, final long k22) { + final double llr = gDataSetsComparison( + new long[]{k11, k12}, new long[]{k21, k22}); + double sqrt = FastMath.sqrt(llr); + if ((double) k11 / (k11 + k12) < (double) k21 / (k21 + k22)) { + sqrt = -sqrt; + } + return sqrt; + } + + /** + *

        Returns the observed significance level, or + * p-value, associated with a G-Value (Log-Likelihood Ratio) for two + * sample test comparing bin frequency counts in {@code observed1} and + * {@code observed2}.

        + * + *

        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.

        + * + *

        See {@link #gTest(double[], long[])} for details + * on how the p-value is computed. The degrees of of freedom used to + * perform the test is one less than the common length of the input observed + * count arrays.

        + * + *

        Preconditions: + *

        • Observed counts must be non-negative.
        • + *
        • Observed counts for a specific bin must not both be zero.
        • + *
        • Observed counts for a specific sample must not all be 0.
        • + *
        • The arrays {@code observed1} and {@code observed2} must + * have the same length and their common length must be at least 2.
        • + *

        + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data + * set + * @return p-value + * @throws DimensionMismatchException the the length of the arrays does not + * match or their common length is less than 2 + * @throws NotPositiveException if any of the entries in {@code observed1} or + * {@code observed2} are negative + * @throws ZeroException if either all counts of {@code observed1} or + * {@code observed2} are zero, or if the count at some index is + * zero for both arrays + * @throws MaxCountExceededException if an error occurs computing the + * p-value. + */ + public double gTestDataSetsComparison(final long[] observed1, + final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException, + MaxCountExceededException { + + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final ChiSquaredDistribution distribution = + new ChiSquaredDistribution(null, (double) observed1.length - 1); + return 1 - distribution.cumulativeProbability( + gDataSetsComparison(observed1, observed2)); + } + + /** + *

        Performs a G-Test (Log-Likelihood Ratio Test) comparing two binned + * data sets. The test evaluates the null hypothesis that the two lists + * of observed counts conform to the same frequency distribution, with + * significance level {@code alpha}. Returns true iff the null + * hypothesis can be rejected with 100 * (1 - alpha) percent confidence. + *

        + *

        See {@link #gDataSetsComparison(long[], long[])} for details + * on the formula used to compute the G (LLR) statistic used in the test and + * {@link #gTest(double[], long[])} for information on how + * the observed significance level is computed. The degrees of of freedom used + * to perform the test is one less than the common length of the input observed + * count arrays.

        + * + * Preconditions:
          + *
        • Observed counts must be non-negative.
        • + *
        • Observed counts for a specific bin must not both be zero.
        • + *
        • Observed counts for a specific sample must not all be 0.
        • + *
        • The arrays {@code observed1} and {@code observed2} must + * have the same length and their common length must be at least 2.
        • + *
        • {@code 0 < alpha < 0.5}

        + * + *

        If any of the preconditions are not met, a + * {@code MathIllegalArgumentException} is thrown.

        + * + * @param observed1 array of observed frequency counts of the first data set + * @param observed2 array of observed frequency counts of the second data + * set + * @param alpha significance level of the test + * @return true iff null hypothesis can be rejected with confidence 1 - + * alpha + * @throws DimensionMismatchException the the length of the arrays does not + * match + * @throws NotPositiveException if any of the entries in {@code observed1} or + * {@code observed2} are negative + * @throws ZeroException if either all counts of {@code observed1} or + * {@code observed2} are zero, or if the count at some index is + * zero for both arrays + * @throws OutOfRangeException if {@code alpha} is not in the range + * (0, 0.5] + * @throws MaxCountExceededException if an error occurs performing the test + */ + public boolean gTestDataSetsComparison( + final long[] observed1, + final long[] observed2, + final double alpha) + throws DimensionMismatchException, NotPositiveException, + ZeroException, OutOfRangeException, MaxCountExceededException { + + if (alpha <= 0 || alpha > 0.5) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5); + } + return gTestDataSetsComparison(observed1, observed2) < alpha; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java new file mode 100644 index 000000000..e4fe37493 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java @@ -0,0 +1,1270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.inference; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashSet; + +import com.fr.third.org.apache.commons.math3.distribution.EnumeratedRealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.RealDistribution; +import com.fr.third.org.apache.commons.math3.distribution.UniformRealDistribution; +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.TooManyIterationsException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.fraction.BigFraction; +import com.fr.third.org.apache.commons.math3.fraction.BigFractionField; +import com.fr.third.org.apache.commons.math3.fraction.FractionConversionException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.FieldMatrix; +import com.fr.third.org.apache.commons.math3.linear.MatrixUtils; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.random.JDKRandomGenerator; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.util.CombinatoricsUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implementation of the + * Kolmogorov-Smirnov (K-S) test for equality of continuous distributions. + *

        + * The K-S test uses a statistic based on the maximum deviation of the empirical distribution of + * sample data points from the distribution expected under the null hypothesis. For one-sample tests + * evaluating the null hypothesis that a set of sample data points follow a given distribution, the + * test statistic is \(D_n=\sup_x |F_n(x)-F(x)|\), where \(F\) is the expected distribution and + * \(F_n\) is the empirical distribution of the \(n\) sample data points. The distribution of + * \(D_n\) is estimated using a method based on [1] with certain quick decisions for extreme values + * given in [2]. + *

        + *

        + * Two-sample tests are also supported, evaluating the null hypothesis that the two samples + * {@code x} and {@code y} come from the same underlying distribution. In this case, the test + * statistic is \(D_{n,m}=\sup_t | F_n(t)-F_m(t)|\) where \(n\) is the length of {@code x}, \(m\) is + * the length of {@code y}, \(F_n\) is the empirical distribution that puts mass \(1/n\) at each of + * the values in {@code x} and \(F_m\) is the empirical distribution of the {@code y} values. The + * default 2-sample test method, {@link #kolmogorovSmirnovTest(double[], double[])} works as + * follows: + *

          + *
        • For small samples (where the product of the sample sizes is less than + * {@value #LARGE_SAMPLE_PRODUCT}), the method presented in [4] is used to compute the + * exact p-value for the 2-sample test.
        • + *
        • When the product of the sample sizes exceeds {@value #LARGE_SAMPLE_PRODUCT}, the asymptotic + * distribution of \(D_{n,m}\) is used. See {@link #approximateP(double, int, int)} for details on + * the approximation.
        • + *

        + * If the product of the sample sizes is less than {@value #LARGE_SAMPLE_PRODUCT} and the sample + * data contains ties, random jitter is added to the sample data to break ties before applying + * the algorithm above. Alternatively, the {@link #bootstrap(double[], double[], int, boolean)} + * method, modeled after ks.boot + * in the R Matching package [3], can be used if ties are known to be present in the data. + *

        + *

        + * In the two-sample case, \(D_{n,m}\) has a discrete distribution. This makes the p-value + * associated with the null hypothesis \(H_0 : D_{n,m} \ge d \) differ from \(H_0 : D_{n,m} > d \) + * by the mass of the observed value \(d\). To distinguish these, the two-sample tests use a boolean + * {@code strict} parameter. This parameter is ignored for large samples. + *

        + *

        + * The methods used by the 2-sample default implementation are also exposed directly: + *

          + *
        • {@link #exactP(double, int, int, boolean)} computes exact 2-sample p-values
        • + *
        • {@link #approximateP(double, int, int)} uses the asymptotic distribution The {@code boolean} + * arguments in the first two methods allow the probability used to estimate the p-value to be + * expressed using strict or non-strict inequality. See + * {@link #kolmogorovSmirnovTest(double[], double[], boolean)}.
        • + *
        + *

        + *

        + * References: + *

        + *
        + * Note that [1] contains an error in computing h, refer to MATH-437 for details. + *

        + * + * @since 3.3 + */ +public class KolmogorovSmirnovTest { + + /** + * Bound on the number of partial sums in {@link #ksSum(double, double, int)} + */ + protected static final int MAXIMUM_PARTIAL_SUM_COUNT = 100000; + + /** Convergence criterion for {@link #ksSum(double, double, int)} */ + protected static final double KS_SUM_CAUCHY_CRITERION = 1E-20; + + /** Convergence criterion for the sums in #pelzGood(double, double, int)} */ + protected static final double PG_SUM_RELATIVE_ERROR = 1.0e-10; + + /** No longer used. */ + @Deprecated + protected static final int SMALL_SAMPLE_PRODUCT = 200; + + /** + * When product of sample sizes exceeds this value, 2-sample K-S test uses asymptotic + * distribution to compute the p-value. + */ + protected static final int LARGE_SAMPLE_PRODUCT = 10000; + + /** Default number of iterations used by {@link #monteCarloP(double, int, int, boolean, int)}. + * Deprecated as of version 3.6, as this method is no longer needed. */ + @Deprecated + protected static final int MONTE_CARLO_ITERATIONS = 1000000; + + /** Random data generator used by {@link #monteCarloP(double, int, int, boolean, int)} */ + private final RandomGenerator rng; + + /** + * Construct a KolmogorovSmirnovTest instance with a default random data generator. + */ + public KolmogorovSmirnovTest() { + rng = new Well19937c(); + } + + /** + * Construct a KolmogorovSmirnovTest with the provided random data generator. + * The #monteCarloP(double, int, int, boolean, int) that uses the generator supplied to this + * constructor is deprecated as of version 3.6. + * + * @param rng random data generator used by {@link #monteCarloP(double, int, int, boolean, int)} + */ + @Deprecated + public KolmogorovSmirnovTest(RandomGenerator rng) { + this.rng = rng; + } + + /** + * Computes the p-value, or observed significance level, of a one-sample Kolmogorov-Smirnov test + * evaluating the null hypothesis that {@code data} conforms to {@code distribution}. If + * {@code exact} is true, the distribution used to compute the p-value is computed using + * extended precision. See {@link #cdfExact(double, int)}. + * + * @param distribution reference distribution + * @param data sample being being evaluated + * @param exact whether or not to force exact computation of the p-value + * @return the p-value associated with the null hypothesis that {@code data} is a sample from + * {@code distribution} + * @throws InsufficientDataException if {@code data} does not have length at least 2 + * @throws NullArgumentException if {@code data} is null + */ + public double kolmogorovSmirnovTest(RealDistribution distribution, double[] data, boolean exact) { + return 1d - cdf(kolmogorovSmirnovStatistic(distribution, data), data.length, exact); + } + + /** + * Computes the one-sample Kolmogorov-Smirnov test statistic, \(D_n=\sup_x |F_n(x)-F(x)|\) where + * \(F\) is the distribution (cdf) function associated with {@code distribution}, \(n\) is the + * length of {@code data} and \(F_n\) is the empirical distribution that puts mass \(1/n\) at + * each of the values in {@code data}. + * + * @param distribution reference distribution + * @param data sample being evaluated + * @return Kolmogorov-Smirnov statistic \(D_n\) + * @throws InsufficientDataException if {@code data} does not have length at least 2 + * @throws NullArgumentException if {@code data} is null + */ + public double kolmogorovSmirnovStatistic(RealDistribution distribution, double[] data) { + checkArray(data); + final int n = data.length; + final double nd = n; + final double[] dataCopy = new double[n]; + System.arraycopy(data, 0, dataCopy, 0, n); + Arrays.sort(dataCopy); + double d = 0d; + for (int i = 1; i <= n; i++) { + final double yi = distribution.cumulativeProbability(dataCopy[i - 1]); + final double currD = FastMath.max(yi - (i - 1) / nd, i / nd - yi); + if (currD > d) { + d = currD; + } + } + return d; + } + + /** + * Computes the p-value, or observed significance level, of a two-sample Kolmogorov-Smirnov test + * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same + * probability distribution. Specifically, what is returned is an estimate of the probability + * that the {@link #kolmogorovSmirnovStatistic(double[], double[])} associated with a randomly + * selected partition of the combined sample into subsamples of sizes {@code x.length} and + * {@code y.length} will strictly exceed (if {@code strict} is {@code true}) or be at least as + * large as {@code strict = false}) as {@code kolmogorovSmirnovStatistic(x, y)}. + *
          + *
        • For small samples (where the product of the sample sizes is less than + * {@value #LARGE_SAMPLE_PRODUCT}), the exact p-value is computed using the method presented + * in [4], implemented in {@link #exactP(double, int, int, boolean)}.
        • + *
        • When the product of the sample sizes exceeds {@value #LARGE_SAMPLE_PRODUCT}, the + * asymptotic distribution of \(D_{n,m}\) is used. See {@link #approximateP(double, int, int)} + * for details on the approximation.
        • + *

        + * If {@code x.length * y.length} < {@value #LARGE_SAMPLE_PRODUCT} and the combined set of values in + * {@code x} and {@code y} contains ties, random jitter is added to {@code x} and {@code y} to + * break ties before computing \(D_{n,m}\) and the p-value. The jitter is uniformly distributed + * on (-minDelta / 2, minDelta / 2) where minDelta is the smallest pairwise difference between + * values in the combined sample.

        + *

        + * If ties are known to be present in the data, {@link #bootstrap(double[], double[], int, boolean)} + * may be used as an alternative method for estimating the p-value.

        + * + * @param x first sample dataset + * @param y second sample dataset + * @param strict whether or not the probability to compute is expressed as a strict inequality + * (ignored for large samples) + * @return p-value associated with the null hypothesis that {@code x} and {@code y} represent + * samples from the same distribution + * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at + * least 2 + * @throws NullArgumentException if either {@code x} or {@code y} is null + * @see #bootstrap(double[], double[], int, boolean) + */ + public double kolmogorovSmirnovTest(double[] x, double[] y, boolean strict) { + final long lengthProduct = (long) x.length * y.length; + double[] xa = null; + double[] ya = null; + if (lengthProduct < LARGE_SAMPLE_PRODUCT && hasTies(x,y)) { + xa = MathArrays.copyOf(x); + ya = MathArrays.copyOf(y); + fixTies(xa, ya); + } else { + xa = x; + ya = y; + } + if (lengthProduct < LARGE_SAMPLE_PRODUCT) { + return exactP(kolmogorovSmirnovStatistic(xa, ya), x.length, y.length, strict); + } + return approximateP(kolmogorovSmirnovStatistic(x, y), x.length, y.length); + } + + /** + * Computes the p-value, or observed significance level, of a two-sample Kolmogorov-Smirnov test + * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same + * probability distribution. Assumes the strict form of the inequality used to compute the + * p-value. See {@link #kolmogorovSmirnovTest(RealDistribution, double[], boolean)}. + * + * @param x first sample dataset + * @param y second sample dataset + * @return p-value associated with the null hypothesis that {@code x} and {@code y} represent + * samples from the same distribution + * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at + * least 2 + * @throws NullArgumentException if either {@code x} or {@code y} is null + */ + public double kolmogorovSmirnovTest(double[] x, double[] y) { + return kolmogorovSmirnovTest(x, y, true); + } + + /** + * Computes the two-sample Kolmogorov-Smirnov test statistic, \(D_{n,m}=\sup_x |F_n(x)-F_m(x)|\) + * where \(n\) is the length of {@code x}, \(m\) is the length of {@code y}, \(F_n\) is the + * empirical distribution that puts mass \(1/n\) at each of the values in {@code x} and \(F_m\) + * is the empirical distribution of the {@code y} values. + * + * @param x first sample + * @param y second sample + * @return test statistic \(D_{n,m}\) used to evaluate the null hypothesis that {@code x} and + * {@code y} represent samples from the same underlying distribution + * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at + * least 2 + * @throws NullArgumentException if either {@code x} or {@code y} is null + */ + public double kolmogorovSmirnovStatistic(double[] x, double[] y) { + return integralKolmogorovSmirnovStatistic(x, y)/((double)(x.length * (long)y.length)); + } + + /** + * Computes the two-sample Kolmogorov-Smirnov test statistic, \(D_{n,m}=\sup_x |F_n(x)-F_m(x)|\) + * where \(n\) is the length of {@code x}, \(m\) is the length of {@code y}, \(F_n\) is the + * empirical distribution that puts mass \(1/n\) at each of the values in {@code x} and \(F_m\) + * is the empirical distribution of the {@code y} values. Finally \(n m D_{n,m}\) is returned + * as long value. + * + * @param x first sample + * @param y second sample + * @return test statistic \(n m D_{n,m}\) used to evaluate the null hypothesis that {@code x} and + * {@code y} represent samples from the same underlying distribution + * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at + * least 2 + * @throws NullArgumentException if either {@code x} or {@code y} is null + */ + private long integralKolmogorovSmirnovStatistic(double[] x, double[] y) { + checkArray(x); + checkArray(y); + // Copy and sort the sample arrays + final double[] sx = MathArrays.copyOf(x); + final double[] sy = MathArrays.copyOf(y); + Arrays.sort(sx); + Arrays.sort(sy); + final int n = sx.length; + final int m = sy.length; + + int rankX = 0; + int rankY = 0; + long curD = 0l; + + // Find the max difference between cdf_x and cdf_y + long supD = 0l; + do { + double z = Double.compare(sx[rankX], sy[rankY]) <= 0 ? sx[rankX] : sy[rankY]; + while(rankX < n && Double.compare(sx[rankX], z) == 0) { + rankX += 1; + curD += m; + } + while(rankY < m && Double.compare(sy[rankY], z) == 0) { + rankY += 1; + curD -= n; + } + if (curD > supD) { + supD = curD; + } + else if (-curD > supD) { + supD = -curD; + } + } while(rankX < n && rankY < m); + return supD; + } + + /** + * Computes the p-value, or observed significance level, of a one-sample Kolmogorov-Smirnov test + * evaluating the null hypothesis that {@code data} conforms to {@code distribution}. + * + * @param distribution reference distribution + * @param data sample being being evaluated + * @return the p-value associated with the null hypothesis that {@code data} is a sample from + * {@code distribution} + * @throws InsufficientDataException if {@code data} does not have length at least 2 + * @throws NullArgumentException if {@code data} is null + */ + public double kolmogorovSmirnovTest(RealDistribution distribution, double[] data) { + return kolmogorovSmirnovTest(distribution, data, false); + } + + /** + * Performs a Kolmogorov-Smirnov + * test evaluating the null hypothesis that {@code data} conforms to {@code distribution}. + * + * @param distribution reference distribution + * @param data sample being being evaluated + * @param alpha significance level of the test + * @return true iff the null hypothesis that {@code data} is a sample from {@code distribution} + * can be rejected with confidence 1 - {@code alpha} + * @throws InsufficientDataException if {@code data} does not have length at least 2 + * @throws NullArgumentException if {@code data} is null + */ + public boolean kolmogorovSmirnovTest(RealDistribution distribution, double[] data, double alpha) { + if ((alpha <= 0) || (alpha > 0.5)) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5); + } + return kolmogorovSmirnovTest(distribution, data) < alpha; + } + + /** + * Estimates the p-value of a two-sample + * Kolmogorov-Smirnov test + * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same + * probability distribution. This method estimates the p-value by repeatedly sampling sets of size + * {@code x.length} and {@code y.length} from the empirical distribution of the combined sample. + * When {@code strict} is true, this is equivalent to the algorithm implemented in the R function + * {@code ks.boot}, described in
        +     * Jasjeet S. Sekhon. 2011. 'Multivariate and Propensity Score Matching
        +     * Software with Automated Balance Optimization: The Matching package for R.'
        +     * Journal of Statistical Software, 42(7): 1-52.
        +     * 
        + * @param x first sample + * @param y second sample + * @param iterations number of bootstrap resampling iterations + * @param strict whether or not the null hypothesis is expressed as a strict inequality + * @return estimated p-value + */ + public double bootstrap(double[] x, double[] y, int iterations, boolean strict) { + final int xLength = x.length; + final int yLength = y.length; + final double[] combined = new double[xLength + yLength]; + System.arraycopy(x, 0, combined, 0, xLength); + System.arraycopy(y, 0, combined, xLength, yLength); + final EnumeratedRealDistribution dist = new EnumeratedRealDistribution(rng, combined); + final long d = integralKolmogorovSmirnovStatistic(x, y); + int greaterCount = 0; + int equalCount = 0; + double[] curX; + double[] curY; + long curD; + for (int i = 0; i < iterations; i++) { + curX = dist.sample(xLength); + curY = dist.sample(yLength); + curD = integralKolmogorovSmirnovStatistic(curX, curY); + if (curD > d) { + greaterCount++; + } else if (curD == d) { + equalCount++; + } + } + return strict ? greaterCount / (double) iterations : + (greaterCount + equalCount) / (double) iterations; + } + + /** + * Computes {@code bootstrap(x, y, iterations, true)}. + * This is equivalent to ks.boot(x,y, nboots=iterations) using the R Matching + * package function. See #bootstrap(double[], double[], int, boolean). + * + * @param x first sample + * @param y second sample + * @param iterations number of bootstrap resampling iterations + * @return estimated p-value + */ + public double bootstrap(double[] x, double[] y, int iterations) { + return bootstrap(x, y, iterations, true); + } + + /** + * Calculates \(P(D_n < d)\) using the method described in [1] with quick decisions for extreme + * values given in [2] (see above). The result is not exact as with + * {@link #cdfExact(double, int)} because calculations are based on + * {@code double} rather than {@link BigFraction}. + * + * @param d statistic + * @param n sample size + * @return \(P(D_n < d)\) + * @throws MathArithmeticException if algorithm fails to convert {@code h} to a + * {@link BigFraction} in expressing {@code d} as \((k + * - h) / m\) for integer {@code k, m} and \(0 \le h < 1\) + */ + public double cdf(double d, int n) + throws MathArithmeticException { + return cdf(d, n, false); + } + + /** + * Calculates {@code P(D_n < d)}. The result is exact in the sense that BigFraction/BigReal is + * used everywhere at the expense of very slow execution time. Almost never choose this in real + * applications unless you are very sure; this is almost solely for verification purposes. + * Normally, you would choose {@link #cdf(double, int)}. See the class + * javadoc for definitions and algorithm description. + * + * @param d statistic + * @param n sample size + * @return \(P(D_n < d)\) + * @throws MathArithmeticException if the algorithm fails to convert {@code h} to a + * {@link BigFraction} in expressing {@code d} as \((k + * - h) / m\) for integer {@code k, m} and \(0 \le h < 1\) + */ + public double cdfExact(double d, int n) + throws MathArithmeticException { + return cdf(d, n, true); + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] with quick decisions for extreme + * values given in [2] (see above). + * + * @param d statistic + * @param n sample size + * @param exact whether the probability should be calculated exact using + * {@link BigFraction} everywhere at the expense of + * very slow execution time, or if {@code double} should be used convenient places to + * gain speed. Almost never choose {@code true} in real applications unless you are very + * sure; {@code true} is almost solely for verification purposes. + * @return \(P(D_n < d)\) + * @throws MathArithmeticException if algorithm fails to convert {@code h} to a + * {@link BigFraction} in expressing {@code d} as \((k + * - h) / m\) for integer {@code k, m} and \(0 \le h < 1\). + */ + public double cdf(double d, int n, boolean exact) + throws MathArithmeticException { + + final double ninv = 1 / ((double) n); + final double ninvhalf = 0.5 * ninv; + + if (d <= ninvhalf) { + return 0; + } else if (ninvhalf < d && d <= ninv) { + double res = 1; + final double f = 2 * d - ninv; + // n! f^n = n*f * (n-1)*f * ... * 1*x + for (int i = 1; i <= n; ++i) { + res *= i * f; + } + return res; + } else if (1 - ninv <= d && d < 1) { + return 1 - 2 * Math.pow(1 - d, n); + } else if (1 <= d) { + return 1; + } + if (exact) { + return exactK(d, n); + } + if (n <= 140) { + return roundedK(d, n); + } + return pelzGood(d, n); + } + + /** + * Calculates the exact value of {@code P(D_n < d)} using the method described in [1] (reference + * in class javadoc above) and {@link BigFraction} (see + * above). + * + * @param d statistic + * @param n sample size + * @return the two-sided probability of \(P(D_n < d)\) + * @throws MathArithmeticException if algorithm fails to convert {@code h} to a + * {@link BigFraction} in expressing {@code d} as \((k + * - h) / m\) for integer {@code k, m} and \(0 \le h < 1\). + */ + private double exactK(double d, int n) + throws MathArithmeticException { + + final int k = (int) Math.ceil(n * d); + + final FieldMatrix H = this.createExactH(d, n); + final FieldMatrix Hpower = H.power(n); + + BigFraction pFrac = Hpower.getEntry(k - 1, k - 1); + + for (int i = 1; i <= n; ++i) { + pFrac = pFrac.multiply(i).divide(n); + } + + /* + * BigFraction.doubleValue converts numerator to double and the denominator to double and + * divides afterwards. That gives NaN quite easy. This does not (scale is the number of + * digits): + */ + return pFrac.bigDecimalValue(20, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** + * Calculates {@code P(D_n < d)} using method described in [1] and doubles (see above). + * + * @param d statistic + * @param n sample size + * @return \(P(D_n < d)\) + */ + private double roundedK(double d, int n) { + + final int k = (int) Math.ceil(n * d); + final RealMatrix H = this.createRoundedH(d, n); + final RealMatrix Hpower = H.power(n); + + double pFrac = Hpower.getEntry(k - 1, k - 1); + for (int i = 1; i <= n; ++i) { + pFrac *= (double) i / (double) n; + } + + return pFrac; + } + + /** + * Computes the Pelz-Good approximation for \(P(D_n < d)\) as described in [2] in the class javadoc. + * + * @param d value of d-statistic (x in [2]) + * @param n sample size + * @return \(P(D_n < d)\) + * @since 3.4 + */ + public double pelzGood(double d, int n) { + // Change the variable since approximation is for the distribution evaluated at d / sqrt(n) + final double sqrtN = FastMath.sqrt(n); + final double z = d * sqrtN; + final double z2 = d * d * n; + final double z4 = z2 * z2; + final double z6 = z4 * z2; + final double z8 = z4 * z4; + + // Eventual return value + double ret = 0; + + // Compute K_0(z) + double sum = 0; + double increment = 0; + double kTerm = 0; + double z2Term = MathUtils.PI_SQUARED / (8 * z2); + int k = 1; + for (; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm = 2 * k - 1; + increment = FastMath.exp(-z2Term * kTerm * kTerm); + sum += increment; + if (increment <= PG_SUM_RELATIVE_ERROR * sum) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + ret = sum * FastMath.sqrt(2 * FastMath.PI) / z; + + // K_1(z) + // Sum is -inf to inf, but k term is always (k + 1/2) ^ 2, so really have + // twice the sum from k = 0 to inf (k = -1 is same as 0, -2 same as 1, ...) + final double twoZ2 = 2 * z2; + sum = 0; + kTerm = 0; + double kTerm2 = 0; + for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm = k + 0.5; + kTerm2 = kTerm * kTerm; + increment = (MathUtils.PI_SQUARED * kTerm2 - z2) * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2); + sum += increment; + if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + final double sqrtHalfPi = FastMath.sqrt(FastMath.PI / 2); + // Instead of doubling sum, divide by 3 instead of 6 + ret += sum * sqrtHalfPi / (3 * z4 * sqrtN); + + // K_2(z) + // Same drill as K_1, but with two doubly infinite sums, all k terms are even powers. + final double z4Term = 2 * z4; + final double z6Term = 6 * z6; + z2Term = 5 * z2; + final double pi4 = MathUtils.PI_SQUARED * MathUtils.PI_SQUARED; + sum = 0; + kTerm = 0; + kTerm2 = 0; + for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm = k + 0.5; + kTerm2 = kTerm * kTerm; + increment = (z6Term + z4Term + MathUtils.PI_SQUARED * (z4Term - z2Term) * kTerm2 + + pi4 * (1 - twoZ2) * kTerm2 * kTerm2) * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2); + sum += increment; + if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + double sum2 = 0; + kTerm2 = 0; + for (k = 1; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm2 = k * k; + increment = MathUtils.PI_SQUARED * kTerm2 * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2); + sum2 += increment; + if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum2)) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + // Again, adjust coefficients instead of doubling sum, sum2 + ret += (sqrtHalfPi / n) * (sum / (36 * z2 * z2 * z2 * z) - sum2 / (18 * z2 * z)); + + // K_3(z) One more time with feeling - two doubly infinite sums, all k powers even. + // Multiply coefficient denominators by 2, so omit doubling sums. + final double pi6 = pi4 * MathUtils.PI_SQUARED; + sum = 0; + double kTerm4 = 0; + double kTerm6 = 0; + for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm = k + 0.5; + kTerm2 = kTerm * kTerm; + kTerm4 = kTerm2 * kTerm2; + kTerm6 = kTerm4 * kTerm2; + increment = (pi6 * kTerm6 * (5 - 30 * z2) + pi4 * kTerm4 * (-60 * z2 + 212 * z4) + + MathUtils.PI_SQUARED * kTerm2 * (135 * z4 - 96 * z6) - 30 * z6 - 90 * z8) * + FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2); + sum += increment; + if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + sum2 = 0; + for (k = 1; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) { + kTerm2 = k * k; + kTerm4 = kTerm2 * kTerm2; + increment = (-pi4 * kTerm4 + 3 * MathUtils.PI_SQUARED * kTerm2 * z2) * + FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2); + sum2 += increment; + if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum2)) { + break; + } + } + if (k == MAXIMUM_PARTIAL_SUM_COUNT) { + throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT); + } + return ret + (sqrtHalfPi / (sqrtN * n)) * (sum / (3240 * z6 * z4) + + + sum2 / (108 * z6)); + + } + + /*** + * Creates {@code H} of size {@code m x m} as described in [1] (see above). + * + * @param d statistic + * @param n sample size + * @return H matrix + * @throws NumberIsTooLargeException if fractional part is greater than 1 + * @throws FractionConversionException if algorithm fails to convert {@code h} to a + * {@link BigFraction} in expressing {@code d} as \((k + * - h) / m\) for integer {@code k, m} and \(0 <= h < 1\). + */ + private FieldMatrix createExactH(double d, int n) + throws NumberIsTooLargeException, FractionConversionException { + + final int k = (int) Math.ceil(n * d); + final int m = 2 * k - 1; + final double hDouble = k - n * d; + if (hDouble >= 1) { + throw new NumberIsTooLargeException(hDouble, 1.0, false); + } + BigFraction h = null; + try { + h = new BigFraction(hDouble, 1.0e-20, 10000); + } catch (final FractionConversionException e1) { + try { + h = new BigFraction(hDouble, 1.0e-10, 10000); + } catch (final FractionConversionException e2) { + h = new BigFraction(hDouble, 1.0e-5, 10000); + } + } + final BigFraction[][] Hdata = new BigFraction[m][m]; + + /* + * Start by filling everything with either 0 or 1. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < m; ++j) { + if (i - j + 1 < 0) { + Hdata[i][j] = BigFraction.ZERO; + } else { + Hdata[i][j] = BigFraction.ONE; + } + } + } + + /* + * Setting up power-array to avoid calculating the same value twice: hPowers[0] = h^1 ... + * hPowers[m-1] = h^m + */ + final BigFraction[] hPowers = new BigFraction[m]; + hPowers[0] = h; + for (int i = 1; i < m; ++i) { + hPowers[i] = h.multiply(hPowers[i - 1]); + } + + /* + * First column and last row has special values (each other reversed). + */ + for (int i = 0; i < m; ++i) { + Hdata[i][0] = Hdata[i][0].subtract(hPowers[i]); + Hdata[m - 1][i] = Hdata[m - 1][i].subtract(hPowers[m - i - 1]); + } + + /* + * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix should be (1 - 2*h^m + + * (2h - 1)^m )/m!" Since 0 <= h < 1, then if h > 1/2 is sufficient to check: + */ + if (h.compareTo(BigFraction.ONE_HALF) == 1) { + Hdata[m - 1][0] = Hdata[m - 1][0].add(h.multiply(2).subtract(1).pow(m)); + } + + /* + * Aside from the first column and last row, the (i, j)-th element is 1/(i - j + 1)! if i - + * j + 1 >= 0, else 0. 1's and 0's are already put, so only division with (i - j + 1)! is + * needed in the elements that have 1's. There is no need to calculate (i - j + 1)! and then + * divide - small steps avoid overflows. Note that i - j + 1 > 0 <=> i + 1 > j instead of + * j'ing all the way to m. Also note that it is started at g = 2 because dividing by 1 isn't + * really necessary. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < i + 1; ++j) { + if (i - j + 1 > 0) { + for (int g = 2; g <= i - j + 1; ++g) { + Hdata[i][j] = Hdata[i][j].divide(g); + } + } + } + } + return new Array2DRowFieldMatrix(BigFractionField.getInstance(), Hdata); + } + + /*** + * Creates {@code H} of size {@code m x m} as described in [1] (see above) + * using double-precision. + * + * @param d statistic + * @param n sample size + * @return H matrix + * @throws NumberIsTooLargeException if fractional part is greater than 1 + */ + private RealMatrix createRoundedH(double d, int n) + throws NumberIsTooLargeException { + + final int k = (int) Math.ceil(n * d); + final int m = 2 * k - 1; + final double h = k - n * d; + if (h >= 1) { + throw new NumberIsTooLargeException(h, 1.0, false); + } + final double[][] Hdata = new double[m][m]; + + /* + * Start by filling everything with either 0 or 1. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < m; ++j) { + if (i - j + 1 < 0) { + Hdata[i][j] = 0; + } else { + Hdata[i][j] = 1; + } + } + } + + /* + * Setting up power-array to avoid calculating the same value twice: hPowers[0] = h^1 ... + * hPowers[m-1] = h^m + */ + final double[] hPowers = new double[m]; + hPowers[0] = h; + for (int i = 1; i < m; ++i) { + hPowers[i] = h * hPowers[i - 1]; + } + + /* + * First column and last row has special values (each other reversed). + */ + for (int i = 0; i < m; ++i) { + Hdata[i][0] = Hdata[i][0] - hPowers[i]; + Hdata[m - 1][i] -= hPowers[m - i - 1]; + } + + /* + * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix should be (1 - 2*h^m + + * (2h - 1)^m )/m!" Since 0 <= h < 1, then if h > 1/2 is sufficient to check: + */ + if (Double.compare(h, 0.5) > 0) { + Hdata[m - 1][0] += FastMath.pow(2 * h - 1, m); + } + + /* + * Aside from the first column and last row, the (i, j)-th element is 1/(i - j + 1)! if i - + * j + 1 >= 0, else 0. 1's and 0's are already put, so only division with (i - j + 1)! is + * needed in the elements that have 1's. There is no need to calculate (i - j + 1)! and then + * divide - small steps avoid overflows. Note that i - j + 1 > 0 <=> i + 1 > j instead of + * j'ing all the way to m. Also note that it is started at g = 2 because dividing by 1 isn't + * really necessary. + */ + for (int i = 0; i < m; ++i) { + for (int j = 0; j < i + 1; ++j) { + if (i - j + 1 > 0) { + for (int g = 2; g <= i - j + 1; ++g) { + Hdata[i][j] /= g; + } + } + } + } + return MatrixUtils.createRealMatrix(Hdata); + } + + /** + * Verifies that {@code array} has length at least 2. + * + * @param array array to test + * @throws NullArgumentException if array is null + * @throws InsufficientDataException if array is too short + */ + private void checkArray(double[] array) { + if (array == null) { + throw new NullArgumentException(LocalizedFormats.NULL_NOT_ALLOWED); + } + if (array.length < 2) { + throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, array.length, + 2); + } + } + + /** + * Computes \( 1 + 2 \sum_{i=1}^\infty (-1)^i e^{-2 i^2 t^2} \) stopping when successive partial + * sums are within {@code tolerance} of one another, or when {@code maxIterations} partial sums + * have been computed. If the sum does not converge before {@code maxIterations} iterations a + * {@link TooManyIterationsException} is thrown. + * + * @param t argument + * @param tolerance Cauchy criterion for partial sums + * @param maxIterations maximum number of partial sums to compute + * @return Kolmogorov sum evaluated at t + * @throws TooManyIterationsException if the series does not converge + */ + public double ksSum(double t, double tolerance, int maxIterations) { + if (t == 0.0) { + return 0.0; + } + + // TODO: for small t (say less than 1), the alternative expansion in part 3 of [1] + // from class javadoc should be used. + + final double x = -2 * t * t; + int sign = -1; + long i = 1; + double partialSum = 0.5d; + double delta = 1; + while (delta > tolerance && i < maxIterations) { + delta = FastMath.exp(x * i * i); + partialSum += sign * delta; + sign *= -1; + i++; + } + if (i == maxIterations) { + throw new TooManyIterationsException(maxIterations); + } + return partialSum * 2; + } + + /** + * Given a d-statistic in the range [0, 1] and the two sample sizes n and m, + * an integral d-statistic in the range [0, n*m] is calculated, that can be used for + * comparison with other integral d-statistics. Depending whether {@code strict} is + * {@code true} or not, the returned value divided by (n*m) is greater than + * (resp greater than or equal to) the given d value (allowing some tolerance). + * + * @param d a d-statistic in the range [0, 1] + * @param n first sample size + * @param m second sample size + * @param strict whether the returned value divided by (n*m) is allowed to be equal to d + * @return the integral d-statistic in the range [0, n*m] + */ + private static long calculateIntegralD(double d, int n, int m, boolean strict) { + final double tol = 1e-12; // d-values within tol of one another are considered equal + long nm = n * (long)m; + long upperBound = (long)FastMath.ceil((d - tol) * nm); + long lowerBound = (long)FastMath.floor((d + tol) * nm); + if (strict && lowerBound == upperBound) { + return upperBound + 1l; + } + else { + return upperBound; + } + } + + /** + * Computes \(P(D_{n,m} > d)\) if {@code strict} is {@code true}; otherwise \(P(D_{n,m} \ge + * d)\), where \(D_{n,m}\) is the 2-sample Kolmogorov-Smirnov statistic. See + * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\). + *

        + * The returned probability is exact, implemented by unwinding the recursive function + * definitions presented in [4] (class javadoc). + *

        + * + * @param d D-statistic value + * @param n first sample size + * @param m second sample size + * @param strict whether or not the probability to compute is expressed as a strict inequality + * @return probability that a randomly selected m-n partition of m + n generates \(D_{n,m}\) + * greater than (resp. greater than or equal to) {@code d} + */ + public double exactP(double d, int n, int m, boolean strict) { + return 1 - n(m, n, m, n, calculateIntegralD(d, m, n, strict), strict) / + CombinatoricsUtils.binomialCoefficientDouble(n + m, m); + } + + /** + * Uses the Kolmogorov-Smirnov distribution to approximate \(P(D_{n,m} > d)\) where \(D_{n,m}\) + * is the 2-sample Kolmogorov-Smirnov statistic. See + * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\). + *

        + * Specifically, what is returned is \(1 - k(d \sqrt{mn / (m + n)})\) where \(k(t) = 1 + 2 + * \sum_{i=1}^\infty (-1)^i e^{-2 i^2 t^2}\). See {@link #ksSum(double, double, int)} for + * details on how convergence of the sum is determined. This implementation passes {@code ksSum} + * {@value #KS_SUM_CAUCHY_CRITERION} as {@code tolerance} and + * {@value #MAXIMUM_PARTIAL_SUM_COUNT} as {@code maxIterations}. + *

        + * + * @param d D-statistic value + * @param n first sample size + * @param m second sample size + * @return approximate probability that a randomly selected m-n partition of m + n generates + * \(D_{n,m}\) greater than {@code d} + */ + public double approximateP(double d, int n, int m) { + final double dm = m; + final double dn = n; + return 1 - ksSum(d * FastMath.sqrt((dm * dn) / (dm + dn)), + KS_SUM_CAUCHY_CRITERION, MAXIMUM_PARTIAL_SUM_COUNT); + } + + /** + * Fills a boolean array randomly with a fixed number of {@code true} values. + * The method uses a simplified version of the Fisher-Yates shuffle algorithm. + * By processing first the {@code true} values followed by the remaining {@code false} values + * less random numbers need to be generated. The method is optimized for the case + * that the number of {@code true} values is larger than or equal to the number of + * {@code false} values. + * + * @param b boolean array + * @param numberOfTrueValues number of {@code true} values the boolean array should finally have + * @param rng random data generator + */ + static void fillBooleanArrayRandomlyWithFixedNumberTrueValues(final boolean[] b, final int numberOfTrueValues, final RandomGenerator rng) { + Arrays.fill(b, true); + for (int k = numberOfTrueValues; k < b.length; k++) { + final int r = rng.nextInt(k + 1); + b[(b[r]) ? r : k] = false; + } + } + + /** + * Uses Monte Carlo simulation to approximate \(P(D_{n,m} > d)\) where \(D_{n,m}\) is the + * 2-sample Kolmogorov-Smirnov statistic. See + * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\). + *

        + * The simulation generates {@code iterations} random partitions of {@code m + n} into an + * {@code n} set and an {@code m} set, computing \(D_{n,m}\) for each partition and returning + * the proportion of values that are greater than {@code d}, or greater than or equal to + * {@code d} if {@code strict} is {@code false}. + *

        + * + * @param d D-statistic value + * @param n first sample size + * @param m second sample size + * @param iterations number of random partitions to generate + * @param strict whether or not the probability to compute is expressed as a strict inequality + * @return proportion of randomly generated m-n partitions of m + n that result in \(D_{n,m}\) + * greater than (resp. greater than or equal to) {@code d} + */ + public double monteCarloP(final double d, final int n, final int m, final boolean strict, + final int iterations) { + return integralMonteCarloP(calculateIntegralD(d, n, m, strict), n, m, iterations); + } + + /** + * Uses Monte Carlo simulation to approximate \(P(D_{n,m} >= d/(n*m))\) where \(D_{n,m}\) is the + * 2-sample Kolmogorov-Smirnov statistic. + *

        + * Here d is the D-statistic represented as long value. + * The real D-statistic is obtained by dividing d by n*m. + * See also {@link #monteCarloP(double, int, int, boolean, int)}. + * + * @param d integral D-statistic + * @param n first sample size + * @param m second sample size + * @param iterations number of random partitions to generate + * @return proportion of randomly generated m-n partitions of m + n that result in \(D_{n,m}\) + * greater than or equal to {@code d/(n*m))} + */ + private double integralMonteCarloP(final long d, final int n, final int m, final int iterations) { + + // ensure that nn is always the max of (n, m) to require fewer random numbers + final int nn = FastMath.max(n, m); + final int mm = FastMath.min(n, m); + final int sum = nn + mm; + + int tail = 0; + final boolean b[] = new boolean[sum]; + for (int i = 0; i < iterations; i++) { + fillBooleanArrayRandomlyWithFixedNumberTrueValues(b, nn, rng); + long curD = 0l; + for(int j = 0; j < b.length; ++j) { + if (b[j]) { + curD += mm; + if (curD >= d) { + tail++; + break; + } + } else { + curD -= nn; + if (curD <= -d) { + tail++; + break; + } + } + } + } + return (double) tail / iterations; + } + + /** + * If there are no ties in the combined dataset formed from x and y, this + * method is a no-op. If there are ties, a uniform random deviate in + * (-minDelta / 2, minDelta / 2) - {0} is added to each value in x and y, where + * minDelta is the minimum difference between unequal values in the combined + * sample. A fixed seed is used to generate the jitter, so repeated activations + * with the same input arrays result in the same values. + * + * NOTE: if there are ties in the data, this method overwrites the data in + * x and y with the jittered values. + * + * @param x first sample + * @param y second sample + */ + private static void fixTies(double[] x, double[] y) { + final double[] values = MathArrays.unique(MathArrays.concatenate(x,y)); + if (values.length == x.length + y.length) { + return; // There are no ties + } + + // Find the smallest difference between values, or 1 if all values are the same + double minDelta = 1; + double prev = values[0]; + double delta = 1; + for (int i = 1; i < values.length; i++) { + delta = prev - values[i]; + if (delta < minDelta) { + minDelta = delta; + } + prev = values[i]; + } + minDelta /= 2; + + // Add jitter using a fixed seed (so same arguments always give same results), + // low-initialization-overhead generator + final RealDistribution dist = + new UniformRealDistribution(new JDKRandomGenerator(100), -minDelta, minDelta); + + // It is theoretically possible that jitter does not break ties, so repeat + // until all ties are gone. Bound the loop and throw MIE if bound is exceeded. + int ct = 0; + boolean ties = true; + do { + jitter(x, dist); + jitter(y, dist); + ties = hasTies(x, y); + ct++; + } while (ties && ct < 1000); + if (ties) { + throw new MathInternalError(); // Should never happen + } + } + + /** + * Returns true iff there are ties in the combined sample + * formed from x and y. + * + * @param x first sample + * @param y second sample + * @return true if x and y together contain ties + */ + private static boolean hasTies(double[] x, double[] y) { + final HashSet values = new HashSet(); + for (int i = 0; i < x.length; i++) { + if (!values.add(x[i])) { + return true; + } + } + for (int i = 0; i < y.length; i++) { + if (!values.add(y[i])) { + return true; + } + } + return false; + } + + /** + * Adds random jitter to {@code data} using deviates sampled from {@code dist}. + *

        + * Note that jitter is applied in-place - i.e., the array + * values are overwritten with the result of applying jitter.

        + * + * @param data input/output data array - entries overwritten by the method + * @param dist probability distribution to sample for jitter values + * @throws NullPointerException if either of the parameters is null + */ + private static void jitter(double[] data, RealDistribution dist) { + for (int i = 0; i < data.length; i++) { + data[i] += dist.sample(); + } + } + + /** + * The function C(i, j) defined in [4] (class javadoc), formula (5.5). + * defined to return 1 if |i/n - j/m| <= c; 0 otherwise. Here c is scaled up + * and recoded as a long to avoid rounding errors in comparison tests, so what + * is actually tested is |im - jn| <= cmn. + * + * @param i first path parameter + * @param j second path paramter + * @param m first sample size + * @param n second sample size + * @param cmn integral D-statistic (see {@link #calculateIntegralD(double, int, int, boolean)}) + * @param strict whether or not the null hypothesis uses strict inequality + * @return C(i,j) for given m, n, c + */ + private static int c(int i, int j, int m, int n, long cmn, boolean strict) { + if (strict) { + return FastMath.abs(i*(long)n - j*(long)m) <= cmn ? 1 : 0; + } + return FastMath.abs(i*(long)n - j*(long)m) < cmn ? 1 : 0; + } + + /** + * The function N(i, j) defined in [4] (class javadoc). + * Returns the number of paths over the lattice {(i,j) : 0 <= i <= n, 0 <= j <= m} + * from (0,0) to (i,j) satisfying C(h,k, m, n, c) = 1 for each (h,k) on the path. + * The return value is integral, but subject to overflow, so it is maintained and + * returned as a double. + * + * @param i first path parameter + * @param j second path parameter + * @param m first sample size + * @param n second sample size + * @param cnm integral D-statistic (see {@link #calculateIntegralD(double, int, int, boolean)}) + * @param strict whether or not the null hypothesis uses strict inequality + * @return number or paths to (i, j) from (0,0) representing D-values as large as c for given m, n + */ + private static double n(int i, int j, int m, int n, long cnm, boolean strict) { + /* + * Unwind the recursive definition given in [4]. + * Compute n(1,1), n(1,2)...n(2,1), n(2,2)... up to n(i,j), one row at a time. + * When n(i,*) are being computed, lag[] holds the values of n(i - 1, *). + */ + final double[] lag = new double[n]; + double last = 0; + for (int k = 0; k < n; k++) { + lag[k] = c(0, k + 1, m, n, cnm, strict); + } + for (int k = 1; k <= i; k++) { + last = c(k, 0, m, n, cnm, strict); + for (int l = 1; l <= j; l++) { + lag[l - 1] = c(k, l, m, n, cnm, strict) * (last + lag[l - 1]); + last = lag[l - 1]; + } + } + return last; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java new file mode 100644 index 000000000..23c2ca7ab --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaNStrategy; +import com.fr.third.org.apache.commons.math3.stat.ranking.TiesStrategy; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaturalRanking; + +/** + * An implementation of the Mann-Whitney U test (also called Wilcoxon rank-sum test). + * + */ +public class MannWhitneyUTest { + + /** Ranking algorithm. */ + private NaturalRanking naturalRanking; + + /** + * Create a test instance using where NaN's are left in place and ties get + * the average of applicable ranks. Use this unless you are very sure of + * what you are doing. + */ + public MannWhitneyUTest() { + naturalRanking = new NaturalRanking(NaNStrategy.FIXED, + TiesStrategy.AVERAGE); + } + + /** + * Create a test instance using the given strategies for NaN's and ties. + * Only use this if you are sure of what you are doing. + * + * @param nanStrategy + * specifies the strategy that should be used for Double.NaN's + * @param tiesStrategy + * specifies the strategy that should be used for ties + */ + public MannWhitneyUTest(final NaNStrategy nanStrategy, + final TiesStrategy tiesStrategy) { + naturalRanking = new NaturalRanking(nanStrategy, tiesStrategy); + } + + /** + * Ensures that the provided arrays fulfills the assumptions. + * + * @param x first sample + * @param y second sample + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + */ + private void ensureDataConformance(final double[] x, final double[] y) + throws NullArgumentException, NoDataException { + + if (x == null || + y == null) { + throw new NullArgumentException(); + } + if (x.length == 0 || + y.length == 0) { + throw new NoDataException(); + } + } + + /** Concatenate the samples into one array. + * @param x first sample + * @param y second sample + * @return concatenated array + */ + private double[] concatenateSamples(final double[] x, final double[] y) { + final double[] z = new double[x.length + y.length]; + + System.arraycopy(x, 0, z, 0, x.length); + System.arraycopy(y, 0, z, x.length, y.length); + + return z; + } + + /** + * Computes the Mann-Whitney + * U statistic comparing mean for two independent samples possibly of + * different length. + *

        + * This statistic can be used to perform a Mann-Whitney U test evaluating + * the null hypothesis that the two independent samples has equal mean. + *

        + *

        + * Let Xi denote the i'th individual of the first sample and + * Yj the j'th individual in the second sample. Note that the + * samples would often have different length. + *

        + *

        + * Preconditions: + *

          + *
        • All observations in the two samples are independent.
        • + *
        • The observations are at least ordinal (continuous are also ordinal).
        • + *
        + *

        + * + * @param x the first sample + * @param y the second sample + * @return Mann-Whitney U statistic (maximum of Ux and Uy) + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + */ + public double mannWhitneyU(final double[] x, final double[] y) + throws NullArgumentException, NoDataException { + + ensureDataConformance(x, y); + + final double[] z = concatenateSamples(x, y); + final double[] ranks = naturalRanking.rank(z); + + double sumRankX = 0; + + /* + * The ranks for x is in the first x.length entries in ranks because x + * is in the first x.length entries in z + */ + for (int i = 0; i < x.length; ++i) { + sumRankX += ranks[i]; + } + + /* + * U1 = R1 - (n1 * (n1 + 1)) / 2 where R1 is sum of ranks for sample 1, + * e.g. x, n1 is the number of observations in sample 1. + */ + final double U1 = sumRankX - ((long) x.length * (x.length + 1)) / 2; + + /* + * It can be shown that U1 + U2 = n1 * n2 + */ + final double U2 = (long) x.length * y.length - U1; + + return FastMath.max(U1, U2); + } + + /** + * @param Umin smallest Mann-Whitney U value + * @param n1 number of subjects in first sample + * @param n2 number of subjects in second sample + * @return two-sided asymptotic p-value + * @throws ConvergenceException if the p-value can not be computed + * due to a convergence error + * @throws MaxCountExceededException if the maximum number of + * iterations is exceeded + */ + private double calculateAsymptoticPValue(final double Umin, + final int n1, + final int n2) + throws ConvergenceException, MaxCountExceededException { + + /* long multiplication to avoid overflow (double not used due to efficiency + * and to avoid precision loss) + */ + final long n1n2prod = (long) n1 * n2; + + // http://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U#Normal_approximation + final double EU = n1n2prod / 2.0; + final double VarU = n1n2prod * (n1 + n2 + 1) / 12.0; + + final double z = (Umin - EU) / FastMath.sqrt(VarU); + + // No try-catch or advertised exception because args are valid + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final NormalDistribution standardNormal = new NormalDistribution(null, 0, 1); + + return 2 * standardNormal.cumulativeProbability(z); + } + + /** + * Returns the asymptotic observed significance level, or + * p-value, associated with a Mann-Whitney + * U statistic comparing mean for two independent samples. + *

        + * Let Xi denote the i'th individual of the first sample and + * Yj the j'th individual in the second sample. Note that the + * samples would often have different length. + *

        + *

        + * Preconditions: + *

          + *
        • All observations in the two samples are independent.
        • + *
        • The observations are at least ordinal (continuous are also ordinal).
        • + *
        + *

        + * Ties give rise to biased variance at the moment. See e.g. http://mlsc.lboro.ac.uk/resources/statistics/Mannwhitney.pdf.

        + * + * @param x the first sample + * @param y the second sample + * @return asymptotic p-value + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + * @throws ConvergenceException if the p-value can not be computed due to a + * convergence error + * @throws MaxCountExceededException if the maximum number of iterations + * is exceeded + */ + public double mannWhitneyUTest(final double[] x, final double[] y) + throws NullArgumentException, NoDataException, + ConvergenceException, MaxCountExceededException { + + ensureDataConformance(x, y); + + final double Umax = mannWhitneyU(x, y); + + /* + * It can be shown that U1 + U2 = n1 * n2 + */ + final double Umin = (long) x.length * y.length - Umax; + + return calculateAsymptoticPValue(Umin, x.length, y.length); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/OneWayAnova.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/OneWayAnova.java new file mode 100644 index 000000000..289479f96 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/OneWayAnova.java @@ -0,0 +1,355 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import java.util.ArrayList; +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.distribution.FDistribution; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.SummaryStatistics; +import com.fr.third.org.apache.commons.math3.util.MathUtils; + +/** + * Implements one-way ANOVA (analysis of variance) statistics. + * + *

        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 TTest}. + *

        + * Uses the {@link FDistribution + * commons-math F Distribution implementation} to estimate exact p-values.

        + *

        This implementation is based on a description at + * http://faculty.vassar.edu/lowry/ch13pt1.html

        + *
        + * Abbreviations: bg = between groups,
        + *                wg = within groups,
        + *                ss = sum squared deviations
        + * 
        + * + * @since 1.2 + */ +public class OneWayAnova { + + /** + * Default constructor. + */ + public OneWayAnova() { + } + + /** + * Computes the ANOVA F-value for a collection of double[] + * arrays. + * + *

        Preconditions:

          + *
        • The categoryData Collection must contain + * double[] arrays.
        • + *
        • There must be at least two double[] arrays in the + * categoryData collection and each of these arrays must + * contain at least two values.

        + * This implementation computes the F statistic using the definitional + * formula

        +     *   F = msbg/mswg
        + * where
        +     *  msbg = between group mean square
        +     *  mswg = within group mean square
        + * are as defined + * here

        + * + * @param categoryData Collection of double[] + * arrays each containing data for one category + * @return Fvalue + * @throws NullArgumentException if categoryData is null + * @throws DimensionMismatchException if the length of the categoryData + * array is less than 2 or a contained double[] array does not have + * at least two values + */ + public double anovaFValue(final Collection categoryData) + throws NullArgumentException, DimensionMismatchException { + + AnovaStats a = anovaStats(categoryData); + return a.F; + + } + + /** + * Computes the ANOVA P-value for a collection of double[] + * arrays. + * + *

        Preconditions:

          + *
        • The categoryData Collection must contain + * double[] arrays.
        • + *
        • There must be at least two double[] arrays in the + * categoryData collection and each of these arrays must + * contain at least two values.

        + * This implementation uses the + * {@link FDistribution + * commons-math F Distribution implementation} to estimate the exact + * p-value, using the formula

        +     *   p = 1 - cumulativeProbability(F)
        + * where F is the F value and cumulativeProbability + * is the commons-math implementation of the F distribution.

        + * + * @param categoryData Collection of double[] + * arrays each containing data for one category + * @return Pvalue + * @throws NullArgumentException if categoryData is null + * @throws DimensionMismatchException if the length of the categoryData + * array is less than 2 or a contained double[] array does not have + * at least two values + * @throws ConvergenceException if the p-value can not be computed due to a convergence error + * @throws MaxCountExceededException if the maximum number of iterations is exceeded + */ + public double anovaPValue(final Collection categoryData) + throws NullArgumentException, DimensionMismatchException, + ConvergenceException, MaxCountExceededException { + + final AnovaStats a = anovaStats(categoryData); + // No try-catch or advertised exception because args are valid + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final FDistribution fdist = new FDistribution(null, a.dfbg, a.dfwg); + return 1.0 - fdist.cumulativeProbability(a.F); + + } + + /** + * Computes the ANOVA P-value for a collection of {@link SummaryStatistics}. + * + *

        Preconditions:

          + *
        • The categoryData Collection must contain + * {@link SummaryStatistics}.
        • + *
        • There must be at least two {@link SummaryStatistics} in the + * categoryData collection and each of these statistics must + * contain at least two values.

        + * This implementation uses the + * {@link FDistribution + * commons-math F Distribution implementation} to estimate the exact + * p-value, using the formula

        +     *   p = 1 - cumulativeProbability(F)
        + * where F is the F value and cumulativeProbability + * is the commons-math implementation of the F distribution.

        + * + * @param categoryData Collection of {@link SummaryStatistics} + * each containing data for one category + * @param allowOneElementData if true, allow computation for one catagory + * only or for one data element per category + * @return Pvalue + * @throws NullArgumentException if categoryData is null + * @throws DimensionMismatchException if the length of the categoryData + * array is less than 2 or a contained {@link SummaryStatistics} does not have + * at least two values + * @throws ConvergenceException if the p-value can not be computed due to a convergence error + * @throws MaxCountExceededException if the maximum number of iterations is exceeded + * @since 3.2 + */ + public double anovaPValue(final Collection categoryData, + final boolean allowOneElementData) + throws NullArgumentException, DimensionMismatchException, + ConvergenceException, MaxCountExceededException { + + final AnovaStats a = anovaStats(categoryData, allowOneElementData); + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final FDistribution fdist = new FDistribution(null, a.dfbg, a.dfwg); + return 1.0 - fdist.cumulativeProbability(a.F); + + } + + /** + * This method calls the method that actually does the calculations (except + * P-value). + * + * @param categoryData + * Collection of double[] arrays each + * containing data for one category + * @return computed AnovaStats + * @throws NullArgumentException + * if categoryData is null + * @throws DimensionMismatchException + * if the length of the categoryData array is less + * than 2 or a contained double[] array does not + * contain at least two values + */ + private AnovaStats anovaStats(final Collection categoryData) + throws NullArgumentException, DimensionMismatchException { + + MathUtils.checkNotNull(categoryData); + + final Collection categoryDataSummaryStatistics = + new ArrayList(categoryData.size()); + + // convert arrays to SummaryStatistics + for (final double[] data : categoryData) { + final SummaryStatistics dataSummaryStatistics = new SummaryStatistics(); + categoryDataSummaryStatistics.add(dataSummaryStatistics); + for (final double val : data) { + dataSummaryStatistics.addValue(val); + } + } + + return anovaStats(categoryDataSummaryStatistics, false); + + } + + /** + * Performs an ANOVA test, evaluating the null hypothesis that there + * is no difference among the means of the data categories. + * + *

        Preconditions:

          + *
        • The categoryData Collection must contain + * double[] arrays.
        • + *
        • There must be at least two double[] arrays in the + * categoryData collection and each of these arrays must + * contain at least two values.
        • + *
        • alpha must be strictly greater than 0 and less than or equal to 0.5. + *

        + * This implementation uses the + * {@link FDistribution + * commons-math F Distribution implementation} to estimate the exact + * p-value, using the formula

        +     *   p = 1 - cumulativeProbability(F)
        + * where F is the F value and cumulativeProbability + * is the commons-math implementation of the F distribution.

        + *

        True is returned iff the estimated p-value is less than alpha.

        + * + * @param categoryData Collection of double[] + * arrays each containing data for one category + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with + * confidence 1 - alpha + * @throws NullArgumentException if categoryData is null + * @throws DimensionMismatchException if the length of the categoryData + * array is less than 2 or a contained double[] array does not have + * at least two values + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws ConvergenceException if the p-value can not be computed due to a convergence error + * @throws MaxCountExceededException if the maximum number of iterations is exceeded + */ + public boolean anovaTest(final Collection categoryData, + final double alpha) + throws NullArgumentException, DimensionMismatchException, + OutOfRangeException, ConvergenceException, MaxCountExceededException { + + if ((alpha <= 0) || (alpha > 0.5)) { + throw new OutOfRangeException( + LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, + alpha, 0, 0.5); + } + return anovaPValue(categoryData) < alpha; + + } + + /** + * This method actually does the calculations (except P-value). + * + * @param categoryData Collection of double[] + * arrays each containing data for one category + * @param allowOneElementData if true, allow computation for one catagory + * only or for one data element per category + * @return computed AnovaStats + * @throws NullArgumentException if categoryData is null + * @throws DimensionMismatchException if allowOneElementData is false and the number of + * categories is less than 2 or a contained SummaryStatistics does not contain + * at least two values + */ + private AnovaStats anovaStats(final Collection categoryData, + final boolean allowOneElementData) + throws NullArgumentException, DimensionMismatchException { + + MathUtils.checkNotNull(categoryData); + + if (!allowOneElementData) { + // check if we have enough categories + if (categoryData.size() < 2) { + throw new DimensionMismatchException(LocalizedFormats.TWO_OR_MORE_CATEGORIES_REQUIRED, + categoryData.size(), 2); + } + + // check if each category has enough data + for (final SummaryStatistics array : categoryData) { + if (array.getN() <= 1) { + throw new DimensionMismatchException(LocalizedFormats.TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED, + (int) array.getN(), 2); + } + } + } + + int dfwg = 0; + double sswg = 0; + double totsum = 0; + double totsumsq = 0; + int totnum = 0; + + for (final SummaryStatistics data : categoryData) { + + final double sum = data.getSum(); + final double sumsq = data.getSumsq(); + final int num = (int) data.getN(); + totnum += num; + totsum += sum; + totsumsq += sumsq; + + dfwg += num - 1; + final double ss = sumsq - ((sum * sum) / num); + sswg += ss; + } + + final double sst = totsumsq - ((totsum * totsum) / totnum); + final double ssbg = sst - sswg; + final int dfbg = categoryData.size() - 1; + final double msbg = ssbg / dfbg; + final double mswg = sswg / dfwg; + final double F = msbg / mswg; + + return new AnovaStats(dfbg, dfwg, F); + + } + + /** + Convenience class to pass dfbg,dfwg,F values around within OneWayAnova. + No get/set methods provided. + */ + private static class AnovaStats { + + /** Degrees of freedom in numerator (between groups). */ + private final int dfbg; + + /** Degrees of freedom in denominator (within groups). */ + private final int dfwg; + + /** Statistic. */ + private final double F; + + /** + * Constructor + * @param dfbg degrees of freedom in numerator (between groups) + * @param dfwg degrees of freedom in denominator (within groups) + * @param F statistic + */ + private AnovaStats(int dfbg, int dfwg, double F) { + this.dfbg = dfbg; + this.dfwg = dfwg; + this.F = F; + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TTest.java new file mode 100644 index 000000000..8405d5b5a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TTest.java @@ -0,0 +1,1184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.TDistribution; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.stat.descriptive.StatisticalSummary; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.stat.StatUtils; + +/** + * An implementation for Student's t-tests. + *

        + * Tests can be:

          + *
        • One-sample or two-sample
        • + *
        • One-sided or two-sided
        • + *
        • Paired or unpaired (for two-sample tests)
        • + *
        • Homoscedastic (equal variance assumption) or heteroscedastic + * (for two sample tests)
        • + *
        • Fixed significance level (boolean-valued) or returning p-values. + *

        + *

        + * 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, double-valued methods return p-values; + * boolean-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 alpha=0.05).

        + *

        + * Input to tests can be either double[] arrays or + * {@link StatisticalSummary} instances.

        + * Uses commons-math {@link TDistribution} + * implementation to estimate exact p-values.

        + * + */ +public class TTest { + /** + * Computes a paired, 2-sample t-statistic based on the data in the input + * arrays. The t-statistic returned is equivalent to what would be returned by + * computing the one-sample t-statistic {@link #t(double, double[])}, with + * mu = 0 and the sample array consisting of the (signed) + * differences between corresponding entries in sample1 and + * sample2. + *

        + * Preconditions:

          + *
        • The input arrays must have the same length and their common length + * must be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return t statistic + * @throws NullArgumentException if the arrays are null + * @throws NoDataException if the arrays are empty + * @throws DimensionMismatchException if the length of the arrays is not equal + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + */ + public double pairedT(final double[] sample1, final double[] sample2) + throws NullArgumentException, NoDataException, + DimensionMismatchException, NumberIsTooSmallException { + + checkSampleData(sample1); + checkSampleData(sample2); + double meanDifference = StatUtils.meanDifference(sample1, sample2); + return t(meanDifference, 0, + StatUtils.varianceDifference(sample1, sample2, meanDifference), + sample1.length); + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a paired, two-sample, two-tailed t-test + * based on the data in the input arrays. + *

        + * 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.

        + *

        + * This test is equivalent to a one-sample t-test computed using + * {@link #tTest(double, double[])} with mu = 0 and the sample + * array consisting of the signed differences between corresponding elements of + * sample1 and sample2.

        + *

        + * Usage Note:
        + * The validity of the p-value depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The input array lengths must be the same and their common length must + * be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return p-value for t-test + * @throws NullArgumentException if the arrays are null + * @throws NoDataException if the arrays are empty + * @throws DimensionMismatchException if the length of the arrays is not equal + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double pairedTTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NoDataException, DimensionMismatchException, + NumberIsTooSmallException, MaxCountExceededException { + + double meanDifference = StatUtils.meanDifference(sample1, sample2); + return tTest(meanDifference, 0, + StatUtils.varianceDifference(sample1, sample2, meanDifference), + sample1.length); + + } + + /** + * Performs a paired t-test evaluating the null hypothesis that the + * mean of the paired differences between sample1 and + * sample2 is 0 in favor of the two-sided alternative that the + * mean paired difference is not equal to 0, with significance level + * alpha. + *

        + * Returns true iff the null hypothesis can be rejected with + * confidence 1 - alpha. To perform a 1-sided test, use + * alpha * 2

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The input array lengths must be the same and their common length + * must be at least 2. + *
        • + *
        • 0 < alpha < 0.5 + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with + * confidence 1 - alpha + * @throws NullArgumentException if the arrays are null + * @throws NoDataException if the arrays are empty + * @throws DimensionMismatchException if the length of the arrays is not equal + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean pairedTTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NoDataException, DimensionMismatchException, + NumberIsTooSmallException, OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return pairedTTest(sample1, sample2) < alpha; + + } + + /** + * Computes a + * t statistic given observed values and a comparison constant. + *

        + * This statistic can be used to perform a one sample t-test for the mean. + *

        + * Preconditions:

          + *
        • The observed array length must be at least 2. + *

        + * + * @param mu comparison constant + * @param observed array of values + * @return t statistic + * @throws NullArgumentException if observed is null + * @throws NumberIsTooSmallException if the length of observed is < 2 + */ + public double t(final double mu, final double[] observed) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(observed); + // No try-catch or advertised exception because args have just been checked + return t(StatUtils.mean(observed), mu, StatUtils.variance(observed), + observed.length); + + } + + /** + * Computes a + * t statistic to use in comparing the mean of the dataset described by + * sampleStats to mu. + *

        + * This statistic can be used to perform a one sample t-test for the mean. + *

        + * Preconditions:

          + *
        • observed.getN() ≥ 2. + *

        + * + * @param mu comparison constant + * @param sampleStats DescriptiveStatistics holding sample summary statitstics + * @return t statistic + * @throws NullArgumentException if sampleStats is null + * @throws NumberIsTooSmallException if the number of samples is < 2 + */ + public double t(final double mu, final StatisticalSummary sampleStats) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(sampleStats); + return t(sampleStats.getMean(), mu, sampleStats.getVariance(), + sampleStats.getN()); + + } + + /** + * Computes a 2-sample t statistic, under the hypothesis of equal + * subpopulation variances. To compute a t-statistic without the + * equal variances hypothesis, use {@link #t(double[], double[])}. + *

        + * This statistic can be used to perform a (homoscedastic) two-sample + * t-test to compare sample means.

        + *

        + * The t-statistic is

        + *

        + *    t = (m1 - m2) / (sqrt(1/n1 +1/n2) sqrt(var)) + *

        + * where n1 is the size of first sample; + * n2 is the size of second sample; + * m1 is the mean of first sample; + * m2 is the mean of second sample + * + * and var is the pooled variance estimate: + *

        + * var = sqrt(((n1 - 1)var1 + (n2 - 1)var2) / ((n1-1) + (n2-1))) + *

        + * with var1 the variance of the first sample and + * var2 the variance of the second sample. + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return t statistic + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + */ + public double homoscedasticT(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(sample1); + checkSampleData(sample2); + // No try-catch or advertised exception because args have just been checked + return homoscedasticT(StatUtils.mean(sample1), StatUtils.mean(sample2), + StatUtils.variance(sample1), StatUtils.variance(sample2), + sample1.length, sample2.length); + + } + + /** + * Computes a 2-sample t statistic, without the hypothesis of equal + * subpopulation variances. To compute a t-statistic assuming equal + * variances, use {@link #homoscedasticT(double[], double[])}. + *

        + * This statistic can be used to perform a two-sample t-test to compare + * sample means.

        + *

        + * The t-statistic is

        + *

        + *    t = (m1 - m2) / sqrt(var1/n1 + var2/n2) + *

        + * where n1 is the size of the first sample + * n2 is the size of the second sample; + * m1 is the mean of the first sample; + * m2 is the mean of the second sample; + * var1 is the variance of the first sample; + * var2 is the variance of the second sample; + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return t statistic + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + */ + public double t(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(sample1); + checkSampleData(sample2); + // No try-catch or advertised exception because args have just been checked + return t(StatUtils.mean(sample1), StatUtils.mean(sample2), + StatUtils.variance(sample1), StatUtils.variance(sample2), + sample1.length, sample2.length); + + } + + /** + * Computes a 2-sample t statistic , 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. + *

        + * This statistic can be used to perform a two-sample t-test to compare + * sample means.

        + *

        + * The returned t-statistic is

        + *

        + *    t = (m1 - m2) / sqrt(var1/n1 + var2/n2) + *

        + * where n1 is the size of the first sample; + * n2 is the size of the second sample; + * m1 is the mean of the first sample; + * m2 is the mean of the second sample + * var1 is the variance of the first sample; + * var2 is the variance of the second sample + *

        + * Preconditions:

          + *
        • The datasets described by the two Univariates must each contain + * at least 2 observations. + *

        + * + * @param sampleStats1 StatisticalSummary describing data from the first sample + * @param sampleStats2 StatisticalSummary describing data from the second sample + * @return t statistic + * @throws NullArgumentException if the sample statistics are null + * @throws NumberIsTooSmallException if the number of samples is < 2 + */ + public double t(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(sampleStats1); + checkSampleData(sampleStats2); + return t(sampleStats1.getMean(), sampleStats2.getMean(), + sampleStats1.getVariance(), sampleStats2.getVariance(), + sampleStats1.getN(), sampleStats2.getN()); + + } + + /** + * Computes a 2-sample t statistic, comparing the means of the datasets + * described by two {@link StatisticalSummary} instances, under the + * assumption of equal subpopulation variances. To compute a t-statistic + * without the equal variances assumption, use + * {@link #t(StatisticalSummary, StatisticalSummary)}. + *

        + * This statistic can be used to perform a (homoscedastic) two-sample + * t-test to compare sample means.

        + *

        + * The t-statistic returned is

        + *

        + *    t = (m1 - m2) / (sqrt(1/n1 +1/n2) sqrt(var)) + *

        + * where n1 is the size of first sample; + * n2 is the size of second sample; + * m1 is the mean of first sample; + * m2 is the mean of second sample + * and var is the pooled variance estimate: + *

        + * var = sqrt(((n1 - 1)var1 + (n2 - 1)var2) / ((n1-1) + (n2-1))) + *

        + * with var1 the variance of the first sample and + * var2 the variance of the second sample. + *

        + * Preconditions:

          + *
        • The datasets described by the two Univariates must each contain + * at least 2 observations. + *

        + * + * @param sampleStats1 StatisticalSummary describing data from the first sample + * @param sampleStats2 StatisticalSummary describing data from the second sample + * @return t statistic + * @throws NullArgumentException if the sample statistics are null + * @throws NumberIsTooSmallException if the number of samples is < 2 + */ + public double homoscedasticT(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException { + + checkSampleData(sampleStats1); + checkSampleData(sampleStats2); + return homoscedasticT(sampleStats1.getMean(), sampleStats2.getMean(), + sampleStats1.getVariance(), sampleStats2.getVariance(), + sampleStats1.getN(), sampleStats2.getN()); + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a one-sample, two-tailed t-test + * comparing the mean of the input array with the constant mu. + *

        + * The number returned is the smallest significance level + * at which one can reject the null hypothesis that the mean equals + * mu in favor of the two-sided alternative that the mean + * is different from mu. For a one-sided test, divide the + * returned value by 2.

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * here + *

        + * Preconditions:

          + *
        • The observed array length must be at least 2. + *

        + * + * @param mu constant value to compare sample mean against + * @param sample array of sample data values + * @return p-value + * @throws NullArgumentException if the sample array is null + * @throws NumberIsTooSmallException if the length of the array is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double tTest(final double mu, final double[] sample) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sample); + // No try-catch or advertised exception because args have just been checked + return tTest(StatUtils.mean(sample), mu, StatUtils.variance(sample), + sample.length); + + } + + /** + * Performs a + * two-sided t-test evaluating the null hypothesis that the mean of the population from + * which sample is drawn equals mu. + *

        + * Returns true iff the null hypothesis can be + * rejected with confidence 1 - alpha. To + * perform a 1-sided test, use alpha * 2

        + *

        + * Examples:

          + *
        1. To test the (2-sided) hypothesis sample mean = mu at + * the 95% level, use
          tTest(mu, sample, 0.05) + *
        2. + *
        3. To test the (one-sided) hypothesis sample mean < mu + * at the 99% level, first verify that the measured sample mean is less + * than mu and then use + *
          tTest(mu, sample, 0.02) + *

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the one-sample + * parametric t-test procedure, as discussed + * here + *

        + * Preconditions:

          + *
        • The observed array length must be at least 2. + *

        + * + * @param mu constant value to compare sample mean against + * @param sample array of sample data values + * @param alpha significance level of the test + * @return p-value + * @throws NullArgumentException if the sample array is null + * @throws NumberIsTooSmallException if the length of the array is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error computing the p-value + */ + public boolean tTest(final double mu, final double[] sample, final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return tTest(mu, sample) < alpha; + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a one-sample, two-tailed t-test + * comparing the mean of the dataset described by sampleStats + * with the constant mu. + *

        + * The number returned is the smallest significance level + * at which one can reject the null hypothesis that the mean equals + * mu in favor of the two-sided alternative that the mean + * is different from mu. For a one-sided test, divide the + * returned value by 2.

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The sample must contain at least 2 observations. + *

        + * + * @param mu constant value to compare sample mean against + * @param sampleStats StatisticalSummary describing sample data + * @return p-value + * @throws NullArgumentException if sampleStats is null + * @throws NumberIsTooSmallException if the number of samples is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double tTest(final double mu, final StatisticalSummary sampleStats) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sampleStats); + return tTest(sampleStats.getMean(), mu, sampleStats.getVariance(), + sampleStats.getN()); + + } + + /** + * Performs a + * two-sided t-test evaluating the null hypothesis that the mean of the + * population from which the dataset described by stats is + * drawn equals mu. + *

        + * Returns true iff the null hypothesis can be rejected with + * confidence 1 - alpha. To perform a 1-sided test, use + * alpha * 2.

        + *

        + * Examples:

          + *
        1. To test the (2-sided) hypothesis sample mean = mu at + * the 95% level, use
          tTest(mu, sampleStats, 0.05) + *
        2. + *
        3. To test the (one-sided) hypothesis sample mean < mu + * at the 99% level, first verify that the measured sample mean is less + * than mu and then use + *
          tTest(mu, sampleStats, 0.02) + *

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the one-sample + * parametric t-test procedure, as discussed + * here + *

        + * Preconditions:

          + *
        • The sample must include at least 2 observations. + *

        + * + * @param mu constant value to compare sample mean against + * @param sampleStats StatisticalSummary describing sample data values + * @param alpha significance level of the test + * @return p-value + * @throws NullArgumentException if sampleStats is null + * @throws NumberIsTooSmallException if the number of samples is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean tTest(final double mu, final StatisticalSummary sampleStats, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return tTest(mu, sampleStats) < alpha; + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a two-sample, two-tailed t-test + * comparing the means of the input arrays. + *

        + * 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.

        + *

        + * 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 + * + * here. To perform the test under the assumption of equal subpopulation + * variances, use {@link #homoscedasticTTest(double[], double[])}.

        + *

        + * Usage Note:
        + * The validity of the p-value depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return p-value for t-test + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double tTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sample1); + checkSampleData(sample2); + // No try-catch or advertised exception because args have just been checked + return tTest(StatUtils.mean(sample1), StatUtils.mean(sample2), + StatUtils.variance(sample1), StatUtils.variance(sample2), + sample1.length, sample2.length); + + } + + /** + * Returns the observed significance level, or + * p-value, 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[])}.

        + *

        + * 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.

        + *

        + * 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.

        + *

        + * Usage Note:
        + * The validity of the p-value depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @return p-value for t-test + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double homoscedasticTTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sample1); + checkSampleData(sample2); + // No try-catch or advertised exception because args have just been checked + return homoscedasticTTest(StatUtils.mean(sample1), + StatUtils.mean(sample2), + StatUtils.variance(sample1), + StatUtils.variance(sample2), + sample1.length, sample2.length); + + } + + /** + * Performs a + * + * two-sided t-test evaluating the null hypothesis that sample1 + * and sample2 are drawn from populations with the same mean, + * with significance level alpha. This test does not assume + * that the subpopulation variances are equal. To perform the test assuming + * equal variances, use + * {@link #homoscedasticTTest(double[], double[], double)}. + *

        + * Returns true iff the null hypothesis that the means are + * equal can be rejected with confidence 1 - alpha. To + * perform a 1-sided test, use alpha * 2

        + *

        + * See {@link #t(double[], double[])} for the formula used to compute the + * t-statistic. Degrees of freedom are approximated using the + * + * Welch-Satterthwaite approximation.

        + *

        + * Examples:

          + *
        1. To test the (2-sided) hypothesis mean 1 = mean 2 at + * the 95% level, use + *
          tTest(sample1, sample2, 0.05). + *
        2. + *
        3. To test the (one-sided) hypothesis mean 1 < mean 2 , + * at the 99% level, first verify that the measured mean of sample 1 + * is less than the mean of sample 2 and then use + *
          tTest(sample1, sample2, 0.02) + *

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *
        • + *
        • 0 < alpha < 0.5 + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with + * confidence 1 - alpha + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean tTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return tTest(sample1, sample2) < alpha; + + } + + /** + * Performs a + * + * two-sided t-test evaluating the null hypothesis that sample1 + * and sample2 are drawn from populations with the same mean, + * with significance level alpha, assuming that the + * subpopulation variances are equal. Use + * {@link #tTest(double[], double[], double)} to perform the test without + * the assumption of equal variances. + *

        + * Returns true iff the null hypothesis that the means are + * equal can be rejected with confidence 1 - alpha. To + * perform a 1-sided test, use alpha * 2. To perform the test + * without the assumption of equal subpopulation variances, use + * {@link #tTest(double[], double[], double)}.

        + *

        + * 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.

        + *

        + * Examples:

          + *
        1. To test the (2-sided) hypothesis mean 1 = mean 2 at + * the 95% level, use
          tTest(sample1, sample2, 0.05). + *
        2. + *
        3. To test the (one-sided) hypothesis mean 1 < mean 2, + * at the 99% level, first verify that the measured mean of + * sample 1 is less than the mean of sample 2 + * and then use + *
          tTest(sample1, sample2, 0.02) + *

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The observed array lengths must both be at least 2. + *
        • + *
        • 0 < alpha < 0.5 + *

        + * + * @param sample1 array of sample data values + * @param sample2 array of sample data values + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with + * confidence 1 - alpha + * @throws NullArgumentException if the arrays are null + * @throws NumberIsTooSmallException if the length of the arrays is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean homoscedasticTTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return homoscedasticTTest(sample1, sample2) < alpha; + + } + + /** + * Returns the observed significance level, or + * p-value, associated with a two-sample, two-tailed t-test + * comparing the means of the datasets described by two StatisticalSummary + * instances. + *

        + * 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.

        + *

        + * The test does not assume that the underlying population variances are + * equal and it uses approximated degrees of freedom computed from the + * sample data to compute the p-value. To perform the test assuming + * equal variances, use + * {@link #homoscedasticTTest(StatisticalSummary, StatisticalSummary)}.

        + *

        + * Usage Note:
        + * The validity of the p-value depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The datasets described by the two Univariates must each contain + * at least 2 observations. + *

        + * + * @param sampleStats1 StatisticalSummary describing data from the first sample + * @param sampleStats2 StatisticalSummary describing data from the second sample + * @return p-value for t-test + * @throws NullArgumentException if the sample statistics are null + * @throws NumberIsTooSmallException if the number of samples is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double tTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sampleStats1); + checkSampleData(sampleStats2); + return tTest(sampleStats1.getMean(), sampleStats2.getMean(), + sampleStats1.getVariance(), sampleStats2.getVariance(), + sampleStats1.getN(), sampleStats2.getN()); + + } + + /** + * Returns the observed significance level, or + * p-value, 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)}. + *

        + * 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.

        + *

        + * 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.

        + *

        + * Usage Note:
        + * The validity of the p-value depends on the assumptions of the parametric + * t-test procedure, as discussed + * here + *

        + * Preconditions:

          + *
        • The datasets described by the two Univariates must each contain + * at least 2 observations. + *

        + * + * @param sampleStats1 StatisticalSummary describing data from the first sample + * @param sampleStats2 StatisticalSummary describing data from the second sample + * @return p-value for t-test + * @throws NullArgumentException if the sample statistics are null + * @throws NumberIsTooSmallException if the number of samples is < 2 + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public double homoscedasticTTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + + checkSampleData(sampleStats1); + checkSampleData(sampleStats2); + return homoscedasticTTest(sampleStats1.getMean(), + sampleStats2.getMean(), + sampleStats1.getVariance(), + sampleStats2.getVariance(), + sampleStats1.getN(), sampleStats2.getN()); + + } + + /** + * Performs a + * + * two-sided t-test evaluating the null hypothesis that + * sampleStats1 and sampleStats2 describe + * datasets drawn from populations with the same mean, with significance + * level alpha. 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)}. + *

        + * Returns true iff the null hypothesis that the means are + * equal can be rejected with confidence 1 - alpha. To + * perform a 1-sided test, use alpha * 2

        + *

        + * See {@link #t(double[], double[])} for the formula used to compute the + * t-statistic. Degrees of freedom are approximated using the + * + * Welch-Satterthwaite approximation.

        + *

        + * Examples:

          + *
        1. To test the (2-sided) hypothesis mean 1 = mean 2 at + * the 95%, use + *
          tTest(sampleStats1, sampleStats2, 0.05) + *
        2. + *
        3. To test the (one-sided) hypothesis mean 1 < mean 2 + * at the 99% level, first verify that the measured mean of + * sample 1 is less than the mean of sample 2 + * and then use + *
          tTest(sampleStats1, sampleStats2, 0.02) + *

        + *

        + * Usage Note:
        + * The validity of the test depends on the assumptions of the parametric + * t-test procedure, as discussed + * + * here

        + *

        + * Preconditions:

          + *
        • The datasets described by the two Univariates must each contain + * at least 2 observations. + *
        • + *
        • 0 < alpha < 0.5 + *

        + * + * @param sampleStats1 StatisticalSummary describing sample data values + * @param sampleStats2 StatisticalSummary describing sample data values + * @param alpha significance level of the test + * @return true if the null hypothesis can be rejected with + * confidence 1 - alpha + * @throws NullArgumentException if the sample statistics are null + * @throws NumberIsTooSmallException if the number of samples is < 2 + * @throws OutOfRangeException if alpha is not in the range (0, 0.5] + * @throws MaxCountExceededException if an error occurs computing the p-value + */ + public boolean tTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + + checkSignificanceLevel(alpha); + return tTest(sampleStats1, sampleStats2) < alpha; + + } + + //----------------------------------------------- Protected methods + + /** + * Computes approximate degrees of freedom for 2-sample t-test. + * + * @param v1 first sample variance + * @param v2 second sample variance + * @param n1 first sample n + * @param n2 second sample n + * @return approximate degrees of freedom + */ + protected double df(double v1, double v2, double n1, double n2) { + return (((v1 / n1) + (v2 / n2)) * ((v1 / n1) + (v2 / n2))) / + ((v1 * v1) / (n1 * n1 * (n1 - 1d)) + (v2 * v2) / + (n2 * n2 * (n2 - 1d))); + } + + /** + * Computes t test statistic for 1-sample t-test. + * + * @param m sample mean + * @param mu constant to test against + * @param v sample variance + * @param n sample n + * @return t test statistic + */ + protected double t(final double m, final double mu, + final double v, final double n) { + return (m - mu) / FastMath.sqrt(v / n); + } + + /** + * Computes t test statistic for 2-sample t-test. + *

        + * Does not assume that subpopulation variances are equal.

        + * + * @param m1 first sample mean + * @param m2 second sample mean + * @param v1 first sample variance + * @param v2 second sample variance + * @param n1 first sample n + * @param n2 second sample n + * @return t test statistic + */ + protected double t(final double m1, final double m2, + final double v1, final double v2, + final double n1, final double n2) { + return (m1 - m2) / FastMath.sqrt((v1 / n1) + (v2 / n2)); + } + + /** + * Computes t test statistic for 2-sample t-test under the hypothesis + * of equal subpopulation variances. + * + * @param m1 first sample mean + * @param m2 second sample mean + * @param v1 first sample variance + * @param v2 second sample variance + * @param n1 first sample n + * @param n2 second sample n + * @return t test statistic + */ + protected double homoscedasticT(final double m1, final double m2, + final double v1, final double v2, + final double n1, final double n2) { + final double pooledVariance = ((n1 - 1) * v1 + (n2 -1) * v2 ) / (n1 + n2 - 2); + return (m1 - m2) / FastMath.sqrt(pooledVariance * (1d / n1 + 1d / n2)); + } + + /** + * Computes p-value for 2-sided, 1-sample t-test. + * + * @param m sample mean + * @param mu constant to test against + * @param v sample variance + * @param n sample n + * @return p-value + * @throws MaxCountExceededException if an error occurs computing the p-value + * @throws MathIllegalArgumentException if n is not greater than 1 + */ + protected double tTest(final double m, final double mu, + final double v, final double n) + throws MaxCountExceededException, MathIllegalArgumentException { + + final double t = FastMath.abs(t(m, mu, v, n)); + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final TDistribution distribution = new TDistribution(null, n - 1); + return 2.0 * distribution.cumulativeProbability(-t); + + } + + /** + * Computes p-value for 2-sided, 2-sample t-test. + *

        + * Does not assume subpopulation variances are equal. Degrees of freedom + * are estimated from the data.

        + * + * @param m1 first sample mean + * @param m2 second sample mean + * @param v1 first sample variance + * @param v2 second sample variance + * @param n1 first sample n + * @param n2 second sample n + * @return p-value + * @throws MaxCountExceededException if an error occurs computing the p-value + * @throws NotStrictlyPositiveException if the estimated degrees of freedom is not + * strictly positive + */ + protected double tTest(final double m1, final double m2, + final double v1, final double v2, + final double n1, final double n2) + throws MaxCountExceededException, NotStrictlyPositiveException { + + final double t = FastMath.abs(t(m1, m2, v1, v2, n1, n2)); + final double degreesOfFreedom = df(v1, v2, n1, n2); + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final TDistribution distribution = new TDistribution(null, degreesOfFreedom); + return 2.0 * distribution.cumulativeProbability(-t); + + } + + /** + * Computes p-value for 2-sided, 2-sample t-test, under the assumption + * of equal subpopulation variances. + *

        + * The sum of the sample sizes minus 2 is used as degrees of freedom.

        + * + * @param m1 first sample mean + * @param m2 second sample mean + * @param v1 first sample variance + * @param v2 second sample variance + * @param n1 first sample n + * @param n2 second sample n + * @return p-value + * @throws MaxCountExceededException if an error occurs computing the p-value + * @throws NotStrictlyPositiveException if the estimated degrees of freedom is not + * strictly positive + */ + protected double homoscedasticTTest(double m1, double m2, + double v1, double v2, + double n1, double n2) + throws MaxCountExceededException, NotStrictlyPositiveException { + + final double t = FastMath.abs(homoscedasticT(m1, m2, v1, v2, n1, n2)); + final double degreesOfFreedom = n1 + n2 - 2; + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final TDistribution distribution = new TDistribution(null, degreesOfFreedom); + return 2.0 * distribution.cumulativeProbability(-t); + + } + + /** + * Check significance level. + * + * @param alpha significance level + * @throws OutOfRangeException if the significance level is out of bounds. + */ + private void checkSignificanceLevel(final double alpha) + throws OutOfRangeException { + + if (alpha <= 0 || alpha > 0.5) { + throw new OutOfRangeException(LocalizedFormats.SIGNIFICANCE_LEVEL, + alpha, 0.0, 0.5); + } + + } + + /** + * Check sample data. + * + * @param data Sample data. + * @throws NullArgumentException if {@code data} is {@code null}. + * @throws NumberIsTooSmallException if there is not enough sample data. + */ + private void checkSampleData(final double[] data) + throws NullArgumentException, NumberIsTooSmallException { + + if (data == null) { + throw new NullArgumentException(); + } + if (data.length < 2) { + throw new NumberIsTooSmallException( + LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC, + data.length, 2, true); + } + + } + + /** + * Check sample data. + * + * @param stat Statistical summary. + * @throws NullArgumentException if {@code data} is {@code null}. + * @throws NumberIsTooSmallException if there is not enough sample data. + */ + private void checkSampleData(final StatisticalSummary stat) + throws NullArgumentException, NumberIsTooSmallException { + + if (stat == null) { + throw new NullArgumentException(); + } + if (stat.getN() < 2) { + throw new NumberIsTooSmallException( + LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC, + stat.getN(), 2, true); + } + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TestUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TestUtils.java new file mode 100644 index 000000000..acb7978fb --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/TestUtils.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.math3.distribution.RealDistribution; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; +import com.fr.third.org.apache.commons.math3.stat.descriptive.StatisticalSummary; + +/** + * A collection of static methods to create inference test instances or to + * perform inference tests. + * + * @since 1.1 + */ +public class TestUtils { + + /** Singleton TTest instance. */ + private static final TTest T_TEST = new TTest(); + + /** Singleton ChiSquareTest instance. */ + private static final ChiSquareTest CHI_SQUARE_TEST = new ChiSquareTest(); + + /** Singleton OneWayAnova instance. */ + private static final OneWayAnova ONE_WAY_ANANOVA = new OneWayAnova(); + + /** Singleton G-Test instance. */ + private static final GTest G_TEST = new GTest(); + + /** Singleton K-S test instance */ + private static final KolmogorovSmirnovTest KS_TEST = new KolmogorovSmirnovTest(); + + /** + * Prevent instantiation. + */ + private TestUtils() { + super(); + } + + // CHECKSTYLE: stop JavadocMethodCheck + + /** + * @see TTest#homoscedasticT(double[], double[]) + */ + public static double homoscedasticT(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.homoscedasticT(sample1, sample2); + } + + /** + * @see TTest#homoscedasticT(StatisticalSummary, StatisticalSummary) + */ + public static double homoscedasticT(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.homoscedasticT(sampleStats1, sampleStats2); + } + + /** + * @see TTest#homoscedasticTTest(double[], double[], double) + */ + public static boolean homoscedasticTTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + return T_TEST.homoscedasticTTest(sample1, sample2, alpha); + } + + /** + * @see TTest#homoscedasticTTest(double[], double[]) + */ + public static double homoscedasticTTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException, MaxCountExceededException { + return T_TEST.homoscedasticTTest(sample1, sample2); + } + + /** + * @see TTest#homoscedasticTTest(StatisticalSummary, StatisticalSummary) + */ + public static double homoscedasticTTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException, MaxCountExceededException { + return T_TEST.homoscedasticTTest(sampleStats1, sampleStats2); + } + + /** + * @see TTest#pairedT(double[], double[]) + */ + public static double pairedT(final double[] sample1, final double[] sample2) + throws NullArgumentException, NoDataException, + DimensionMismatchException, NumberIsTooSmallException { + return T_TEST.pairedT(sample1, sample2); + } + + /** + * @see TTest#pairedTTest(double[], double[], double) + */ + public static boolean pairedTTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NoDataException, DimensionMismatchException, + NumberIsTooSmallException, OutOfRangeException, MaxCountExceededException { + return T_TEST.pairedTTest(sample1, sample2, alpha); + } + + /** + * @see TTest#pairedTTest(double[], double[]) + */ + public static double pairedTTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NoDataException, DimensionMismatchException, + NumberIsTooSmallException, MaxCountExceededException { + return T_TEST.pairedTTest(sample1, sample2); + } + + /** + * @see TTest#t(double, double[]) + */ + public static double t(final double mu, final double[] observed) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.t(mu, observed); + } + + /** + * @see TTest#t(double, StatisticalSummary) + */ + public static double t(final double mu, final StatisticalSummary sampleStats) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.t(mu, sampleStats); + } + + /** + * @see TTest#t(double[], double[]) + */ + public static double t(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.t(sample1, sample2); + } + + /** + * @see TTest#t(StatisticalSummary, StatisticalSummary) + */ + public static double t(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException { + return T_TEST.t(sampleStats1, sampleStats2); + } + + /** + * @see TTest#tTest(double, double[], double) + */ + public static boolean tTest(final double mu, final double[] sample, final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + return T_TEST.tTest(mu, sample, alpha); + } + + /** + * @see TTest#tTest(double, double[]) + */ + public static double tTest(final double mu, final double[] sample) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + return T_TEST.tTest(mu, sample); + } + + /** + * @see TTest#tTest(double, StatisticalSummary, double) + */ + public static boolean tTest(final double mu, final StatisticalSummary sampleStats, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + return T_TEST.tTest(mu, sampleStats, alpha); + } + + /** + * @see TTest#tTest(double, StatisticalSummary) + */ + public static double tTest(final double mu, final StatisticalSummary sampleStats) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + return T_TEST.tTest(mu, sampleStats); + } + + /** + * @see TTest#tTest(double[], double[], double) + */ + public static boolean tTest(final double[] sample1, final double[] sample2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + return T_TEST.tTest(sample1, sample2, alpha); + } + + /** + * @see TTest#tTest(double[], double[]) + */ + public static double tTest(final double[] sample1, final double[] sample2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + return T_TEST.tTest(sample1, sample2); + } + + /** + * @see TTest#tTest(StatisticalSummary, StatisticalSummary, double) + */ + public static boolean tTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2, + final double alpha) + throws NullArgumentException, NumberIsTooSmallException, + OutOfRangeException, MaxCountExceededException { + return T_TEST.tTest(sampleStats1, sampleStats2, alpha); + } + + /** + * @see TTest#tTest(StatisticalSummary, StatisticalSummary) + */ + public static double tTest(final StatisticalSummary sampleStats1, + final StatisticalSummary sampleStats2) + throws NullArgumentException, NumberIsTooSmallException, + MaxCountExceededException { + return T_TEST.tTest(sampleStats1, sampleStats2); + } + + /** + * @see ChiSquareTest#chiSquare(double[], long[]) + */ + public static double chiSquare(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException { + return CHI_SQUARE_TEST.chiSquare(expected, observed); + } + + /** + * @see ChiSquareTest#chiSquare(long[][]) + */ + public static double chiSquare(final long[][] counts) + throws NullArgumentException, NotPositiveException, + DimensionMismatchException { + return CHI_SQUARE_TEST.chiSquare(counts); + } + + /** + * @see ChiSquareTest#chiSquareTest(double[], long[], double) + */ + public static boolean chiSquareTest(final double[] expected, final long[] observed, + final double alpha) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, OutOfRangeException, MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTest(expected, observed, alpha); + } + + /** + * @see ChiSquareTest#chiSquareTest(double[], long[]) + */ + public static double chiSquareTest(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTest(expected, observed); + } + + /** + * @see ChiSquareTest#chiSquareTest(long[][], double) + */ + public static boolean chiSquareTest(final long[][] counts, final double alpha) + throws NullArgumentException, DimensionMismatchException, + NotPositiveException, OutOfRangeException, MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTest(counts, alpha); + } + + /** + * @see ChiSquareTest#chiSquareTest(long[][]) + */ + public static double chiSquareTest(final long[][] counts) + throws NullArgumentException, DimensionMismatchException, + NotPositiveException, MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTest(counts); + } + + /** + * @see ChiSquareTest#chiSquareDataSetsComparison(long[], long[]) + * + * @since 1.2 + */ + public static double chiSquareDataSetsComparison(final long[] observed1, + final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException { + return CHI_SQUARE_TEST.chiSquareDataSetsComparison(observed1, observed2); + } + + /** + * @see ChiSquareTest#chiSquareTestDataSetsComparison(long[], long[]) + * + * @since 1.2 + */ + public static double chiSquareTestDataSetsComparison(final long[] observed1, + final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException, + MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTestDataSetsComparison(observed1, observed2); + } + + /** + * @see ChiSquareTest#chiSquareTestDataSetsComparison(long[], long[], double) + * + * @since 1.2 + */ + public static boolean chiSquareTestDataSetsComparison(final long[] observed1, + final long[] observed2, + final double alpha) + throws DimensionMismatchException, NotPositiveException, + ZeroException, OutOfRangeException, MaxCountExceededException { + return CHI_SQUARE_TEST.chiSquareTestDataSetsComparison(observed1, observed2, alpha); + } + + /** + * @see OneWayAnova#anovaFValue(Collection) + * + * @since 1.2 + */ + public static double oneWayAnovaFValue(final Collection categoryData) + throws NullArgumentException, DimensionMismatchException { + return ONE_WAY_ANANOVA.anovaFValue(categoryData); + } + + /** + * @see OneWayAnova#anovaPValue(Collection) + * + * @since 1.2 + */ + public static double oneWayAnovaPValue(final Collection categoryData) + throws NullArgumentException, DimensionMismatchException, + ConvergenceException, MaxCountExceededException { + return ONE_WAY_ANANOVA.anovaPValue(categoryData); + } + + /** + * @see OneWayAnova#anovaTest(Collection,double) + * + * @since 1.2 + */ + public static boolean oneWayAnovaTest(final Collection categoryData, + final double alpha) + throws NullArgumentException, DimensionMismatchException, + OutOfRangeException, ConvergenceException, MaxCountExceededException { + return ONE_WAY_ANANOVA.anovaTest(categoryData, alpha); + } + + /** + * @see GTest#g(double[], long[]) + * @since 3.1 + */ + public static double g(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException { + return G_TEST.g(expected, observed); + } + + /** + * @see GTest#gTest( double[], long[] ) + * @since 3.1 + */ + public static double gTest(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + return G_TEST.gTest(expected, observed); + } + + /** + * @see GTest#gTestIntrinsic(double[], long[] ) + * @since 3.1 + */ + public static double gTestIntrinsic(final double[] expected, final long[] observed) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, MaxCountExceededException { + return G_TEST.gTestIntrinsic(expected, observed); + } + + /** + * @see GTest#gTest( double[],long[],double) + * @since 3.1 + */ + public static boolean gTest(final double[] expected, final long[] observed, + final double alpha) + throws NotPositiveException, NotStrictlyPositiveException, + DimensionMismatchException, OutOfRangeException, MaxCountExceededException { + return G_TEST.gTest(expected, observed, alpha); + } + + /** + * @see GTest#gDataSetsComparison(long[], long[]) + * @since 3.1 + */ + public static double gDataSetsComparison(final long[] observed1, + final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException { + return G_TEST.gDataSetsComparison(observed1, observed2); + } + + /** + * @see GTest#rootLogLikelihoodRatio(long, long, long, long) + * @since 3.1 + */ + public static double rootLogLikelihoodRatio(final long k11, final long k12, final long k21, final long k22) + throws DimensionMismatchException, NotPositiveException, ZeroException { + return G_TEST.rootLogLikelihoodRatio(k11, k12, k21, k22); + } + + + /** + * @see GTest#gTestDataSetsComparison(long[], long[]) + * @since 3.1 + */ + public static double gTestDataSetsComparison(final long[] observed1, + final long[] observed2) + throws DimensionMismatchException, NotPositiveException, ZeroException, + MaxCountExceededException { + return G_TEST.gTestDataSetsComparison(observed1, observed2); + } + + /** + * @see GTest#gTestDataSetsComparison(long[],long[],double) + * @since 3.1 + */ + public static boolean gTestDataSetsComparison(final long[] observed1, + final long[] observed2, + final double alpha) + throws DimensionMismatchException, NotPositiveException, + ZeroException, OutOfRangeException, MaxCountExceededException { + return G_TEST.gTestDataSetsComparison(observed1, observed2, alpha); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovStatistic(RealDistribution, double[]) + * @since 3.3 + */ + public static double kolmogorovSmirnovStatistic(RealDistribution dist, double[] data) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovStatistic(dist, data); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[]) + * @since 3.3 + */ + public static double kolmogorovSmirnovTest(RealDistribution dist, double[] data) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovTest(dist, data); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[], boolean) + * @since 3.3 + */ + public static double kolmogorovSmirnovTest(RealDistribution dist, double[] data, boolean strict) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovTest(dist, data, strict); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[], double) + * @since 3.3 + */ + public static boolean kolmogorovSmirnovTest(RealDistribution dist, double[] data, double alpha) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovTest(dist, data, alpha); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovStatistic(double[], double[]) + * @since 3.3 + */ + public static double kolmogorovSmirnovStatistic(double[] x, double[] y) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovStatistic(x, y); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovTest(double[], double[]) + * @since 3.3 + */ + public static double kolmogorovSmirnovTest(double[] x, double[] y) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovTest(x, y); + } + + /** + * @see KolmogorovSmirnovTest#kolmogorovSmirnovTest(double[], double[], boolean) + * @since 3.3 + */ + public static double kolmogorovSmirnovTest(double[] x, double[] y, boolean strict) + throws InsufficientDataException, NullArgumentException { + return KS_TEST.kolmogorovSmirnovTest(x, y, strict); + } + + /** + * @see KolmogorovSmirnovTest#exactP(double, int, int, boolean) + * @since 3.3 + */ + public static double exactP(double d, int m, int n, boolean strict) { + return KS_TEST.exactP(d, n, m, strict); + } + + /** + * @see KolmogorovSmirnovTest#approximateP(double, int, int) + * @since 3.3 + */ + public static double approximateP(double d, int n, int m) { + return KS_TEST.approximateP(d, n, m); + } + + /** + * @see KolmogorovSmirnovTest#monteCarloP(double, int, int, boolean, int) + * @since 3.3 + */ + public static double monteCarloP(double d, int n, int m, boolean strict, int iterations) { + return KS_TEST.monteCarloP(d, n, m, strict, iterations); + } + + + // CHECKSTYLE: resume JavadocMethodCheck + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java new file mode 100644 index 000000000..d49a38380 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java @@ -0,0 +1,325 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.inference; + +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaNStrategy; +import com.fr.third.org.apache.commons.math3.stat.ranking.TiesStrategy; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.stat.ranking.NaturalRanking; + +/** + * An implementation of the Wilcoxon signed-rank test. + * + */ +public class WilcoxonSignedRankTest { + + /** Ranking algorithm. */ + private NaturalRanking naturalRanking; + + /** + * Create a test instance where NaN's are left in place and ties get + * the average of applicable ranks. Use this unless you are very sure + * of what you are doing. + */ + public WilcoxonSignedRankTest() { + naturalRanking = new NaturalRanking(NaNStrategy.FIXED, + TiesStrategy.AVERAGE); + } + + /** + * Create a test instance using the given strategies for NaN's and ties. + * Only use this if you are sure of what you are doing. + * + * @param nanStrategy + * specifies the strategy that should be used for Double.NaN's + * @param tiesStrategy + * specifies the strategy that should be used for ties + */ + public WilcoxonSignedRankTest(final NaNStrategy nanStrategy, + final TiesStrategy tiesStrategy) { + naturalRanking = new NaturalRanking(nanStrategy, tiesStrategy); + } + + /** + * Ensures that the provided arrays fulfills the assumptions. + * + * @param x first sample + * @param y second sample + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + * @throws DimensionMismatchException if {@code x} and {@code y} do not + * have the same length. + */ + private void ensureDataConformance(final double[] x, final double[] y) + throws NullArgumentException, NoDataException, DimensionMismatchException { + + if (x == null || + y == null) { + throw new NullArgumentException(); + } + if (x.length == 0 || + y.length == 0) { + throw new NoDataException(); + } + if (y.length != x.length) { + throw new DimensionMismatchException(y.length, x.length); + } + } + + /** + * Calculates y[i] - x[i] for all i + * + * @param x first sample + * @param y second sample + * @return z = y - x + */ + private double[] calculateDifferences(final double[] x, final double[] y) { + + final double[] z = new double[x.length]; + + for (int i = 0; i < x.length; ++i) { + z[i] = y[i] - x[i]; + } + + return z; + } + + /** + * Calculates |z[i]| for all i + * + * @param z sample + * @return |z| + * @throws NullArgumentException if {@code z} is {@code null} + * @throws NoDataException if {@code z} is zero-length. + */ + private double[] calculateAbsoluteDifferences(final double[] z) + throws NullArgumentException, NoDataException { + + if (z == null) { + throw new NullArgumentException(); + } + + if (z.length == 0) { + throw new NoDataException(); + } + + final double[] zAbs = new double[z.length]; + + for (int i = 0; i < z.length; ++i) { + zAbs[i] = FastMath.abs(z[i]); + } + + return zAbs; + } + + /** + * Computes the + * Wilcoxon signed ranked statistic comparing mean for two related + * samples or repeated measurements on a single sample. + *

        + * This statistic can be used to perform a Wilcoxon signed ranked test + * evaluating the null hypothesis that the two related samples or repeated + * measurements on a single sample has equal mean. + *

        + *

        + * Let Xi denote the i'th individual of the first sample and + * Yi the related i'th individual in the second sample. Let + * Zi = Yi - Xi. + *

        + *

        + * Preconditions: + *

          + *
        • The differences Zi must be independent.
        • + *
        • Each Zi comes from a continuous population (they must be + * identical) and is symmetric about a common median.
        • + *
        • The values that Xi and Yi represent are + * ordered, so the comparisons greater than, less than, and equal to are + * meaningful.
        • + *
        + *

        + * + * @param x the first sample + * @param y the second sample + * @return wilcoxonSignedRank statistic (the larger of W+ and W-) + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + * @throws DimensionMismatchException if {@code x} and {@code y} do not + * have the same length. + */ + public double wilcoxonSignedRank(final double[] x, final double[] y) + throws NullArgumentException, NoDataException, DimensionMismatchException { + + ensureDataConformance(x, y); + + // throws IllegalArgumentException if x and y are not correctly + // specified + final double[] z = calculateDifferences(x, y); + final double[] zAbs = calculateAbsoluteDifferences(z); + + final double[] ranks = naturalRanking.rank(zAbs); + + double Wplus = 0; + + for (int i = 0; i < z.length; ++i) { + if (z[i] > 0) { + Wplus += ranks[i]; + } + } + + final int N = x.length; + final double Wminus = (((double) (N * (N + 1))) / 2.0) - Wplus; + + return FastMath.max(Wplus, Wminus); + } + + /** + * Algorithm inspired by + * http://www.fon.hum.uva.nl/Service/Statistics/Signed_Rank_Algorihms.html#C + * by Rob van Son, Institute of Phonetic Sciences & IFOTT, + * University of Amsterdam + * + * @param Wmax largest Wilcoxon signed rank value + * @param N number of subjects (corresponding to x.length) + * @return two-sided exact p-value + */ + private double calculateExactPValue(final double Wmax, final int N) { + + // Total number of outcomes (equal to 2^N but a lot faster) + final int m = 1 << N; + + int largerRankSums = 0; + + for (int i = 0; i < m; ++i) { + int rankSum = 0; + + // Generate all possible rank sums + for (int j = 0; j < N; ++j) { + + // (i >> j) & 1 extract i's j-th bit from the right + if (((i >> j) & 1) == 1) { + rankSum += j + 1; + } + } + + if (rankSum >= Wmax) { + ++largerRankSums; + } + } + + /* + * largerRankSums / m gives the one-sided p-value, so it's multiplied + * with 2 to get the two-sided p-value + */ + return 2 * ((double) largerRankSums) / ((double) m); + } + + /** + * @param Wmin smallest Wilcoxon signed rank value + * @param N number of subjects (corresponding to x.length) + * @return two-sided asymptotic p-value + */ + private double calculateAsymptoticPValue(final double Wmin, final int N) { + + final double ES = (double) (N * (N + 1)) / 4.0; + + /* Same as (but saves computations): + * final double VarW = ((double) (N * (N + 1) * (2*N + 1))) / 24; + */ + final double VarS = ES * ((double) (2 * N + 1) / 6.0); + + // - 0.5 is a continuity correction + final double z = (Wmin - ES - 0.5) / FastMath.sqrt(VarS); + + // No try-catch or advertised exception because args are valid + // pass a null rng to avoid unneeded overhead as we will not sample from this distribution + final NormalDistribution standardNormal = new NormalDistribution(null, 0, 1); + + return 2*standardNormal.cumulativeProbability(z); + } + + /** + * Returns the observed significance level, or + * p-value, associated with a + * Wilcoxon signed ranked statistic comparing mean for two related + * samples or repeated measurements on a single sample. + *

        + * Let Xi denote the i'th individual of the first sample and + * Yi the related i'th individual in the second sample. Let + * Zi = Yi - Xi. + *

        + *

        + * Preconditions: + *

          + *
        • The differences Zi must be independent.
        • + *
        • Each Zi comes from a continuous population (they must be + * identical) and is symmetric about a common median.
        • + *
        • The values that Xi and Yi represent are + * ordered, so the comparisons greater than, less than, and equal to are + * meaningful.
        • + *
        + *

        + * + * @param x the first sample + * @param y the second sample + * @param exactPValue + * if the exact p-value is wanted (only works for x.length <= 30, + * if true and x.length > 30, this is ignored because + * calculations may take too long) + * @return p-value + * @throws NullArgumentException if {@code x} or {@code y} are {@code null}. + * @throws NoDataException if {@code x} or {@code y} are zero-length. + * @throws DimensionMismatchException if {@code x} and {@code y} do not + * have the same length. + * @throws NumberIsTooLargeException if {@code exactPValue} is {@code true} + * and {@code x.length} > 30 + * @throws ConvergenceException if the p-value can not be computed due to + * a convergence error + * @throws MaxCountExceededException if the maximum number of iterations + * is exceeded + */ + public double wilcoxonSignedRankTest(final double[] x, final double[] y, + final boolean exactPValue) + throws NullArgumentException, NoDataException, DimensionMismatchException, + NumberIsTooLargeException, ConvergenceException, MaxCountExceededException { + + ensureDataConformance(x, y); + + final int N = x.length; + final double Wmax = wilcoxonSignedRank(x, y); + + if (exactPValue && N > 30) { + throw new NumberIsTooLargeException(N, 30, true); + } + + if (exactPValue) { + return calculateExactPValue(Wmax, N); + } else { + final double Wmin = ( (double)(N*(N+1)) / 2.0 ) - Wmax; + return calculateAsymptoticPValue(Wmin, N); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/package-info.java new file mode 100644 index 000000000..4910a8154 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/inference/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Classes providing hypothesis testing. + * + */ +package com.fr.third.org.apache.commons.math3.stat.inference; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java new file mode 100644 index 000000000..5dd82d8f1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the Agresti-Coull method for creating a binomial proportion confidence interval. + * + * @see + * Agresti-Coull interval (Wikipedia) + * @since 3.3 + */ +public class AgrestiCoullInterval implements BinomialConfidenceInterval { + + /** {@inheritDoc} */ + public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) { + IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel); + final double alpha = (1.0 - confidenceLevel) / 2; + final NormalDistribution normalDistribution = new NormalDistribution(); + final double z = normalDistribution.inverseCumulativeProbability(1 - alpha); + final double zSquared = FastMath.pow(z, 2); + final double modifiedNumberOfTrials = numberOfTrials + zSquared; + final double modifiedSuccessesRatio = (1.0 / modifiedNumberOfTrials) * (numberOfSuccesses + 0.5 * zSquared); + final double difference = z * + FastMath.sqrt(1.0 / modifiedNumberOfTrials * modifiedSuccessesRatio * + (1 - modifiedSuccessesRatio)); + return new ConfidenceInterval(modifiedSuccessesRatio - difference, modifiedSuccessesRatio + difference, + confidenceLevel); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java new file mode 100644 index 000000000..1cd72a9aa --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Interface to generate confidence intervals for a binomial proportion. + * + * @see Binomial + * proportion confidence interval (Wikipedia) + * @since 3.3 + */ +public interface BinomialConfidenceInterval { + + /** + * Create a confidence interval for the true probability of success + * of an unknown binomial distribution with the given observed number + * of trials, successes and confidence level. + *

        + * Preconditions: + *

          + *
        • {@code numberOfTrials} must be positive
        • + *
        • {@code numberOfSuccesses} may not exceed {@code numberOfTrials}
        • + *
        • {@code confidenceLevel} must be strictly between 0 and 1 (exclusive)
        • + *
        + *

        + * + * @param numberOfTrials number of trials + * @param numberOfSuccesses number of successes + * @param confidenceLevel desired probability that the true probability of + * success falls within the returned interval + * @return Confidence interval containing the probability of success with + * probability {@code confidenceLevel} + * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}. + * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}. + */ + ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) + throws NotStrictlyPositiveException, NotPositiveException, + NumberIsTooLargeException, OutOfRangeException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java new file mode 100644 index 000000000..57aefaba6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.distribution.FDistribution; + +/** + * Implements the Clopper-Pearson method for creating a binomial proportion confidence interval. + * + * @see + * Clopper-Pearson interval (Wikipedia) + * @since 3.3 + */ +public class ClopperPearsonInterval implements BinomialConfidenceInterval { + + /** {@inheritDoc} */ + public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel); + double lowerBound = 0; + double upperBound = 0; + final double alpha = (1.0 - confidenceLevel) / 2.0; + + final FDistribution distributionLowerBound = new FDistribution(2 * (numberOfTrials - numberOfSuccesses + 1), + 2 * numberOfSuccesses); + final double fValueLowerBound = distributionLowerBound.inverseCumulativeProbability(1 - alpha); + if (numberOfSuccesses > 0) { + lowerBound = numberOfSuccesses / + (numberOfSuccesses + (numberOfTrials - numberOfSuccesses + 1) * fValueLowerBound); + } + + final FDistribution distributionUpperBound = new FDistribution(2 * (numberOfSuccesses + 1), + 2 * (numberOfTrials - numberOfSuccesses)); + final double fValueUpperBound = distributionUpperBound.inverseCumulativeProbability(1 - alpha); + if (numberOfSuccesses > 0) { + upperBound = (numberOfSuccesses + 1) * fValueUpperBound / + (numberOfTrials - numberOfSuccesses + (numberOfSuccesses + 1) * fValueUpperBound); + } + + return new ConfidenceInterval(lowerBound, upperBound, confidenceLevel); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ConfidenceInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ConfidenceInterval.java new file mode 100644 index 000000000..3db239aa7 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/ConfidenceInterval.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Represents an interval estimate of a population parameter. + * + * @since 3.3 + */ +public class ConfidenceInterval { + + /** Lower endpoint of the interval */ + private double lowerBound; + + /** Upper endpoint of the interval */ + private double upperBound; + + /** + * The asserted probability that the interval contains the population + * parameter + */ + private double confidenceLevel; + + /** + * Create a confidence interval with the given bounds and confidence level. + *

        + * Preconditions: + *

          + *
        • {@code lower} must be strictly less than {@code upper}
        • + *
        • {@code confidenceLevel} must be strictly between 0 and 1 (exclusive)
        • + *
        + *

        + * + * @param lowerBound lower endpoint of the interval + * @param upperBound upper endpoint of the interval + * @param confidenceLevel coverage probability + * @throws MathIllegalArgumentException if the preconditions are not met + */ + public ConfidenceInterval(double lowerBound, double upperBound, double confidenceLevel) { + checkParameters(lowerBound, upperBound, confidenceLevel); + this.lowerBound = lowerBound; + this.upperBound = upperBound; + this.confidenceLevel = confidenceLevel; + } + + /** + * @return the lower endpoint of the interval + */ + public double getLowerBound() { + return lowerBound; + } + + /** + * @return the upper endpoint of the interval + */ + public double getUpperBound() { + return upperBound; + } + + /** + * @return the asserted probability that the interval contains the + * population parameter + */ + public double getConfidenceLevel() { + return confidenceLevel; + } + + /** + * @return String representation of the confidence interval + */ + @Override + public String toString() { + return "[" + lowerBound + ";" + upperBound + "] (confidence level:" + confidenceLevel + ")"; + } + + /** + * Verifies that (lower, upper) is a valid non-empty interval and confidence + * is strictly between 0 and 1. + * + * @param lower lower endpoint + * @param upper upper endpoint + * @param confidence confidence level + */ + private void checkParameters(double lower, double upper, double confidence) { + if (lower >= upper) { + throw new MathIllegalArgumentException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper); + } + if (confidence <= 0 || confidence >= 1) { + throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_BOUNDS_CONFIDENCE_LEVEL, confidence, 0, 1); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/IntervalUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/IntervalUtils.java new file mode 100644 index 000000000..9fbde2fdf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/IntervalUtils.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Factory methods to generate confidence intervals for a binomial proportion. + * The supported methods are: + *
          + *
        • Agresti-Coull interval
        • + *
        • Clopper-Pearson method (exact method)
        • + *
        • Normal approximation (based on central limit theorem)
        • + *
        • Wilson score interval
        • + *
        + * + * @since 3.3 + */ +public final class IntervalUtils { + + /** Singleton Agresti-Coull instance. */ + private static final BinomialConfidenceInterval AGRESTI_COULL = new AgrestiCoullInterval(); + + /** Singleton Clopper-Pearson instance. */ + private static final BinomialConfidenceInterval CLOPPER_PEARSON = new ClopperPearsonInterval(); + + /** Singleton NormalApproximation instance. */ + private static final BinomialConfidenceInterval NORMAL_APPROXIMATION = new NormalApproximationInterval(); + + /** Singleton Wilson score instance. */ + private static final BinomialConfidenceInterval WILSON_SCORE = new WilsonScoreInterval(); + + /** + * Prevent instantiation. + */ + private IntervalUtils() { + } + + /** + * Create an Agresti-Coull binomial confidence interval for the true + * probability of success of an unknown binomial distribution with the given + * observed number of trials, successes and confidence level. + * + * @param numberOfTrials number of trials + * @param numberOfSuccesses number of successes + * @param confidenceLevel desired probability that the true probability of + * success falls within the returned interval + * @return Confidence interval containing the probability of success with + * probability {@code confidenceLevel} + * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}. + * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}. + */ + public static ConfidenceInterval getAgrestiCoullInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + return AGRESTI_COULL.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel); + } + + /** + * Create a Clopper-Pearson binomial confidence interval for the true + * probability of success of an unknown binomial distribution with the given + * observed number of trials, successes and confidence level. + *

        + * Preconditions: + *

          + *
        • {@code numberOfTrials} must be positive
        • + *
        • {@code numberOfSuccesses} may not exceed {@code numberOfTrials}
        • + *
        • {@code confidenceLevel} must be strictly between 0 and 1 (exclusive)
        • + *
        + *

        + * + * @param numberOfTrials number of trials + * @param numberOfSuccesses number of successes + * @param confidenceLevel desired probability that the true probability of + * success falls within the returned interval + * @return Confidence interval containing the probability of success with + * probability {@code confidenceLevel} + * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}. + * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}. + */ + public static ConfidenceInterval getClopperPearsonInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + return CLOPPER_PEARSON.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel); + } + + /** + * Create a binomial confidence interval for the true probability of success + * of an unknown binomial distribution with the given observed number of + * trials, successes and confidence level using the Normal approximation to + * the binomial distribution. + * + * @param numberOfTrials number of trials + * @param numberOfSuccesses number of successes + * @param confidenceLevel desired probability that the true probability of + * success falls within the interval + * @return Confidence interval containing the probability of success with + * probability {@code confidenceLevel} + */ + public static ConfidenceInterval getNormalApproximationInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + return NORMAL_APPROXIMATION.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel); + } + + /** + * Create a Wilson score binomial confidence interval for the true + * probability of success of an unknown binomial distribution with the given + * observed number of trials, successes and confidence level. + * + * @param numberOfTrials number of trials + * @param numberOfSuccesses number of successes + * @param confidenceLevel desired probability that the true probability of + * success falls within the returned interval + * @return Confidence interval containing the probability of success with + * probability {@code confidenceLevel} + * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}. + * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}. + */ + public static ConfidenceInterval getWilsonScoreInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + return WILSON_SCORE.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel); + } + + /** + * Verifies that parameters satisfy preconditions. + * + * @param numberOfTrials number of trials (must be positive) + * @param numberOfSuccesses number of successes (must not exceed numberOfTrials) + * @param confidenceLevel confidence level (must be strictly between 0 and 1) + * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}. + * @throws NotPositiveException if {@code numberOfSuccesses < 0}. + * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}. + * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}. + */ + static void checkParameters(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) { + if (numberOfTrials <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_TRIALS, numberOfTrials); + } + if (numberOfSuccesses < 0) { + throw new NotPositiveException(LocalizedFormats.NEGATIVE_NUMBER_OF_SUCCESSES, numberOfSuccesses); + } + if (numberOfSuccesses > numberOfTrials) { + throw new NumberIsTooLargeException(LocalizedFormats.NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE, + numberOfSuccesses, numberOfTrials, true); + } + if (confidenceLevel <= 0 || confidenceLevel >= 1) { + throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUNDS_CONFIDENCE_LEVEL, + confidenceLevel, 0, 1); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java new file mode 100644 index 000000000..94b6e58f2 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the normal approximation method for creating a binomial proportion confidence interval. + * + * @see + * Normal approximation interval (Wikipedia) + * @since 3.3 + */ +public class NormalApproximationInterval implements BinomialConfidenceInterval { + + /** {@inheritDoc} */ + public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, + double confidenceLevel) { + IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel); + final double mean = (double) numberOfSuccesses / (double) numberOfTrials; + final double alpha = (1.0 - confidenceLevel) / 2; + final NormalDistribution normalDistribution = new NormalDistribution(); + final double difference = normalDistribution.inverseCumulativeProbability(1 - alpha) * + FastMath.sqrt(1.0 / numberOfTrials * mean * (1 - mean)); + return new ConfidenceInterval(mean - difference, mean + difference, confidenceLevel); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java new file mode 100644 index 000000000..961530b3f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.interval; + +import com.fr.third.org.apache.commons.math3.distribution.NormalDistribution; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the Wilson score method for creating a binomial proportion confidence interval. + * + * @see + * Wilson score interval (Wikipedia) + * @since 3.3 + */ +public class WilsonScoreInterval implements BinomialConfidenceInterval { + + /** {@inheritDoc} */ + public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) { + IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel); + final double alpha = (1.0 - confidenceLevel) / 2; + final NormalDistribution normalDistribution = new NormalDistribution(); + final double z = normalDistribution.inverseCumulativeProbability(1 - alpha); + final double zSquared = FastMath.pow(z, 2); + final double mean = (double) numberOfSuccesses / (double) numberOfTrials; + + final double factor = 1.0 / (1 + (1.0 / numberOfTrials) * zSquared); + final double modifiedSuccessRatio = mean + (1.0 / (2 * numberOfTrials)) * zSquared; + final double difference = z * + FastMath.sqrt(1.0 / numberOfTrials * mean * (1 - mean) + + (1.0 / (4 * FastMath.pow(numberOfTrials, 2)) * zSquared)); + + final double lowerBound = factor * (modifiedSuccessRatio - difference); + final double upperBound = factor * (modifiedSuccessRatio + difference); + return new ConfidenceInterval(lowerBound, upperBound, confidenceLevel); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/package-info.java new file mode 100644 index 000000000..658abd437 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/interval/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Classes providing binomial proportion confidence interval construction. + * + */ +package com.fr.third.org.apache.commons.math3.stat.interval; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/package-info.java new file mode 100644 index 000000000..8bb4874e9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Data storage, manipulation and summary routines. + */ +package com.fr.third.org.apache.commons.math3.stat; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaNStrategy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaNStrategy.java new file mode 100644 index 000000000..2b9710cf4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaNStrategy.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.ranking; + +/** + * Strategies for handling NaN values in rank transformations. + *
          + *
        • MINIMAL - NaNs are treated as minimal in the ordering, equivalent to + * (that is, tied with) Double.NEGATIVE_INFINITY.
        • + *
        • MAXIMAL - NaNs are treated as maximal in the ordering, equivalent to + * Double.POSITIVE_INFINITY
        • + *
        • REMOVED - NaNs are removed before the rank transform is applied
        • + *
        • 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.
        • + *
        • FAILED - If any NaN is encountered in the input array, an appropriate + * exception is thrown
        • + *
        + * + * @since 2.0 + */ +public enum NaNStrategy { + + /** NaNs are considered minimal in the ordering */ + MINIMAL, + + /** NaNs are considered maximal in the ordering */ + MAXIMAL, + + /** NaNs are removed before computing ranks */ + REMOVED, + + /** NaNs are left in place */ + FIXED, + + /** NaNs result in an exception + * @since 3.1 + */ + FAILED +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaturalRanking.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaturalRanking.java new file mode 100644 index 000000000..72ffa5104 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/NaturalRanking.java @@ -0,0 +1,474 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.ranking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.random.RandomDataGenerator; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.util.FastMath; + + +/** + *

        Ranking based on the natural ordering on doubles.

        + *

        NaNs are treated according to the configured {@link NaNStrategy} and ties + * are handled using the selected {@link TiesStrategy}. + * Configuration settings are supplied in optional constructor arguments. + * Defaults are {@link NaNStrategy#FAILED} and {@link TiesStrategy#AVERAGE}, + * respectively. When using {@link TiesStrategy#RANDOM}, a + * {@link RandomGenerator} may be supplied as a constructor argument.

        + *

        Examples: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        + * Input data: (20, 17, 30, 42.3, 17, 50, Double.NaN, Double.NEGATIVE_INFINITY, 17) + *
        NaNStrategyTiesStrategyrank(data)
        default (NaNs maximal)default (ties averaged)(5, 3, 6, 7, 3, 8, 9, 1, 3)
        default (NaNs maximal)MINIMUM(5, 2, 6, 7, 2, 8, 9, 1, 2)
        MINIMALdefault (ties averaged)(6, 4, 7, 8, 4, 9, 1.5, 1.5, 4)
        REMOVEDSEQUENTIAL(5, 2, 6, 7, 3, 8, 1, 4)
        MINIMALMAXIMUM(6, 5, 7, 8, 5, 9, 2, 2, 5)

        + * + * @since 2.0 + */ +public class NaturalRanking implements RankingAlgorithm { + + /** default NaN strategy */ + public static final NaNStrategy DEFAULT_NAN_STRATEGY = NaNStrategy.FAILED; + + /** default ties strategy */ + public static final TiesStrategy DEFAULT_TIES_STRATEGY = TiesStrategy.AVERAGE; + + /** NaN strategy - defaults to NaNs maximal */ + private final NaNStrategy nanStrategy; + + /** Ties strategy - defaults to ties averaged */ + private final TiesStrategy tiesStrategy; + + /** Source of random data - used only when ties strategy is RANDOM */ + private final RandomDataGenerator randomData; + + /** + * Create a NaturalRanking with default strategies for handling ties and NaNs. + */ + public NaturalRanking() { + super(); + tiesStrategy = DEFAULT_TIES_STRATEGY; + nanStrategy = DEFAULT_NAN_STRATEGY; + randomData = null; + } + + /** + * Create a NaturalRanking with the given TiesStrategy. + * + * @param tiesStrategy the TiesStrategy to use + */ + public NaturalRanking(TiesStrategy tiesStrategy) { + super(); + this.tiesStrategy = tiesStrategy; + nanStrategy = DEFAULT_NAN_STRATEGY; + randomData = new RandomDataGenerator(); + } + + /** + * Create a NaturalRanking with the given NaNStrategy. + * + * @param nanStrategy the NaNStrategy to use + */ + public NaturalRanking(NaNStrategy nanStrategy) { + super(); + this.nanStrategy = nanStrategy; + tiesStrategy = DEFAULT_TIES_STRATEGY; + randomData = null; + } + + /** + * Create a NaturalRanking with the given NaNStrategy and TiesStrategy. + * + * @param nanStrategy NaNStrategy to use + * @param tiesStrategy TiesStrategy to use + */ + public NaturalRanking(NaNStrategy nanStrategy, TiesStrategy tiesStrategy) { + super(); + this.nanStrategy = nanStrategy; + this.tiesStrategy = tiesStrategy; + randomData = new RandomDataGenerator(); + } + + /** + * Create a NaturalRanking with TiesStrategy.RANDOM and the given + * RandomGenerator as the source of random data. + * + * @param randomGenerator source of random data + */ + public NaturalRanking(RandomGenerator randomGenerator) { + super(); + this.tiesStrategy = TiesStrategy.RANDOM; + nanStrategy = DEFAULT_NAN_STRATEGY; + randomData = new RandomDataGenerator(randomGenerator); + } + + + /** + * Create a NaturalRanking with the given NaNStrategy, TiesStrategy.RANDOM + * and the given source of random data. + * + * @param nanStrategy NaNStrategy to use + * @param randomGenerator source of random data + */ + public NaturalRanking(NaNStrategy nanStrategy, + RandomGenerator randomGenerator) { + super(); + this.nanStrategy = nanStrategy; + this.tiesStrategy = TiesStrategy.RANDOM; + randomData = new RandomDataGenerator(randomGenerator); + } + + /** + * Return the NaNStrategy + * + * @return returns the NaNStrategy + */ + public NaNStrategy getNanStrategy() { + return nanStrategy; + } + + /** + * Return the TiesStrategy + * + * @return the TiesStrategy + */ + public TiesStrategy getTiesStrategy() { + return tiesStrategy; + } + + /** + * Rank data using the natural ordering on Doubles, with + * NaN values handled according to nanStrategy and ties + * resolved using tiesStrategy. + * + * @param data array to be ranked + * @return array of ranks + * @throws NotANumberException if the selected {@link NaNStrategy} is {@code FAILED} + * and a {@link Double#NaN} is encountered in the input data + */ + public double[] rank(double[] data) { + + // Array recording initial positions of data to be ranked + IntDoublePair[] ranks = new IntDoublePair[data.length]; + for (int i = 0; i < data.length; i++) { + ranks[i] = new IntDoublePair(data[i], i); + } + + // Recode, remove or record positions of NaNs + List nanPositions = null; + switch (nanStrategy) { + case MAXIMAL: // Replace NaNs with +INFs + recodeNaNs(ranks, Double.POSITIVE_INFINITY); + break; + case MINIMAL: // Replace NaNs with -INFs + recodeNaNs(ranks, Double.NEGATIVE_INFINITY); + break; + case REMOVED: // Drop NaNs from data + ranks = removeNaNs(ranks); + break; + case FIXED: // Record positions of NaNs + nanPositions = getNanPositions(ranks); + break; + case FAILED: + nanPositions = getNanPositions(ranks); + if (nanPositions.size() > 0) { + throw new NotANumberException(); + } + break; + default: // this should not happen unless NaNStrategy enum is changed + throw new MathInternalError(); + } + + // Sort the IntDoublePairs + Arrays.sort(ranks); + + // Walk the sorted array, filling output array using sorted positions, + // resolving ties as we go + double[] out = new double[ranks.length]; + int pos = 1; // position in sorted array + out[ranks[0].getPosition()] = pos; + List tiesTrace = new ArrayList(); + 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(); + 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 ranks. + * + * @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 ranks array is expected to take the same value + * for all indices in tiesTrace. 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 ranks is constant + * -- that is, for any i and j in TiesTrace, ranks[i] == ranks[j] + * + */ + private void resolveTie(double[] ranks, List 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 iterator = tiesTrace.iterator(); + long f = FastMath.round(c); + while (iterator.hasNext()) { + // No advertised exception because args are guaranteed valid + ranks[iterator.next()] = + randomData.nextLong(f, f + length - 1); + } + break; + case SEQUENTIAL: // Fill sequentially from c to c + length - 1 + // walk and fill + iterator = tiesTrace.iterator(); + f = FastMath.round(c); + int i = 0; + while (iterator.hasNext()) { + ranks[iterator.next()] = f + i++; + } + break; + default: // this should not happen unless TiesStrategy enum is changed + throw new MathInternalError(); + } + } + + /** + * Setsdata[i] = value for each i in tiesTrace. + * + * @param data array to modify + * @param tiesTrace list of index values to set + * @param value value to set + */ + private void fill(double[] data, List tiesTrace, double value) { + Iterator iterator = tiesTrace.iterator(); + while (iterator.hasNext()) { + data[iterator.next()] = value; + } + } + + /** + * Set ranks[i] = Double.NaN for each i in nanPositions. + * + * @param ranks array to modify + * @param nanPositions list of index values to set to Double.NaN + */ + private void restoreNaNs(double[] ranks, List nanPositions) { + if (nanPositions.size() == 0) { + return; + } + Iterator iterator = nanPositions.iterator(); + while (iterator.hasNext()) { + ranks[iterator.next().intValue()] = Double.NaN; + } + + } + + /** + * Returns a list of indexes where ranks is NaN. + * + * @param ranks array to search for NaNs + * @return list of indexes i such that ranks[i] = NaN + */ + private List getNanPositions(IntDoublePair[] ranks) { + ArrayList out = new ArrayList(); + 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 { + + /** Value of the pair */ + private final double value; + + /** Original position of the pair */ + private final int position; + + /** + * Construct an IntDoublePair with the given value and position. + * @param value the value of the pair + * @param position the original position + */ + IntDoublePair(double value, int position) { + this.value = value; + this.position = position; + } + + /** + * Compare this IntDoublePair to another pair. + * Only the values are compared. + * + * @param other the other pair to compare this to + * @return result of Double.compare(value, other.value) + */ + public int compareTo(IntDoublePair other) { + return Double.compare(value, other.value); + } + + // N.B. equals() and hashCode() are not implemented; see MATH-610 for discussion. + + /** + * Returns the value of the pair. + * @return value + */ + public double getValue() { + return value; + } + + /** + * Returns the original position of the pair. + * @return position + */ + public int getPosition() { + return position; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java new file mode 100644 index 000000000..096b904a5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.ranking; + +/** + * Interface representing a rank transformation. + * + * @since 2.0 + */ +public interface RankingAlgorithm { + /** + *

        Performs a rank transformation on the input data, returning an array + * of ranks.

        + * + *

        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.

        + * + * @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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/TiesStrategy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/TiesStrategy.java new file mode 100644 index 000000000..972ebe029 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/TiesStrategy.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.ranking; + +/** + * Strategies for handling tied values in rank transformations. + *
          + *
        • 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)
        • + *
        • 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)
        • + *
        • 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)
        • + *
        • 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)
        • + *
        • 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.
        • + *
        + * + * @since 2.0 + */ +public enum TiesStrategy { + + /** Ties assigned sequential ranks in order of occurrence */ + SEQUENTIAL, + + /** Ties get the minimum applicable rank */ + MINIMUM, + + /** Ties get the maximum applicable rank */ + MAXIMUM, + + /** Ties get the average of applicable ranks */ + AVERAGE, + + /** Ties get a random integral value from among applicable ranks */ + RANDOM +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/package-info.java new file mode 100644 index 000000000..9fca10fa6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/ranking/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Classes providing rank transformations. + * + */ +package com.fr.third.org.apache.commons.math3.stat.ranking; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java new file mode 100644 index 000000000..223dd748c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.InsufficientDataException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.ArrayRealVector; +import com.fr.third.org.apache.commons.math3.linear.NonSquareMatrixException; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.Variance; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Abstract base class for implementations of MultipleLinearRegression. + * @since 2.0 + */ +public abstract class AbstractMultipleLinearRegression implements + MultipleLinearRegression { + + /** X sample data. */ + private RealMatrix xMatrix; + + /** Y sample data. */ + private RealVector yVector; + + /** Whether or not the regression model includes an intercept. True means no intercept. */ + private boolean noIntercept = false; + + /** + * @return the X sample data. + */ + protected RealMatrix getX() { + return xMatrix; + } + + /** + * @return the Y sample data. + */ + protected RealVector getY() { + return yVector; + } + + /** + * @return true if the model has no intercept term; false otherwise + * @since 2.2 + */ + public boolean isNoIntercept() { + return noIntercept; + } + + /** + * @param noIntercept true means the model is to be estimated without an intercept term + * @since 2.2 + */ + public void setNoIntercept(boolean noIntercept) { + this.noIntercept = noIntercept; + } + + /** + *

        Loads model x and y sample data from a flat input array, overriding any previous sample. + *

        + *

        Assumes that rows are concatenated with y values first in each row. For example, an input + * data array containing the sequence of values (1, 2, 3, 4, 5, 6, 7, 8, 9) with + * nobs = 3 and nvars = 2 creates a regression dataset with two + * independent variables, as below: + *

        +     *   y   x[0]  x[1]
        +     *   --------------
        +     *   1     2     3
        +     *   4     5     6
        +     *   7     8     9
        +     * 
        + *

        + *

        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 true, + * the X matrix will be created without an initial column of "1"s; otherwise this column will + * be added. + *

        + *

        Throws IllegalArgumentException if any of the following preconditions fail: + *

        • data cannot be null
        • + *
        • data.length = nobs * (nvars + 1)
        • + *
        • nobs > nvars
        + *

        + * + * @param data input data array + * @param nobs number of observations (rows) + * @param nvars number of independent variables (columns, not counting y) + * @throws NullArgumentException if the data array is null + * @throws DimensionMismatchException if the length of the data array is not equal + * to nobs * (nvars + 1) + * @throws InsufficientDataException if nobs is less than + * nvars + 1 + */ + public void newSampleData(double[] data, int nobs, int nvars) { + if (data == null) { + throw new NullArgumentException(); + } + if (data.length != nobs * (nvars + 1)) { + throw new DimensionMismatchException(data.length, nobs * (nvars + 1)); + } + if (nobs <= nvars) { + throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, nobs, nvars + 1); + } + double[] y = new double[nobs]; + final int cols = noIntercept ? nvars: nvars + 1; + double[][] x = new double[nobs][cols]; + int pointer = 0; + for (int i = 0; i < nobs; i++) { + y[i] = data[pointer++]; + if (!noIntercept) { + x[i][0] = 1.0d; + } + for (int j = noIntercept ? 0 : 1; j < cols; j++) { + x[i][j] = data[pointer++]; + } + } + this.xMatrix = new Array2DRowRealMatrix(x); + this.yVector = new ArrayRealVector(y); + } + + /** + * Loads new y sample data, overriding any previous data. + * + * @param y the array representing the y sample + * @throws NullArgumentException if y is null + * @throws NoDataException if y is empty + */ + protected void newYSampleData(double[] y) { + if (y == null) { + throw new NullArgumentException(); + } + if (y.length == 0) { + throw new NoDataException(); + } + this.yVector = new ArrayRealVector(y); + } + + /** + *

        Loads new x sample data, overriding any previous data. + *

        + * The input x array should have one row for each sample + * observation, with columns corresponding to independent variables. + * For example, if
        +     *  x = new double[][] {{1, 2}, {3, 4}, {5, 6}} 
        + * then setXSampleData(x) results in a model with two independent + * variables and 3 observations: + *
        +     *   x[0]  x[1]
        +     *   ----------
        +     *     1    2
        +     *     3    4
        +     *     5    6
        +     * 
        + *

        + *

        Note that there is no need to add an initial unitary column (column of 1's) when + * specifying a model including an intercept term. + *

        + * @param x the rectangular array representing the x sample + * @throws NullArgumentException if x is null + * @throws NoDataException if x is empty + * @throws DimensionMismatchException if x is not rectangular + */ + protected void newXSampleData(double[][] x) { + if (x == null) { + throw new NullArgumentException(); + } + if (x.length == 0) { + throw new NoDataException(); + } + if (noIntercept) { + this.xMatrix = new Array2DRowRealMatrix(x, true); + } else { // Augment design matrix with initial unitary column + final int nVars = x[0].length; + final double[][] xAug = new double[x.length][nVars + 1]; + for (int i = 0; i < x.length; i++) { + if (x[i].length != nVars) { + throw new DimensionMismatchException(x[i].length, nVars); + } + xAug[i][0] = 1.0d; + System.arraycopy(x[i], 0, xAug[i], 1, nVars); + } + this.xMatrix = new Array2DRowRealMatrix(xAug, false); + } + } + + /** + * Validates sample data. Checks that + *
        • Neither x nor y is null or empty;
        • + *
        • The length (i.e. number of rows) of x equals the length of y
        • + *
        • 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.
        • + *
        + * + * @param x the [n,k] array representing the x data + * @param y the [n,1] array representing the y data + * @throws NullArgumentException if {@code x} or {@code y} is null + * @throws DimensionMismatchException if {@code x} and {@code y} do not + * have the same length + * @throws NoDataException if {@code x} or {@code y} are zero-length + * @throws MathIllegalArgumentException if the number of rows of {@code x} + * is not larger than the number of columns + 1 + */ + protected void validateSampleData(double[][] x, double[] y) throws MathIllegalArgumentException { + if ((x == null) || (y == null)) { + throw new NullArgumentException(); + } + if (x.length != y.length) { + throw new DimensionMismatchException(y.length, x.length); + } + if (x.length == 0) { // Must be no y data either + throw new NoDataException(); + } + if (x[0].length + 1 > x.length) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, + x.length, x[0].length); + } + } + + /** + * Validates that the x data and covariance matrix have the same + * number of rows and that the covariance matrix is square. + * + * @param x the [n,k] array representing the x sample + * @param covariance the [n,n] array representing the covariance matrix + * @throws DimensionMismatchException if the number of rows in x is not equal + * to the number of rows in covariance + * @throws NonSquareMatrixException if the covariance matrix is not square + */ + protected void validateCovarianceData(double[][] x, double[][] covariance) { + if (x.length != covariance.length) { + throw new DimensionMismatchException(x.length, covariance.length); + } + if (covariance.length > 0 && covariance.length != covariance[0].length) { + throw new NonSquareMatrixException(covariance.length, covariance[0].length); + } + } + + /** + * {@inheritDoc} + */ + public double[] estimateRegressionParameters() { + RealVector b = calculateBeta(); + return b.toArray(); + } + + /** + * {@inheritDoc} + */ + public double[] estimateResiduals() { + RealVector b = calculateBeta(); + RealVector e = yVector.subtract(xMatrix.operate(b)); + return e.toArray(); + } + + /** + * {@inheritDoc} + */ + public double[][] estimateRegressionParametersVariance() { + return calculateBetaVariance().getData(); + } + + /** + * {@inheritDoc} + */ + public double[] estimateRegressionParametersStandardErrors() { + double[][] betaVariance = estimateRegressionParametersVariance(); + double sigma = calculateErrorVariance(); + int length = betaVariance[0].length; + double[] result = new double[length]; + for (int i = 0; i < length; i++) { + result[i] = FastMath.sqrt(sigma * betaVariance[i][i]); + } + return result; + } + + /** + * {@inheritDoc} + */ + public double estimateRegressandVariance() { + return calculateYVariance(); + } + + /** + * Estimates the variance of the error. + * + * @return estimate of the error variance + * @since 2.2 + */ + public double estimateErrorVariance() { + return calculateErrorVariance(); + + } + + /** + * Estimates the standard error of the regression. + * + * @return regression standard error + * @since 2.2 + */ + public double estimateRegressionStandardError() { + return FastMath.sqrt(estimateErrorVariance()); + } + + /** + * Calculates the beta of multiple linear regression in matrix notation. + * + * @return beta + */ + protected abstract RealVector calculateBeta(); + + /** + * Calculates the beta variance of multiple linear regression in matrix + * notation. + * + * @return beta variance + */ + protected abstract RealMatrix calculateBetaVariance(); + + + /** + * Calculates the variance of the y values. + * + * @return Y variance + */ + protected double calculateYVariance() { + return new Variance().evaluate(yVector.toArray()); + } + + /** + *

        Calculates the variance of the error term.

        + * Uses the formula
        +     * var(u) = u · u / (n - k)
        +     * 
        + * where n and k are the row and column dimensions of the design + * matrix X. + * + * @return error variance estimate + * @since 2.2 + */ + protected double calculateErrorVariance() { + RealVector residuals = calculateResiduals(); + return residuals.dotProduct(residuals) / + (xMatrix.getRowDimension() - xMatrix.getColumnDimension()); + } + + /** + * Calculates the residuals of multiple linear regression in matrix + * notation. + * + *
        +     * u = y - X * b
        +     * 
        + * + * @return The residuals [n,1] matrix + */ + protected RealVector calculateResiduals() { + RealVector b = calculateBeta(); + return yVector.subtract(xMatrix.operate(b)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java new file mode 100644 index 000000000..3ddb5f167 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.LUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; + +/** + * The GLS implementation of multiple linear regression. + * + * GLS assumes a general covariance matrix Omega of the error + *
        + * u ~ N(0, Omega)
        + * 
        + * + * Estimated by GLS, + *
        + * b=(X' Omega^-1 X)^-1X'Omega^-1 y
        + * 
        + * whose variance is + *
        + * Var(b)=(X' Omega^-1 X)^-1
        + * 
        + * @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. + *

        The inverse of the covariance matrix is lazily evaluated and cached.

        + * @return inverse of the covariance + */ + protected RealMatrix getOmegaInverse() { + if (OmegaInverse == null) { + OmegaInverse = new LUDecomposition(Omega).getSolver().getInverse(); + } + return OmegaInverse; + } + + /** + * Calculates beta by GLS. + *
        +     *  b=(X' Omega^-1 X)^-1X'Omega^-1 y
        +     * 
        + * @return beta + */ + @Override + protected RealVector calculateBeta() { + RealMatrix OI = getOmegaInverse(); + RealMatrix XT = getX().transpose(); + RealMatrix XTOIX = XT.multiply(OI).multiply(getX()); + RealMatrix inverse = new LUDecomposition(XTOIX).getSolver().getInverse(); + return inverse.multiply(XT).multiply(OI).operate(getY()); + } + + /** + * Calculates the variance on the beta. + *
        +     *  Var(b)=(X' Omega^-1 X)^-1
        +     * 
        + * @return The beta variance matrix + */ + @Override + protected RealMatrix calculateBetaVariance() { + RealMatrix OI = getOmegaInverse(); + RealMatrix XTOIX = getX().transpose().multiply(OI).multiply(getX()); + return new LUDecomposition(XTOIX).getSolver().getInverse(); + } + + + /** + * Calculates the estimated variance of the error term using the formula + *
        +     *  Var(u) = Tr(u' Omega^-1 u)/(n-k)
        +     * 
        + * where n and k are the row and column dimensions of the design + * matrix X. + * + * @return error variance + * @since 2.2 + */ + @Override + protected double calculateErrorVariance() { + RealVector residuals = calculateResiduals(); + double t = residuals.dotProduct(getOmegaInverse().operate(residuals)); + return t / (getX().getRowDimension() - getX().getColumnDimension()); + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java new file mode 100644 index 000000000..01e9aabdf --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java @@ -0,0 +1,1102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * This class is a concrete implementation of the {@link UpdatingMultipleLinearRegression} interface. + * + *

        The algorithm is described in:

        + * Algorithm AS 274: Least Squares Routines to Supplement Those of Gentleman
        + * Author(s): Alan J. Miller
        + * Source: Journal of the Royal Statistical Society.
        + * Series C (Applied Statistics), Vol. 41, No. 2
        + * (1992), pp. 458-478
        + * Published by: Blackwell Publishing for the Royal Statistical Society
        + * Stable URL: http://www.jstor.org/stable/2347583 

        + * + *

        This method for multiple regression forms the solution to the OLS problem + * by updating the QR decomposition as described by Gentleman.

        + * + * @since 3.0 + */ +public class MillerUpdatingRegression implements UpdatingMultipleLinearRegression { + + /** number of variables in regression */ + private final int nvars; + /** diagonals of cross products matrix */ + private final double[] d; + /** the elements of the R`Y */ + private final double[] rhs; + /** the off diagonal portion of the R matrix */ + private final double[] r; + /** the tolerance for each of the variables */ + private final double[] tol; + /** residual sum of squares for all nested regressions */ + private final double[] rss; + /** order of the regressors */ + private final int[] vorder; + /** scratch space for tolerance calc */ + private final double[] work_tolset; + /** number of observations entered */ + private long nobs = 0; + /** sum of squared errors of largest regression */ + private double sserr = 0.0; + /** has rss been called? */ + private boolean rss_set = false; + /** has the tolerance setting method been called */ + private boolean tol_set = false; + /** flags for variables with linear dependency problems */ + private final boolean[] lindep; + /** singular x values */ + private final double[] x_sing; + /** workspace for singularity method */ + private final double[] work_sing; + /** summation of Y variable */ + private double sumy = 0.0; + /** summation of squared Y values */ + private double sumsqy = 0.0; + /** boolean flag whether a regression constant is added */ + private boolean hasIntercept; + /** zero tolerance */ + private final double epsilon; + /** + * Set the default constructor to private access + * to prevent inadvertent instantiation + */ + @SuppressWarnings("unused") + private MillerUpdatingRegression() { + this(-1, false, Double.NaN); + } + + /** + * This is the augmented constructor for the MillerUpdatingRegression class. + * + * @param numberOfVariables number of regressors to expect, not including constant + * @param includeConstant include a constant automatically + * @param errorTolerance zero tolerance, how machine zero is determined + * @throws ModelSpecificationException if {@code numberOfVariables is less than 1} + */ + public MillerUpdatingRegression(int numberOfVariables, boolean includeConstant, double errorTolerance) + throws ModelSpecificationException { + if (numberOfVariables < 1) { + throw new ModelSpecificationException(LocalizedFormats.NO_REGRESSORS); + } + if (includeConstant) { + this.nvars = numberOfVariables + 1; + } else { + this.nvars = numberOfVariables; + } + this.hasIntercept = includeConstant; + this.nobs = 0; + this.d = new double[this.nvars]; + this.rhs = new double[this.nvars]; + this.r = new double[this.nvars * (this.nvars - 1) / 2]; + this.tol = new double[this.nvars]; + this.rss = new double[this.nvars]; + this.vorder = new int[this.nvars]; + this.x_sing = new double[this.nvars]; + this.work_sing = new double[this.nvars]; + this.work_tolset = new double[this.nvars]; + this.lindep = new boolean[this.nvars]; + for (int i = 0; i < this.nvars; i++) { + vorder[i] = i; + } + if (errorTolerance > 0) { + this.epsilon = errorTolerance; + } else { + this.epsilon = -errorTolerance; + } + } + + /** + * Primary constructor for the MillerUpdatingRegression. + * + * @param numberOfVariables maximum number of potential regressors + * @param includeConstant include a constant automatically + * @throws ModelSpecificationException if {@code numberOfVariables is less than 1} + */ + public MillerUpdatingRegression(int numberOfVariables, boolean includeConstant) + throws ModelSpecificationException { + this(numberOfVariables, includeConstant, Precision.EPSILON); + } + + /** + * A getter method which determines whether a constant is included. + * @return true regression has an intercept, false no intercept + */ + public boolean hasIntercept() { + return this.hasIntercept; + } + + /** + * Gets the number of observations added to the regression model. + * @return number of observations + */ + public long getN() { + return this.nobs; + } + + /** + * Adds an observation to the regression model. + * @param x the array with regressor values + * @param y the value of dependent variable given these regressors + * @exception ModelSpecificationException if the length of {@code x} does not equal + * the number of independent variables in the model + */ + public void addObservation(final double[] x, final double y) + throws ModelSpecificationException { + + if ((!this.hasIntercept && x.length != nvars) || + (this.hasIntercept && x.length + 1 != nvars)) { + throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION, + x.length, nvars); + } + if (!this.hasIntercept) { + include(MathArrays.copyOf(x, x.length), 1.0, y); + } else { + final double[] tmp = new double[x.length + 1]; + System.arraycopy(x, 0, tmp, 1, x.length); + tmp[0] = 1.0; + include(tmp, 1.0, y); + } + ++nobs; + + } + + /** + * Adds multiple observations to the model. + * @param x observations on the regressors + * @param y observations on the regressand + * @throws ModelSpecificationException if {@code x} is not rectangular, does not match + * the length of {@code y} or does not contain sufficient data to estimate the model + */ + public void addObservations(double[][] x, double[] y) throws ModelSpecificationException { + if ((x == null) || (y == null) || (x.length != y.length)) { + throw new ModelSpecificationException( + LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, + (x == null) ? 0 : x.length, + (y == null) ? 0 : y.length); + } + if (x.length == 0) { // Must be no y data either + throw new ModelSpecificationException( + LocalizedFormats.NO_DATA); + } + if (x[0].length + 1 > x.length) { + throw new ModelSpecificationException( + LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, + x.length, x[0].length); + } + for (int i = 0; i < x.length; i++) { + addObservation(x[i], y[i]); + } + } + + /** + * The include method is where the QR decomposition occurs. This statement forms all + * intermediate data which will be used for all derivative measures. + * According to the miller paper, note that in the original implementation the x vector + * is overwritten. In this implementation, the include method is passed a copy of the + * original data vector so that there is no contamination of the data. Additionally, + * this method differs slightly from Gentleman's method, in that the assumption is + * of dense design matrices, there is some advantage in using the original gentleman algorithm + * on sparse matrices. + * + * @param x observations on the regressors + * @param wi weight of the this observation (-1,1) + * @param yi observation on the regressand + */ + private void include(final double[] x, final double wi, final double yi) { + int nextr = 0; + double w = wi; + double y = yi; + double xi; + double di; + double wxi; + double dpi; + double xk; + double _w; + this.rss_set = false; + sumy = smartAdd(yi, sumy); + sumsqy = smartAdd(sumsqy, yi * yi); + for (int i = 0; i < x.length; i++) { + if (w == 0.0) { + return; + } + xi = x[i]; + + if (xi == 0.0) { + nextr += nvars - i - 1; + continue; + } + di = d[i]; + wxi = w * xi; + _w = w; + if (di != 0.0) { + dpi = smartAdd(di, wxi * xi); + final double tmp = wxi * xi / di; + if (FastMath.abs(tmp) > Precision.EPSILON) { + w = (di * w) / dpi; + } + } else { + dpi = wxi * xi; + w = 0.0; + } + d[i] = dpi; + for (int k = i + 1; k < nvars; k++) { + xk = x[k]; + x[k] = smartAdd(xk, -xi * r[nextr]); + if (di != 0.0) { + r[nextr] = smartAdd(di * r[nextr], (_w * xi) * xk) / dpi; + } else { + r[nextr] = xk / xi; + } + ++nextr; + } + xk = y; + y = smartAdd(xk, -xi * rhs[i]); + if (di != 0.0) { + rhs[i] = smartAdd(di * rhs[i], wxi * xk) / dpi; + } else { + rhs[i] = xk / xi; + } + } + sserr = smartAdd(sserr, w * y * y); + } + + /** + * Adds to number a and b such that the contamination due to + * numerical smallness of one addend does not corrupt the sum. + * @param a - an addend + * @param b - an addend + * @return the sum of the a and b + */ + private double smartAdd(double a, double b) { + final double _a = FastMath.abs(a); + final double _b = FastMath.abs(b); + if (_a > _b) { + final double eps = _a * Precision.EPSILON; + if (_b > eps) { + return a + b; + } + return a; + } else { + final double eps = _b * Precision.EPSILON; + if (_a > eps) { + return a + b; + } + return b; + } + } + + /** + * As the name suggests, clear wipes the internals and reorders everything in the + * canonical order. + */ + public void clear() { + Arrays.fill(this.d, 0.0); + Arrays.fill(this.rhs, 0.0); + Arrays.fill(this.r, 0.0); + Arrays.fill(this.tol, 0.0); + Arrays.fill(this.rss, 0.0); + Arrays.fill(this.work_tolset, 0.0); + Arrays.fill(this.work_sing, 0.0); + Arrays.fill(this.x_sing, 0.0); + Arrays.fill(this.lindep, false); + for (int i = 0; i < nvars; i++) { + this.vorder[i] = i; + } + this.nobs = 0; + this.sserr = 0.0; + this.sumy = 0.0; + this.sumsqy = 0.0; + this.rss_set = false; + this.tol_set = false; + } + + /** + * This sets up tolerances for singularity testing. + */ + private void tolset() { + int pos; + double total; + final double eps = this.epsilon; + for (int i = 0; i < nvars; i++) { + this.work_tolset[i] = FastMath.sqrt(d[i]); + } + tol[0] = eps * this.work_tolset[0]; + for (int col = 1; col < nvars; col++) { + pos = col - 1; + total = work_tolset[col]; + for (int row = 0; row < col; row++) { + total += FastMath.abs(r[pos]) * work_tolset[row]; + pos += nvars - row - 2; + } + tol[col] = eps * total; + } + tol_set = true; + } + + /** + * The regcf method conducts the linear regression and extracts the + * parameter vector. Notice that the algorithm can do subset regression + * with no alteration. + * + * @param nreq how many of the regressors to include (either in canonical + * order, or in the current reordered state) + * @return an array with the estimated slope coefficients + * @throws ModelSpecificationException if {@code nreq} is less than 1 + * or greater than the number of independent variables + */ + private double[] regcf(int nreq) throws ModelSpecificationException { + int nextr; + if (nreq < 1) { + throw new ModelSpecificationException(LocalizedFormats.NO_REGRESSORS); + } + if (nreq > this.nvars) { + throw new ModelSpecificationException( + LocalizedFormats.TOO_MANY_REGRESSORS, nreq, this.nvars); + } + if (!this.tol_set) { + tolset(); + } + final double[] ret = new double[nreq]; + boolean rankProblem = false; + for (int i = nreq - 1; i > -1; i--) { + if (FastMath.sqrt(d[i]) < tol[i]) { + ret[i] = 0.0; + d[i] = 0.0; + rankProblem = true; + } else { + ret[i] = rhs[i]; + nextr = i * (nvars + nvars - i - 1) / 2; + for (int j = i + 1; j < nreq; j++) { + ret[i] = smartAdd(ret[i], -r[nextr] * ret[j]); + ++nextr; + } + } + } + if (rankProblem) { + for (int i = 0; i < nreq; i++) { + if (this.lindep[i]) { + ret[i] = Double.NaN; + } + } + } + return ret; + } + + /** + * The method which checks for singularities and then eliminates the offending + * columns. + */ + private void singcheck() { + int pos; + for (int i = 0; i < nvars; i++) { + work_sing[i] = FastMath.sqrt(d[i]); + } + for (int col = 0; col < nvars; col++) { + // Set elements within R to zero if they are less than tol(col) in + // absolute value after being scaled by the square root of their row + // multiplier + final double temp = tol[col]; + pos = col - 1; + for (int row = 0; row < col - 1; row++) { + if (FastMath.abs(r[pos]) * work_sing[row] < temp) { + r[pos] = 0.0; + } + pos += nvars - row - 2; + } + // If diagonal element is near zero, set it to zero, set appropriate + // element of LINDEP, and use INCLUD to augment the projections in + // the lower rows of the orthogonalization. + lindep[col] = false; + if (work_sing[col] < temp) { + lindep[col] = true; + if (col < nvars - 1) { + Arrays.fill(x_sing, 0.0); + int _pi = col * (nvars + nvars - col - 1) / 2; + for (int _xi = col + 1; _xi < nvars; _xi++, _pi++) { + x_sing[_xi] = r[_pi]; + r[_pi] = 0.0; + } + final double y = rhs[col]; + final double weight = d[col]; + d[col] = 0.0; + rhs[col] = 0.0; + this.include(x_sing, weight, y); + } else { + sserr += d[col] * rhs[col] * rhs[col]; + } + } + } + } + + /** + * Calculates the sum of squared errors for the full regression + * and all subsets in the following manner:
        +     * rss[] ={
        +     * ResidualSumOfSquares_allNvars,
        +     * ResidualSumOfSquares_FirstNvars-1,
        +     * ResidualSumOfSquares_FirstNvars-2,
        +     * ..., ResidualSumOfSquares_FirstVariable} 
        + */ + private void ss() { + double total = sserr; + rss[nvars - 1] = sserr; + for (int i = nvars - 1; i > 0; i--) { + total += d[i] * rhs[i] * rhs[i]; + rss[i - 1] = total; + } + rss_set = true; + } + + /** + * Calculates the cov matrix assuming only the first nreq variables are + * included in the calculation. The returned array contains a symmetric + * matrix stored in lower triangular form. The matrix will have + * ( nreq + 1 ) * nreq / 2 elements. For illustration
        +     * cov =
        +     * {
        +     *  cov_00,
        +     *  cov_10, cov_11,
        +     *  cov_20, cov_21, cov22,
        +     *  ...
        +     * } 
        + * + * @param nreq how many of the regressors to include (either in canonical + * order, or in the current reordered state) + * @return an array with the variance covariance of the included + * regressors in lower triangular form + */ + private double[] cov(int nreq) { + if (this.nobs <= nreq) { + return null; + } + double rnk = 0.0; + for (int i = 0; i < nreq; i++) { + if (!this.lindep[i]) { + rnk += 1.0; + } + } + final double var = rss[nreq - 1] / (nobs - rnk); + final double[] rinv = new double[nreq * (nreq - 1) / 2]; + inverse(rinv, nreq); + final double[] covmat = new double[nreq * (nreq + 1) / 2]; + Arrays.fill(covmat, Double.NaN); + int pos2; + int pos1; + int start = 0; + double total = 0; + for (int row = 0; row < nreq; row++) { + pos2 = start; + if (!this.lindep[row]) { + for (int col = row; col < nreq; col++) { + if (!this.lindep[col]) { + pos1 = start + col - row; + if (row == col) { + total = 1.0 / d[col]; + } else { + total = rinv[pos1 - 1] / d[col]; + } + for (int k = col + 1; k < nreq; k++) { + if (!this.lindep[k]) { + total += rinv[pos1] * rinv[pos2] / d[k]; + } + ++pos1; + ++pos2; + } + covmat[ (col + 1) * col / 2 + row] = total * var; + } else { + pos2 += nreq - col - 1; + } + } + } + start += nreq - row - 1; + } + return covmat; + } + + /** + * This internal method calculates the inverse of the upper-triangular portion + * of the R matrix. + * @param rinv the storage for the inverse of r + * @param nreq how many of the regressors to include (either in canonical + * order, or in the current reordered state) + */ + private void inverse(double[] rinv, int nreq) { + int pos = nreq * (nreq - 1) / 2 - 1; + int pos1 = -1; + int pos2 = -1; + double total = 0.0; + Arrays.fill(rinv, Double.NaN); + for (int row = nreq - 1; row > 0; --row) { + if (!this.lindep[row]) { + final int start = (row - 1) * (nvars + nvars - row) / 2; + for (int col = nreq; col > row; --col) { + pos1 = start; + pos2 = pos; + total = 0.0; + for (int k = row; k < col - 1; k++) { + pos2 += nreq - k - 1; + if (!this.lindep[k]) { + total += -r[pos1] * rinv[pos2]; + } + ++pos1; + } + rinv[pos] = total - r[pos1]; + --pos; + } + } else { + pos -= nreq - row; + } + } + } + + /** + * In the original algorithm only the partial correlations of the regressors + * is returned to the user. In this implementation, we have
        +     * corr =
        +     * {
        +     *   corrxx - lower triangular
        +     *   corrxy - bottom row of the matrix
        +     * }
        +     * Replaces subroutines PCORR and COR of:
        +     * ALGORITHM AS274  APPL. STATIST. (1992) VOL.41, NO. 2 
        + * + *

        Calculate partial correlations after the variables in rows + * 1, 2, ..., IN have been forced into the regression. + * If IN = 1, and the first row of R represents a constant in the + * model, then the usual simple correlations are returned.

        + * + *

        If IN = 0, the value returned in array CORMAT for the correlation + * of variables Xi & Xj is:

        +     * sum ( Xi.Xj ) / Sqrt ( sum (Xi^2) . sum (Xj^2) )

        + * + *

        On return, array CORMAT contains the upper triangle of the matrix of + * partial correlations stored by rows, excluding the 1's on the diagonal. + * e.g. if IN = 2, the consecutive elements returned are: + * (3,4) (3,5) ... (3,ncol), (4,5) (4,6) ... (4,ncol), etc. + * Array YCORR stores the partial correlations with the Y-variable + * starting with YCORR(IN+1) = partial correlation with the variable in + * position (IN+1).

        + * + * @param in how many of the regressors to include (either in canonical + * order, or in the current reordered state) + * @return an array with the partial correlations of the remainder of + * regressors with each other and the regressand, in lower triangular form + */ + public double[] getPartialCorrelations(int in) { + final double[] output = new double[(nvars - in + 1) * (nvars - in) / 2]; + int pos; + int pos1; + int pos2; + final int rms_off = -in; + final int wrk_off = -(in + 1); + final double[] rms = new double[nvars - in]; + final double[] work = new double[nvars - in - 1]; + double sumxx; + double sumxy; + double sumyy; + final int offXX = (nvars - in) * (nvars - in - 1) / 2; + if (in < -1 || in >= nvars) { + return null; + } + final int nvm = nvars - 1; + final int base_pos = r.length - (nvm - in) * (nvm - in + 1) / 2; + if (d[in] > 0.0) { + rms[in + rms_off] = 1.0 / FastMath.sqrt(d[in]); + } + for (int col = in + 1; col < nvars; col++) { + pos = base_pos + col - 1 - in; + sumxx = d[col]; + for (int row = in; row < col; row++) { + sumxx += d[row] * r[pos] * r[pos]; + pos += nvars - row - 2; + } + if (sumxx > 0.0) { + rms[col + rms_off] = 1.0 / FastMath.sqrt(sumxx); + } else { + rms[col + rms_off] = 0.0; + } + } + sumyy = sserr; + for (int row = in; row < nvars; row++) { + sumyy += d[row] * rhs[row] * rhs[row]; + } + if (sumyy > 0.0) { + sumyy = 1.0 / FastMath.sqrt(sumyy); + } + pos = 0; + for (int col1 = in; col1 < nvars; col1++) { + sumxy = 0.0; + Arrays.fill(work, 0.0); + pos1 = base_pos + col1 - in - 1; + for (int row = in; row < col1; row++) { + pos2 = pos1 + 1; + for (int col2 = col1 + 1; col2 < nvars; col2++) { + work[col2 + wrk_off] += d[row] * r[pos1] * r[pos2]; + pos2++; + } + sumxy += d[row] * r[pos1] * rhs[row]; + pos1 += nvars - row - 2; + } + pos2 = pos1 + 1; + for (int col2 = col1 + 1; col2 < nvars; col2++) { + work[col2 + wrk_off] += d[col1] * r[pos2]; + ++pos2; + output[ (col2 - 1 - in) * (col2 - in) / 2 + col1 - in] = + work[col2 + wrk_off] * rms[col1 + rms_off] * rms[col2 + rms_off]; + ++pos; + } + sumxy += d[col1] * rhs[col1]; + output[col1 + rms_off + offXX] = sumxy * rms[col1 + rms_off] * sumyy; + } + + return output; + } + + /** + * ALGORITHM AS274 APPL. STATIST. (1992) VOL.41, NO. 2. + * Move variable from position FROM to position TO in an + * orthogonal reduction produced by AS75.1. + * + * @param from initial position + * @param to destination + */ + private void vmove(int from, int to) { + double d1; + double d2; + double X; + double d1new; + double d2new; + double cbar; + double sbar; + double Y; + int first; + int inc; + int m1; + int m2; + int mp1; + int pos; + boolean bSkipTo40 = false; + if (from == to) { + return; + } + if (!this.rss_set) { + ss(); + } + int count = 0; + if (from < to) { + first = from; + inc = 1; + count = to - from; + } else { + first = from - 1; + inc = -1; + count = from - to; + } + + int m = first; + int idx = 0; + while (idx < count) { + m1 = m * (nvars + nvars - m - 1) / 2; + m2 = m1 + nvars - m - 1; + mp1 = m + 1; + + d1 = d[m]; + d2 = d[mp1]; + // Special cases. + if (d1 > this.epsilon || d2 > this.epsilon) { + X = r[m1]; + if (FastMath.abs(X) * FastMath.sqrt(d1) < tol[mp1]) { + X = 0.0; + } + if (d1 < this.epsilon || FastMath.abs(X) < this.epsilon) { + d[m] = d2; + d[mp1] = d1; + r[m1] = 0.0; + for (int col = m + 2; col < nvars; col++) { + ++m1; + X = r[m1]; + r[m1] = r[m2]; + r[m2] = X; + ++m2; + } + X = rhs[m]; + rhs[m] = rhs[mp1]; + rhs[mp1] = X; + bSkipTo40 = true; + //break; + } else if (d2 < this.epsilon) { + d[m] = d1 * X * X; + r[m1] = 1.0 / X; + for (int _i = m1 + 1; _i < m1 + nvars - m - 1; _i++) { + r[_i] /= X; + } + rhs[m] /= X; + bSkipTo40 = true; + //break; + } + if (!bSkipTo40) { + d1new = d2 + d1 * X * X; + cbar = d2 / d1new; + sbar = X * d1 / d1new; + d2new = d1 * cbar; + d[m] = d1new; + d[mp1] = d2new; + r[m1] = sbar; + for (int col = m + 2; col < nvars; col++) { + ++m1; + Y = r[m1]; + r[m1] = cbar * r[m2] + sbar * Y; + r[m2] = Y - X * r[m2]; + ++m2; + } + Y = rhs[m]; + rhs[m] = cbar * rhs[mp1] + sbar * Y; + rhs[mp1] = Y - X * rhs[mp1]; + } + } + if (m > 0) { + pos = m; + for (int row = 0; row < m; row++) { + X = r[pos]; + r[pos] = r[pos - 1]; + r[pos - 1] = X; + pos += nvars - row - 2; + } + } + // Adjust variable order (VORDER), the tolerances (TOL) and + // the vector of residual sums of squares (RSS). + m1 = vorder[m]; + vorder[m] = vorder[mp1]; + vorder[mp1] = m1; + X = tol[m]; + tol[m] = tol[mp1]; + tol[mp1] = X; + rss[m] = rss[mp1] + d[mp1] * rhs[mp1] * rhs[mp1]; + + m += inc; + ++idx; + } + } + + /** + * ALGORITHM AS274 APPL. STATIST. (1992) VOL.41, NO. 2 + * + *

        Re-order the variables in an orthogonal reduction produced by + * AS75.1 so that the N variables in LIST start at position POS1, + * though will not necessarily be in the same order as in LIST. + * Any variables in VORDER before position POS1 are not moved. + * Auxiliary routine called: VMOVE.

        + * + *

        This internal method reorders the regressors.

        + * + * @param list the regressors to move + * @param pos1 where the list will be placed + * @return -1 error, 0 everything ok + */ + private int reorderRegressors(int[] list, int pos1) { + int next; + int i; + int l; + if (list.length < 1 || list.length > nvars + 1 - pos1) { + return -1; + } + next = pos1; + i = pos1; + while (i < nvars) { + l = vorder[i]; + for (int j = 0; j < list.length; j++) { + if (l == list[j] && i > next) { + this.vmove(i, next); + ++next; + if (next >= list.length + pos1) { + return 0; + } else { + break; + } + } + } + ++i; + } + return 0; + } + + /** + * Gets the diagonal of the Hat matrix also known as the leverage matrix. + * + * @param row_data returns the diagonal of the hat matrix for this observation + * @return the diagonal element of the hatmatrix + */ + public double getDiagonalOfHatMatrix(double[] row_data) { + double[] wk = new double[this.nvars]; + int pos; + double total; + + if (row_data.length > nvars) { + return Double.NaN; + } + double[] xrow; + if (this.hasIntercept) { + xrow = new double[row_data.length + 1]; + xrow[0] = 1.0; + System.arraycopy(row_data, 0, xrow, 1, row_data.length); + } else { + xrow = row_data; + } + double hii = 0.0; + for (int col = 0; col < xrow.length; col++) { + if (FastMath.sqrt(d[col]) < tol[col]) { + wk[col] = 0.0; + } else { + pos = col - 1; + total = xrow[col]; + for (int row = 0; row < col; row++) { + total = smartAdd(total, -wk[row] * r[pos]); + pos += nvars - row - 2; + } + wk[col] = total; + hii = smartAdd(hii, (total * total) / d[col]); + } + } + return hii; + } + + /** + * Gets the order of the regressors, useful if some type of reordering + * has been called. Calling regress with int[]{} args will trigger + * a reordering. + * + * @return int[] with the current order of the regressors + */ + public int[] getOrderOfRegressors(){ + return MathArrays.copyOf(vorder); + } + + /** + * Conducts a regression on the data in the model, using all regressors. + * + * @return RegressionResults the structure holding all regression results + * @exception ModelSpecificationException - thrown if number of observations is + * less than the number of variables + */ + public RegressionResults regress() throws ModelSpecificationException { + return regress(this.nvars); + } + + /** + * Conducts a regression on the data in the model, using a subset of regressors. + * + * @param numberOfRegressors many of the regressors to include (either in canonical + * order, or in the current reordered state) + * @return RegressionResults the structure holding all regression results + * @exception ModelSpecificationException - thrown if number of observations is + * less than the number of variables or number of regressors requested + * is greater than the regressors in the model + */ + public RegressionResults regress(int numberOfRegressors) throws ModelSpecificationException { + if (this.nobs <= numberOfRegressors) { + throw new ModelSpecificationException( + LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, + this.nobs, numberOfRegressors); + } + if( numberOfRegressors > this.nvars ){ + throw new ModelSpecificationException( + LocalizedFormats.TOO_MANY_REGRESSORS, numberOfRegressors, this.nvars); + } + + tolset(); + singcheck(); + + double[] beta = this.regcf(numberOfRegressors); + + ss(); + + double[] cov = this.cov(numberOfRegressors); + + int rnk = 0; + for (int i = 0; i < this.lindep.length; i++) { + if (!this.lindep[i]) { + ++rnk; + } + } + + boolean needsReorder = false; + for (int i = 0; i < numberOfRegressors; i++) { + if (this.vorder[i] != i) { + needsReorder = true; + break; + } + } + if (!needsReorder) { + return new RegressionResults( + beta, new double[][]{cov}, true, this.nobs, rnk, + this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false); + } else { + double[] betaNew = new double[beta.length]; + double[] covNew = new double[cov.length]; + + int[] newIndices = new int[beta.length]; + for (int i = 0; i < nvars; i++) { + for (int j = 0; j < numberOfRegressors; j++) { + if (this.vorder[j] == i) { + betaNew[i] = beta[ j]; + newIndices[i] = j; + } + } + } + + int idx1 = 0; + int idx2; + int _i; + int _j; + for (int i = 0; i < beta.length; i++) { + _i = newIndices[i]; + for (int j = 0; j <= i; j++, idx1++) { + _j = newIndices[j]; + if (_i > _j) { + idx2 = _i * (_i + 1) / 2 + _j; + } else { + idx2 = _j * (_j + 1) / 2 + _i; + } + covNew[idx1] = cov[idx2]; + } + } + return new RegressionResults( + betaNew, new double[][]{covNew}, true, this.nobs, rnk, + this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false); + } + } + + /** + * Conducts a regression on the data in the model, using regressors in array + * Calling this method will change the internal order of the regressors + * and care is required in interpreting the hatmatrix. + * + * @param variablesToInclude array of variables to include in regression + * @return RegressionResults the structure holding all regression results + * @exception ModelSpecificationException - thrown if number of observations is + * less than the number of variables, the number of regressors requested + * is greater than the regressors in the model or a regressor index in + * regressor array does not exist + */ + public RegressionResults regress(int[] variablesToInclude) throws ModelSpecificationException { + if (variablesToInclude.length > this.nvars) { + throw new ModelSpecificationException( + LocalizedFormats.TOO_MANY_REGRESSORS, variablesToInclude.length, this.nvars); + } + if (this.nobs <= this.nvars) { + throw new ModelSpecificationException( + LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, + this.nobs, this.nvars); + } + Arrays.sort(variablesToInclude); + int iExclude = 0; + for (int i = 0; i < variablesToInclude.length; i++) { + if (i >= this.nvars) { + throw new ModelSpecificationException( + LocalizedFormats.INDEX_LARGER_THAN_MAX, i, this.nvars); + } + if (i > 0 && variablesToInclude[i] == variablesToInclude[i - 1]) { + variablesToInclude[i] = -1; + ++iExclude; + } + } + int[] series; + if (iExclude > 0) { + int j = 0; + series = new int[variablesToInclude.length - iExclude]; + for (int i = 0; i < variablesToInclude.length; i++) { + if (variablesToInclude[i] > -1) { + series[j] = variablesToInclude[i]; + ++j; + } + } + } else { + series = variablesToInclude; + } + + reorderRegressors(series, 0); + tolset(); + singcheck(); + + double[] beta = this.regcf(series.length); + + ss(); + + double[] cov = this.cov(series.length); + + int rnk = 0; + for (int i = 0; i < this.lindep.length; i++) { + if (!this.lindep[i]) { + ++rnk; + } + } + + boolean needsReorder = false; + for (int i = 0; i < this.nvars; i++) { + if (this.vorder[i] != series[i]) { + needsReorder = true; + break; + } + } + if (!needsReorder) { + return new RegressionResults( + beta, new double[][]{cov}, true, this.nobs, rnk, + this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false); + } else { + double[] betaNew = new double[beta.length]; + int[] newIndices = new int[beta.length]; + for (int i = 0; i < series.length; i++) { + for (int j = 0; j < this.vorder.length; j++) { + if (this.vorder[j] == series[i]) { + betaNew[i] = beta[ j]; + newIndices[i] = j; + } + } + } + double[] covNew = new double[cov.length]; + int idx1 = 0; + int idx2; + int _i; + int _j; + for (int i = 0; i < beta.length; i++) { + _i = newIndices[i]; + for (int j = 0; j <= i; j++, idx1++) { + _j = newIndices[j]; + if (_i > _j) { + idx2 = _i * (_i + 1) / 2 + _j; + } else { + idx2 = _j * (_j + 1) / 2 + _i; + } + covNew[idx1] = cov[idx2]; + } + } + return new RegressionResults( + betaNew, new double[][]{covNew}, true, this.nobs, rnk, + this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/ModelSpecificationException.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/ModelSpecificationException.java new file mode 100644 index 000000000..3ee41fe5d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/ModelSpecificationException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; + +/** + * Exception thrown when a regression model is not correctly specified. + * + * @since 3.0 + */ +public class ModelSpecificationException extends MathIllegalArgumentException { + /** Serializable version Id. */ + private static final long serialVersionUID = 4206514456095401070L; + + /** + * @param pattern message pattern describing the specification error. + * + * @param args arguments. + */ + public ModelSpecificationException(Localizable pattern, + Object ... args) { + super(pattern, args); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java new file mode 100644 index 000000000..fef10b2a0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +/** + * The multiple linear regression can be represented in matrix-notation. + *
        + *  y=X*b+u
        + * 
        + * where y is an n-vector regressand, X is a [n,k] matrix whose k columns are called + * regressors, b is k-vector of regression parameters and u is an n-vector + * of error terms or residuals. + * + * The notation is quite standard in literature, + * cf eg Davidson and MacKinnon, Econometrics Theory and Methods, 2004. + * @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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.java new file mode 100644 index 000000000..689eec8c3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.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 com.fr.third.org.apache.commons.math3.stat.regression; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.linear.Array2DRowRealMatrix; +import com.fr.third.org.apache.commons.math3.linear.LUDecomposition; +import com.fr.third.org.apache.commons.math3.linear.QRDecomposition; +import com.fr.third.org.apache.commons.math3.linear.RealMatrix; +import com.fr.third.org.apache.commons.math3.linear.RealVector; +import com.fr.third.org.apache.commons.math3.linear.SingularMatrixException; +import com.fr.third.org.apache.commons.math3.stat.descriptive.moment.SecondMoment; +import com.fr.third.org.apache.commons.math3.stat.StatUtils; + +/** + *

        Implements ordinary least squares (OLS) to estimate the parameters of a + * multiple linear regression model.

        + * + *

        The regression coefficients, b, satisfy the normal equations: + *

         XT X b = XT y 

        + * + *

        To solve the normal equations, this implementation uses QR decomposition + * of the X matrix. (See {@link QRDecomposition} for details on the + * decomposition algorithm.) The X matrix, also known as the design matrix, + * 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 X + * matrix includes an initial column identically equal to 1. We solve the normal equations + * as follows: + *

         XTX b = XT y
        + * (QR)T (QR) b = (QR)Ty
        + * RT (QTQ) R b = RT QT y
        + * RT R b = RT QT y
        + * (RT)-1 RT R b = (RT)-1 RT QT y
        + * R b = QT y 

        + * + *

        Given Q and R, the last equation is solved by back-substitution.

        + * + * @since 2.0 + */ +public class OLSMultipleLinearRegression extends AbstractMultipleLinearRegression { + + /** Cached QR decomposition of X matrix */ + private QRDecomposition qr = null; + + /** Singularity threshold for QR decomposition */ + private final double threshold; + + /** + * Create an empty OLSMultipleLinearRegression instance. + */ + public OLSMultipleLinearRegression() { + this(0d); + } + + /** + * Create an empty OLSMultipleLinearRegression instance, using the given + * singularity threshold for the QR decomposition. + * + * @param threshold the singularity threshold + * @since 3.3 + */ + public OLSMultipleLinearRegression(final double threshold) { + this.threshold = threshold; + } + + /** + * Loads model x and y sample data, overriding any previous sample. + * + * Computes and caches QR decomposition of the X matrix. + * @param y the [n,1] array representing the y sample + * @param x the [n,k] array representing the x sample + * @throws MathIllegalArgumentException if the x and y array data are not + * compatible for the regression + */ + public void newSampleData(double[] y, double[][] x) throws MathIllegalArgumentException { + validateSampleData(x, y); + newYSampleData(y); + newXSampleData(x); + } + + /** + * {@inheritDoc} + *

        This implementation computes and caches the QR decomposition of the X matrix.

        + */ + @Override + public void newSampleData(double[] data, int nobs, int nvars) { + super.newSampleData(data, nobs, nvars); + qr = new QRDecomposition(getX(), threshold); + } + + /** + *

        Compute the "hat" matrix. + *

        + *

        The hat matrix is defined in terms of the design matrix X + * by X(XTX)-1XT + *

        + *

        The implementation here uses the QR decomposition to compute the + * hat matrix as Q IpQT where Ip 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, + * The American Statistician, Vol. 32, No. 1 (Feb., 1978), pp. 17-22. + *

        + *

        Data for the model must have been successfully loaded using one of + * the {@code newSampleData} methods before invoking this method; otherwise + * a {@code NullPointerException} will be thrown.

        + * + * @return the hat matrix + * @throws NullPointerException unless method {@code newSampleData} has been + * called beforehand. + */ + public RealMatrix calculateHat() { + // Create augmented identity matrix + RealMatrix Q = qr.getQ(); + final int p = qr.getR().getColumnDimension(); + final int n = Q.getColumnDimension(); + // No try-catch or advertised NotStrictlyPositiveException - NPE above if n < 3 + Array2DRowRealMatrix augI = new Array2DRowRealMatrix(n, n); + double[][] augIData = augI.getDataRef(); + for (int i = 0; i < n; i++) { + for (int j =0; j < n; j++) { + if (i == j && i < p) { + augIData[i][j] = 1d; + } else { + augIData[i][j] = 0d; + } + } + } + + // Compute and return Hat matrix + // No DME advertised - args valid if we get here + return Q.multiply(augI).multiply(Q.transpose()); + } + + /** + *

        Returns the sum of squared deviations of Y from its mean.

        + * + *

        If the model has no intercept term, 0 is used for the + * mean of Y - i.e., what is returned is the sum of the squared Y values.

        + * + *

        The value returned by this method is the SSTO value used in + * the {@link #calculateRSquared() R-squared} computation.

        + * + * @return SSTO - the total sum of squares + * @throws NullPointerException if the sample has not been set + * @see #isNoIntercept() + * @since 2.2 + */ + public double calculateTotalSumOfSquares() { + if (isNoIntercept()) { + return StatUtils.sumSq(getY().toArray()); + } else { + return new SecondMoment().evaluate(getY().toArray()); + } + } + + /** + * Returns the sum of squared residuals. + * + * @return residual sum of squares + * @since 2.2 + * @throws SingularMatrixException if the design matrix is singular + * @throws NullPointerException if the data for the model have not been loaded + */ + public double calculateResidualSumOfSquares() { + final RealVector residuals = calculateResiduals(); + // No advertised DME, args are valid + return residuals.dotProduct(residuals); + } + + /** + * Returns the R-Squared statistic, defined by the formula
        +     * R2 = 1 - SSR / SSTO
        +     * 
        + * where SSR is the {@link #calculateResidualSumOfSquares() sum of squared residuals} + * and SSTO is the {@link #calculateTotalSumOfSquares() total sum of squares} + * + *

        If there is no variance in y, i.e., SSTO = 0, NaN is returned.

        + * + * @return R-square statistic + * @throws NullPointerException if the sample has not been set + * @throws SingularMatrixException if the design matrix is singular + * @since 2.2 + */ + public double calculateRSquared() { + return 1 - calculateResidualSumOfSquares() / calculateTotalSumOfSquares(); + } + + /** + *

        Returns the adjusted R-squared statistic, defined by the formula

        +     * R2adj = 1 - [SSR (n - 1)] / [SSTO (n - p)]
        +     * 
        + * 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).

        + * + *

        If the regression is estimated without an intercept term, what is returned is

        +     *  1 - (1 - {@link #calculateRSquared()}) * (n / (n - p)) 
        +     * 

        + * + *

        If there is no variance in y, i.e., SSTO = 0, NaN is returned.

        + * + * @return adjusted R-Squared statistic + * @throws NullPointerException if the sample has not been set + * @throws SingularMatrixException if the design matrix is singular + * @see #isNoIntercept() + * @since 2.2 + */ + public double calculateAdjustedRSquared() { + final double n = getX().getRowDimension(); + if (isNoIntercept()) { + return 1 - (1 - calculateRSquared()) * (n / (n - getX().getColumnDimension())); + } else { + return 1 - (calculateResidualSumOfSquares() * (n - 1)) / + (calculateTotalSumOfSquares() * (n - getX().getColumnDimension())); + } + } + + /** + * {@inheritDoc} + *

        This implementation computes and caches the QR decomposition of the X matrix + * once it is successfully loaded.

        + */ + @Override + protected void newXSampleData(double[][] x) { + super.newXSampleData(x); + qr = new QRDecomposition(getX(), threshold); + } + + /** + * Calculates the regression coefficients using OLS. + * + *

        Data for the model must have been successfully loaded using one of + * the {@code newSampleData} methods before invoking this method; otherwise + * a {@code NullPointerException} will be thrown.

        + * + * @return beta + * @throws SingularMatrixException if the design matrix is singular + * @throws NullPointerException if the data for the model have not been loaded + */ + @Override + protected RealVector calculateBeta() { + return qr.getSolver().solve(getY()); + } + + /** + *

        Calculates the variance-covariance matrix of the regression parameters. + *

        + *

        Var(b) = (XTX)-1 + *

        + *

        Uses QR decomposition to reduce (XTX)-1 + * to (RTR)-1, with only the top p rows of + * R included, where p = the length of the beta vector.

        + * + *

        Data for the model must have been successfully loaded using one of + * the {@code newSampleData} methods before invoking this method; otherwise + * a {@code NullPointerException} will be thrown.

        + * + * @return The beta variance-covariance matrix + * @throws SingularMatrixException if the design matrix is singular + * @throws NullPointerException if the data for the model have not been loaded + */ + @Override + protected RealMatrix calculateBetaVariance() { + int p = getX().getColumnDimension(); + RealMatrix Raug = qr.getR().getSubMatrix(0, p - 1 , 0, p - 1); + RealMatrix Rinv = new LUDecomposition(Raug).getSolver().getInverse(); + return Rinv.multiply(Rinv.transpose()); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/RegressionResults.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/RegressionResults.java new file mode 100644 index 000000000..c428f8cba --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/RegressionResults.java @@ -0,0 +1,422 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Results of a Multiple Linear Regression model fit. + * + * @since 3.0 + */ +public class RegressionResults implements Serializable { + + /** INDEX of Sum of Squared Errors */ + private static final int SSE_IDX = 0; + /** INDEX of Sum of Squares of Model */ + private static final int SST_IDX = 1; + /** INDEX of R-Squared of regression */ + private static final int RSQ_IDX = 2; + /** INDEX of Mean Squared Error */ + private static final int MSE_IDX = 3; + /** INDEX of Adjusted R Squared */ + private static final int ADJRSQ_IDX = 4; + /** UID */ + private static final long serialVersionUID = 1l; + /** regression slope parameters */ + private final double[] parameters; + /** variance covariance matrix of parameters */ + private final double[][] varCovData; + /** boolean flag for variance covariance matrix in symm compressed storage */ + private final boolean isSymmetricVCD; + /** rank of the solution */ + @SuppressWarnings("unused") + private final int rank; + /** number of observations on which results are based */ + private final long nobs; + /** boolean flag indicator of whether a constant was included*/ + private final boolean containsConstant; + /** array storing global results, SSE, MSE, RSQ, adjRSQ */ + private final double[] globalFitInfo; + + /** + * Set the default constructor to private access + * to prevent inadvertent instantiation + */ + @SuppressWarnings("unused") + private RegressionResults() { + this.parameters = null; + this.varCovData = null; + this.rank = -1; + this.nobs = -1; + this.containsConstant = false; + this.isSymmetricVCD = false; + this.globalFitInfo = null; + } + + /** + * Constructor for Regression Results. + * + * @param parameters a double array with the regression slope estimates + * @param varcov the variance covariance matrix, stored either in a square matrix + * or as a compressed + * @param isSymmetricCompressed a flag which denotes that the variance covariance + * matrix is in symmetric compressed format + * @param nobs the number of observations of the regression estimation + * @param rank the number of independent variables in the regression + * @param sumy the sum of the independent variable + * @param sumysq the sum of the squared independent variable + * @param sse sum of squared errors + * @param containsConstant true model has constant, false model does not have constant + * @param copyData if true a deep copy of all input data is made, if false only references + * are copied and the RegressionResults become mutable + */ + public RegressionResults( + final double[] parameters, final double[][] varcov, + final boolean isSymmetricCompressed, + final long nobs, final int rank, + final double sumy, final double sumysq, final double sse, + final boolean containsConstant, + final boolean copyData) { + if (copyData) { + this.parameters = MathArrays.copyOf(parameters); + this.varCovData = new double[varcov.length][]; + for (int i = 0; i < varcov.length; i++) { + this.varCovData[i] = MathArrays.copyOf(varcov[i]); + } + } else { + this.parameters = parameters; + this.varCovData = varcov; + } + this.isSymmetricVCD = isSymmetricCompressed; + this.nobs = nobs; + this.rank = rank; + this.containsConstant = containsConstant; + this.globalFitInfo = new double[5]; + Arrays.fill(this.globalFitInfo, Double.NaN); + + if (rank > 0) { + this.globalFitInfo[SST_IDX] = containsConstant ? + (sumysq - sumy * sumy / nobs) : sumysq; + } + + this.globalFitInfo[SSE_IDX] = sse; + this.globalFitInfo[MSE_IDX] = this.globalFitInfo[SSE_IDX] / + (nobs - rank); + this.globalFitInfo[RSQ_IDX] = 1.0 - + this.globalFitInfo[SSE_IDX] / + this.globalFitInfo[SST_IDX]; + + if (!containsConstant) { + this.globalFitInfo[ADJRSQ_IDX] = 1.0- + (1.0 - this.globalFitInfo[RSQ_IDX]) * + ( (double) nobs / ( (double) (nobs - rank))); + } else { + this.globalFitInfo[ADJRSQ_IDX] = 1.0 - (sse * (nobs - 1.0)) / + (globalFitInfo[SST_IDX] * (nobs - rank)); + } + } + + /** + *

        Returns the parameter estimate for the regressor at the given index.

        + * + *

        A redundant regressor will have its redundancy flag set, as well as + * a parameters estimated equal to {@code Double.NaN}

        + * + * @param index Index. + * @return the parameters estimated for regressor at index. + * @throws OutOfRangeException if {@code index} is not in the interval + * {@code [0, number of parameters)}. + */ + public double getParameterEstimate(int index) throws OutOfRangeException { + if (parameters == null) { + return Double.NaN; + } + if (index < 0 || index >= this.parameters.length) { + throw new OutOfRangeException(index, 0, this.parameters.length - 1); + } + return this.parameters[index]; + } + + /** + *

        Returns a copy of the regression parameters estimates.

        + * + *

        The parameter estimates are returned in the natural order of the data.

        + * + *

        A redundant regressor will have its redundancy flag set, as will + * a parameter estimate equal to {@code Double.NaN}.

        + * + * @return array of parameter estimates, null if no estimation occurred + */ + public double[] getParameterEstimates() { + if (this.parameters == null) { + return null; + } + return MathArrays.copyOf(parameters); + } + + /** + * Returns the standard + * error of the parameter estimate at index, + * usually denoted s(bindex). + * + * @param index Index. + * @return the standard errors associated with parameters estimated at index. + * @throws OutOfRangeException if {@code index} is not in the interval + * {@code [0, number of parameters)}. + */ + public double getStdErrorOfEstimate(int index) throws OutOfRangeException { + if (parameters == null) { + return Double.NaN; + } + if (index < 0 || index >= this.parameters.length) { + throw new OutOfRangeException(index, 0, this.parameters.length - 1); + } + double var = this.getVcvElement(index, index); + if (!Double.isNaN(var) && var > Double.MIN_VALUE) { + return FastMath.sqrt(var); + } + return Double.NaN; + } + + /** + *

        Returns the standard + * error of the parameter estimates, + * usually denoted s(bi).

        + * + *

        If there are problems with an ill conditioned design matrix then the regressor + * which is redundant will be assigned Double.NaN.

        + * + * @return an array standard errors associated with parameters estimates, + * null if no estimation occurred + */ + public double[] getStdErrorOfEstimates() { + if (parameters == null) { + return null; + } + double[] se = new double[this.parameters.length]; + for (int i = 0; i < this.parameters.length; i++) { + double var = this.getVcvElement(i, i); + if (!Double.isNaN(var) && var > Double.MIN_VALUE) { + se[i] = FastMath.sqrt(var); + continue; + } + se[i] = Double.NaN; + } + return se; + } + + /** + *

        Returns the covariance between regression parameters i and j.

        + * + *

        If there are problems with an ill conditioned design matrix then the covariance + * which involves redundant columns will be assigned {@code Double.NaN}.

        + * + * @param i {@code i}th regression parameter. + * @param j {@code j}th regression parameter. + * @return the covariance of the parameter estimates. + * @throws OutOfRangeException if {@code i} or {@code j} is not in the + * interval {@code [0, number of parameters)}. + */ + public double getCovarianceOfParameters(int i, int j) throws OutOfRangeException { + if (parameters == null) { + return Double.NaN; + } + if (i < 0 || i >= this.parameters.length) { + throw new OutOfRangeException(i, 0, this.parameters.length - 1); + } + if (j < 0 || j >= this.parameters.length) { + throw new OutOfRangeException(j, 0, this.parameters.length - 1); + } + return this.getVcvElement(i, j); + } + + /** + *

        Returns the number of parameters estimated in the model.

        + * + *

        This is the maximum number of regressors, some techniques may drop + * redundant parameters

        + * + * @return number of regressors, -1 if not estimated + */ + public int getNumberOfParameters() { + if (this.parameters == null) { + return -1; + } + return this.parameters.length; + } + + /** + * Returns the number of observations added to the regression model. + * + * @return Number of observations, -1 if an error condition prevents estimation + */ + public long getN() { + return this.nobs; + } + + /** + *

        Returns the sum of squared deviations of the y values about their mean.

        + * + *

        This is defined as SSTO + * here.

        + * + *

        If {@code n < 2}, this returns {@code Double.NaN}.

        + * + * @return sum of squared deviations of y values + */ + public double getTotalSumSquares() { + return this.globalFitInfo[SST_IDX]; + } + + /** + *

        Returns the sum of squared deviations of the predicted y values about + * their mean (which equals the mean of y).

        + * + *

        This is usually abbreviated SSR or SSM. It is defined as SSM + * here

        + * + *

        Preconditions:

          + *
        • 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, Double.NaN is + * returned. + *

        + * + * @return sum of squared deviations of predicted y values + */ + public double getRegressionSumSquares() { + return this.globalFitInfo[SST_IDX] - this.globalFitInfo[SSE_IDX]; + } + + /** + *

        Returns the + * sum of squared errors (SSE) associated with the regression + * model.

        + * + *

        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.

        + * + *

        Preconditions:

          + *
        • numberOfParameters data pairs + * must have been added before invoking this method. If this method is + * invoked before a model can be estimated, Double,NaN is + * returned. + *

        + * + * @return sum of squared errors associated with the regression model + */ + public double getErrorSumSquares() { + return this.globalFitInfo[ SSE_IDX]; + } + + /** + *

        Returns the sum of squared errors divided by the degrees of freedom, + * usually abbreviated MSE.

        + * + *

        If there are fewer than numberOfParameters + 1 data pairs in the model, + * or if there is no variation in x, this returns + * Double.NaN.

        + * + * @return sum of squared deviations of y values + */ + public double getMeanSquareError() { + return this.globalFitInfo[ MSE_IDX]; + } + + /** + *

        Returns the + * coefficient of multiple determination, + * usually denoted r-square.

        + * + *

        Preconditions:

          + *
        • At least numberOfParameters observations (with at least numberOfParameters different x values) + * must have been added before invoking this method. If this method is + * invoked before a model can be estimated, {@code Double,NaN} is + * returned. + *

        + * + * @return r-square, a double in the interval [0, 1] + */ + public double getRSquared() { + return this.globalFitInfo[ RSQ_IDX]; + } + + /** + *

        Returns the adjusted R-squared statistic, defined by the formula

        +     * R2adj = 1 - [SSR (n - 1)] / [SSTO (n - p)]
        +     * 
        + * where SSR is the sum of squared residuals}, + * SSTO is the total sum of squares}, n is the number + * of observations and p is the number of parameters estimated (including the intercept).

        + * + *

        If the regression is estimated without an intercept term, what is returned is

        +     *  1 - (1 - {@link #getRSquared()} ) * (n / (n - p)) 
        +     * 

        + * + * @return adjusted R-Squared statistic + */ + public double getAdjustedRSquared() { + return this.globalFitInfo[ ADJRSQ_IDX]; + } + + /** + * Returns true if the regression model has been computed including an intercept. + * In this case, the coefficient of the intercept is the first element of the + * {@link #getParameterEstimates() parameter estimates}. + * @return true if the model has an intercept term + */ + public boolean hasIntercept() { + return this.containsConstant; + } + + /** + * Gets the i-jth element of the variance-covariance matrix. + * + * @param i first variable index + * @param j second variable index + * @return the requested variance-covariance matrix entry + */ + private double getVcvElement(int i, int j) { + if (this.isSymmetricVCD) { + if (this.varCovData.length > 1) { + //could be stored in upper or lower triangular + if (i == j) { + return varCovData[i][i]; + } else if (i >= varCovData[j].length) { + return varCovData[i][j]; + } else { + return varCovData[j][i]; + } + } else {//could be in single array + if (i > j) { + return varCovData[0][(i + 1) * i / 2 + j]; + } else { + return varCovData[0][(j + 1) * j / 2 + i]; + } + } + } else { + return this.varCovData[i][j]; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/SimpleRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/SimpleRegression.java new file mode 100644 index 000000000..b9a2d2cbc --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/SimpleRegression.java @@ -0,0 +1,882 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.stat.regression; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.distribution.TDistribution; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.Precision; + +/** + * Estimates an ordinary least squares regression model + * with one independent variable. + *

        + * y = intercept + slope * x

        + *

        + * Standard errors for intercept and slope are + * available as well as ANOVA, r-square and Pearson's r statistics.

        + *

        + * 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.

        + *

        + * Usage Notes:

          + *
        • 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 NaN. At least two observations with + * different x coordinates are required to estimate a bivariate regression + * model. + *
        • + *
        • 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. + *
        • + *
        • The intercept term may be suppressed by passing {@code false} to + * the {@link #SimpleRegression(boolean)} constructor. When the + * {@code hasIntercept} property is false, the model is estimated without a + * constant term and {@link #getIntercept()} returns {@code 0}.
        • + *

        + * + */ +public class SimpleRegression implements Serializable, UpdatingMultipleLinearRegression { + + /** Serializable version identifier */ + private static final long serialVersionUID = -3004689053607543335L; + + /** sum of x values */ + private double sumX = 0d; + + /** total variation in x (sum of squared deviations from xbar) */ + private double sumXX = 0d; + + /** sum of y values */ + private double sumY = 0d; + + /** total variation in y (sum of squared deviations from ybar) */ + private double sumYY = 0d; + + /** sum of products */ + private double sumXY = 0d; + + /** number of observations */ + private long n = 0; + + /** mean of accumulated x values, used in updating formulas */ + private double xbar = 0; + + /** mean of accumulated y values, used in updating formulas */ + private double ybar = 0; + + /** include an intercept or not */ + private final boolean hasIntercept; + // ---------------------Public methods-------------------------------------- + + /** + * Create an empty SimpleRegression instance + */ + public SimpleRegression() { + this(true); + } + /** + * Create a SimpleRegression instance, specifying whether or not to estimate + * an intercept. + * + *

        Use {@code false} to estimate a model with no intercept. When the + * {@code hasIntercept} property is false, the model is estimated without a + * constant term and {@link #getIntercept()} returns {@code 0}.

        + * + * @param includeIntercept whether or not to include an intercept term in + * the regression model + */ + public SimpleRegression(boolean includeIntercept) { + super(); + hasIntercept = includeIntercept; + } + + /** + * Adds the observation (x,y) to the regression data set. + *

        + * 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.

        + * + * + * @param x independent variable value + * @param y dependent variable value + */ + public void addData(final double x,final double y) { + if (n == 0) { + xbar = x; + ybar = y; + } else { + if( hasIntercept ){ + final double fact1 = 1.0 + n; + final double fact2 = n / (1.0 + n); + final double dx = x - xbar; + final double dy = y - ybar; + sumXX += dx * dx * fact2; + sumYY += dy * dy * fact2; + sumXY += dx * dy * fact2; + xbar += dx / fact1; + ybar += dy / fact1; + } + } + if( !hasIntercept ){ + sumXX += x * x ; + sumYY += y * y ; + sumXY += x * y ; + } + sumX += x; + sumY += y; + n++; + } + + /** + * Appends data from another regression calculation to this one. + * + *

        The mean update formulae are based on a paper written by Philippe + * Pébay: + * + * Formulas for Robust, One-Pass Parallel Computation of Covariances and + * Arbitrary-Order Statistical Moments, 2008, Technical Report + * SAND2008-6212, Sandia National Laboratories.

        + * + * @param reg model to append data from + * @since 3.3 + */ + public void append(SimpleRegression reg) { + if (n == 0) { + xbar = reg.xbar; + ybar = reg.ybar; + sumXX = reg.sumXX; + sumYY = reg.sumYY; + sumXY = reg.sumXY; + } else { + if (hasIntercept) { + final double fact1 = reg.n / (double) (reg.n + n); + final double fact2 = n * reg.n / (double) (reg.n + n); + final double dx = reg.xbar - xbar; + final double dy = reg.ybar - ybar; + sumXX += reg.sumXX + dx * dx * fact2; + sumYY += reg.sumYY + dy * dy * fact2; + sumXY += reg.sumXY + dx * dy * fact2; + xbar += dx * fact1; + ybar += dy * fact1; + }else{ + sumXX += reg.sumXX; + sumYY += reg.sumYY; + sumXY += reg.sumXY; + } + } + sumX += reg.sumX; + sumY += reg.sumY; + n += reg.n; + } + + /** + * Removes the observation (x,y) from the regression data set. + *

        + * 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.

        + * + * The method has no effect if there are no points of data (i.e. n=0) + * + * @param x independent variable value + * @param y dependent variable value + */ + public void removeData(final double x,final double y) { + if (n > 0) { + if (hasIntercept) { + final double fact1 = n - 1.0; + final double fact2 = n / (n - 1.0); + final double dx = x - xbar; + final double dy = y - ybar; + sumXX -= dx * dx * fact2; + sumYY -= dy * dy * fact2; + sumXY -= dx * dy * fact2; + xbar -= dx / fact1; + ybar -= dy / fact1; + } else { + final double fact1 = n - 1.0; + sumXX -= x * x; + sumYY -= y * y; + sumXY -= x * y; + xbar -= x / fact1; + ybar -= y / fact1; + } + sumX -= x; + sumY -= y; + n--; + } + } + + /** + * Adds the observations represented by the elements in + * data. + *

        + * (data[0][0],data[0][1]) will be the first observation, then + * (data[1][0],data[1][1]), etc.

        + *

        + * This method does not replace data that has already been added. The + * observations represented by data are added to the existing + * dataset.

        + *

        + * To replace all data, use clear() before adding the new + * data.

        + * + * @param data array of observations to be added + * @throws ModelSpecificationException if the length of {@code data[i]} is not + * greater than or equal to 2 + */ + public void addData(final double[][] data) throws ModelSpecificationException { + for (int i = 0; i < data.length; i++) { + if( data[i].length < 2 ){ + throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION, + data[i].length, 2); + } + addData(data[i][0], data[i][1]); + } + } + + /** + * Adds one observation to the regression model. + * + * @param x the independent variables which form the design matrix + * @param y the dependent or response variable + * @throws ModelSpecificationException if the length of {@code x} does not equal + * the number of independent variables in the model + */ + public void addObservation(final double[] x,final double y) + throws ModelSpecificationException { + if( x == null || x.length == 0 ){ + throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION,x!=null?x.length:0, 1); + } + addData( x[0], y ); + } + + /** + * Adds a series of observations to the regression model. The lengths of + * x and y must be the same and x must be rectangular. + * + * @param x a series of observations on the independent variables + * @param y a series of observations on the dependent variable + * The length of x and y must be the same + * @throws ModelSpecificationException if {@code x} is not rectangular, does not match + * the length of {@code y} or does not contain sufficient data to estimate the model + */ + public void addObservations(final double[][] x,final double[] y) throws ModelSpecificationException { + if ((x == null) || (y == null) || (x.length != y.length)) { + throw new ModelSpecificationException( + LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, + (x == null) ? 0 : x.length, + (y == null) ? 0 : y.length); + } + boolean obsOk=true; + for( int i = 0 ; i < x.length; i++){ + if( x[i] == null || x[i].length == 0 ){ + obsOk = false; + } + } + if( !obsOk ){ + throw new ModelSpecificationException( + LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, + 0, 1); + } + for( int i = 0 ; i < x.length ; i++){ + addData( x[i][0], y[i] ); + } + } + + /** + * Removes observations represented by the elements in data. + *

        + * 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.

        + *

        + * To remove all data, use clear().

        + * + * @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" y value associated with the + * supplied x value, based on the data that has been + * added to the model when this method is activated. + *

        + * predict(x) = intercept + slope * x

        + *

        + * Preconditions:

          + *
        • 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, Double,NaN is + * returned. + *

        + * + * @param x input x value + * @return predicted y value + */ + public double predict(final double x) { + final double b1 = getSlope(); + if (hasIntercept) { + return getIntercept(b1) + b1 * x; + } + return b1 * x; + } + + /** + * Returns the intercept of the estimated regression line, if + * {@link #hasIntercept()} is true; otherwise 0. + *

        + * The least squares estimate of the intercept is computed using the + * normal equations. + * The intercept is sometimes denoted b0.

        + *

        + * Preconditions:

          + *
        • 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, Double,NaN is + * returned. + *

        + * + * @return the intercept of the regression line if the model includes an + * intercept; 0 otherwise + * @see #SimpleRegression(boolean) + */ + public double getIntercept() { + return hasIntercept ? getIntercept(getSlope()) : 0.0; + } + + /** + * Returns true if the model includes an intercept term. + * + * @return true if the regression includes an intercept; false otherwise + * @see #SimpleRegression(boolean) + */ + public boolean hasIntercept() { + return hasIntercept; + } + + /** + * Returns the slope of the estimated regression line. + *

        + * The least squares estimate of the slope is computed using the + * normal equations. + * The slope is sometimes denoted b1.

        + *

        + * Preconditions:

          + *
        • 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, Double.NaN is + * returned. + *

        + * + * @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 + * sum of squared errors (SSE) associated with the regression + * model. + *

        + * The sum is computed using the computational formula

        + *

        + * SSE = SYY - (SXY * SXY / SXX)

        + *

        + * where SYY is the sum of the squared deviations of the y + * values about their mean, SXX is similarly defined and + * SXY is the sum of the products of x and y mean deviations. + *

        + * The sums are accumulated using the updating algorithm referenced in + * {@link #addData}.

        + *

        + * 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.

        + *

        + * Preconditions:

          + *
        • 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, Double,NaN is + * returned. + *

        + * + * @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. + *

        + * This is defined as SSTO + * here.

        + *

        + * If n < 2, this returns Double.NaN.

        + * + * @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 n < 2, this returns Double.NaN.

        + * + * @return sum of squared deviations of x values + */ + public double getXSumSquares() { + if (n < 2) { + return Double.NaN; + } + return sumXX; + } + + /** + * Returns the sum of crossproducts, xi*yi. + * + * @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). + *

        + * This is usually abbreviated SSR or SSM. It is defined as SSM + * here

        + *

        + * Preconditions:

          + *
        • 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, Double.NaN is + * returned. + *

        + * + * @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. + *

        + * If there are fewer than three data pairs in the model, + * or if there is no variation in x, this returns + * Double.NaN.

        + * + * @return sum of squared deviations of y values + */ + public double getMeanSquareError() { + if (n < 3) { + return Double.NaN; + } + return hasIntercept ? (getSumSquaredErrors() / (n - 2)) : (getSumSquaredErrors() / (n - 1)); + } + + /** + * Returns + * Pearson's product moment correlation coefficient, + * usually denoted r. + *

        + * Preconditions:

          + *
        • 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, Double,NaN is + * returned. + *

        + * + * @return Pearson's r + */ + public double getR() { + double b1 = getSlope(); + double result = FastMath.sqrt(getRSquare()); + if (b1 < 0) { + result = -result; + } + return result; + } + + /** + * Returns the + * coefficient of determination, + * usually denoted r-square. + *

        + * Preconditions:

          + *
        • 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, Double,NaN is + * returned. + *

        + * + * @return r-square + */ + public double getRSquare() { + double ssto = getTotalSumSquares(); + return (ssto - getSumSquaredErrors()) / ssto; + } + + /** + * Returns the + * standard error of the intercept estimate, + * usually denoted s(b0). + *

        + * If there are fewer that three observations in the + * model, or if there is no variation in x, this returns + * Double.NaN.

        Additionally, a Double.NaN is + * returned when the intercept is constrained to be zero + * + * @return standard error associated with intercept estimate + */ + public double getInterceptStdErr() { + if( !hasIntercept ){ + return Double.NaN; + } + return FastMath.sqrt( + getMeanSquareError() * ((1d / n) + (xbar * xbar) / sumXX)); + } + + /** + * Returns the standard + * error of the slope estimate, + * usually denoted s(b1). + *

        + * If there are fewer that three data pairs in the model, + * or if there is no variation in x, this returns Double.NaN. + *

        + * + * @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. + *

        + * The 95% confidence interval is

        + *

        + * (getSlope() - getSlopeConfidenceInterval(), + * getSlope() + getSlopeConfidenceInterval())

        + *

        + * If there are fewer that three observations in the + * model, or if there is no variation in x, this returns + * Double.NaN.

        + *

        + * Usage Note:
        + * The validity of this statistic depends on the assumption that the + * observations included in the model are drawn from a + * + * Bivariate Normal Distribution.

        + * + * @return half-width of 95% confidence interval for the slope estimate + * @throws OutOfRangeException if the confidence interval can not be computed. + */ + public double getSlopeConfidenceInterval() throws OutOfRangeException { + return getSlopeConfidenceInterval(0.05d); + } + + /** + * Returns the half-width of a (100-100*alpha)% confidence interval for + * the slope estimate. + *

        + * The (100-100*alpha)% confidence interval is

        + *

        + * (getSlope() - getSlopeConfidenceInterval(), + * getSlope() + getSlopeConfidenceInterval())

        + *

        + * To request, for example, a 99% confidence interval, use + * alpha = .01

        + *

        + * Usage Note:
        + * The validity of this statistic depends on the assumption that the + * observations included in the model are drawn from a + * + * Bivariate Normal Distribution.

        + *

        + * Preconditions:

          + *
        • If there are fewer that three observations in the + * model, or if there is no variation in x, this returns + * Double.NaN. + *
        • + *
        • (0 < alpha < 1); otherwise an + * OutOfRangeException is thrown. + *

        + * + * @param alpha the desired significance level + * @return half-width of 95% confidence interval for the slope estimate + * @throws OutOfRangeException if the confidence interval can not be computed. + */ + public double getSlopeConfidenceInterval(final double alpha) + throws OutOfRangeException { + if (n < 3) { + return Double.NaN; + } + if (alpha >= 1 || alpha <= 0) { + throw new OutOfRangeException(LocalizedFormats.SIGNIFICANCE_LEVEL, + alpha, 0, 1); + } + // No advertised NotStrictlyPositiveException here - will return NaN above + TDistribution distribution = new TDistribution(n - 2); + return getSlopeStdErr() * + distribution.inverseCumulativeProbability(1d - alpha / 2d); + } + + /** + * Returns the significance level of the slope (equiv) correlation. + *

        + * Specifically, the returned value is the smallest alpha + * such that the slope confidence interval with significance level + * equal to alpha does not include 0. + * On regression output, this is often denoted Prob(|t| > 0) + *

        + * Usage Note:
        + * The validity of this statistic depends on the assumption that the + * observations included in the model are drawn from a + * + * Bivariate Normal Distribution.

        + *

        + * If there are fewer that three observations in the + * model, or if there is no variation in x, this returns + * Double.NaN.

        + * + * @return significance level for slope/correlation + * @throws MaxCountExceededException + * if the significance level can not be computed. + */ + public double getSignificance() { + if (n < 3) { + return Double.NaN; + } + // No advertised NotStrictlyPositiveException here - will return NaN above + TDistribution distribution = new TDistribution(n - 2); + return 2d * (1.0 - distribution.cumulativeProbability( + FastMath.abs(getSlope()) / getSlopeStdErr())); + } + + // ---------------------Private methods----------------------------------- + + /** + * Returns the intercept of the estimated regression line, given the slope. + *

        + * Will return NaN if slope is NaN.

        + * + * @param slope current slope + * @return the intercept of the regression line + */ + private double getIntercept(final double slope) { + if( hasIntercept){ + return (sumY - slope * sumX) / n; + } + return 0.0; + } + + /** + * Computes SSR from b1. + * + * @param slope regression slope estimate + * @return sum of squared deviations of predicted y values + */ + private double getRegressionSumSquares(final double slope) { + return slope * slope * sumXX; + } + + /** + * Performs a regression on data present in buffers and outputs a RegressionResults object. + * + *

        If there are fewer than 3 observations in the model and {@code hasIntercept} is true + * a {@code NoDataException} is thrown. If there is no intercept term, the model must + * contain at least 2 observations.

        + * + * @return RegressionResults acts as a container of regression output + * @throws ModelSpecificationException if the model is not correctly specified + * @throws NoDataException if there is not sufficient data in the model to + * estimate the regression parameters + */ + public RegressionResults regress() throws ModelSpecificationException, NoDataException { + if (hasIntercept) { + if (n < 3) { + throw new NoDataException(LocalizedFormats.NOT_ENOUGH_DATA_REGRESSION); + } + if (FastMath.abs(sumXX) > Precision.SAFE_MIN) { + final double[] params = new double[] { getIntercept(), getSlope() }; + final double mse = getMeanSquareError(); + final double _syy = sumYY + sumY * sumY / n; + final double[] vcv = new double[] { mse * (xbar * xbar / sumXX + 1.0 / n), -xbar * mse / sumXX, mse / sumXX }; + return new RegressionResults(params, new double[][] { vcv }, true, n, 2, sumY, _syy, getSumSquaredErrors(), true, + false); + } else { + final double[] params = new double[] { sumY / n, Double.NaN }; + // final double mse = getMeanSquareError(); + final double[] vcv = new double[] { ybar / (n - 1.0), Double.NaN, Double.NaN }; + return new RegressionResults(params, new double[][] { vcv }, true, n, 1, sumY, sumYY, getSumSquaredErrors(), true, + false); + } + } else { + if (n < 2) { + throw new NoDataException(LocalizedFormats.NOT_ENOUGH_DATA_REGRESSION); + } + if (!Double.isNaN(sumXX)) { + final double[] vcv = new double[] { getMeanSquareError() / sumXX }; + final double[] params = new double[] { sumXY / sumXX }; + return new RegressionResults(params, new double[][] { vcv }, true, n, 1, sumY, sumYY, getSumSquaredErrors(), false, + false); + } else { + final double[] vcv = new double[] { Double.NaN }; + final double[] params = new double[] { Double.NaN }; + return new RegressionResults(params, new double[][] { vcv }, true, n, 1, Double.NaN, Double.NaN, Double.NaN, false, + false); + } + } + } + + /** + * Performs a regression on data present in buffers including only regressors + * indexed in variablesToInclude and outputs a RegressionResults object + * @param variablesToInclude an array of indices of regressors to include + * @return RegressionResults acts as a container of regression output + * @throws MathIllegalArgumentException if the variablesToInclude array is null or zero length + * @throws OutOfRangeException if a requested variable is not present in model + */ + public RegressionResults regress(int[] variablesToInclude) throws MathIllegalArgumentException{ + if( variablesToInclude == null || variablesToInclude.length == 0){ + throw new MathIllegalArgumentException(LocalizedFormats.ARRAY_ZERO_LENGTH_OR_NULL_NOT_ALLOWED); + } + if( variablesToInclude.length > 2 || (variablesToInclude.length > 1 && !hasIntercept) ){ + throw new ModelSpecificationException( + LocalizedFormats.ARRAY_SIZE_EXCEEDS_MAX_VARIABLES, + (variablesToInclude.length > 1 && !hasIntercept) ? 1 : 2); + } + + if( hasIntercept ){ + if( variablesToInclude.length == 2 ){ + if( variablesToInclude[0] == 1 ){ + throw new ModelSpecificationException(LocalizedFormats.NOT_INCREASING_SEQUENCE); + }else if( variablesToInclude[0] != 0 ){ + throw new OutOfRangeException( variablesToInclude[0], 0,1 ); + } + if( variablesToInclude[1] != 1){ + throw new OutOfRangeException( variablesToInclude[0], 0,1 ); + } + return regress(); + }else{ + if( variablesToInclude[0] != 1 && variablesToInclude[0] != 0 ){ + throw new OutOfRangeException( variablesToInclude[0],0,1 ); + } + final double _mean = sumY * sumY / n; + final double _syy = sumYY + _mean; + if( variablesToInclude[0] == 0 ){ + //just the mean + final double[] vcv = new double[]{ sumYY/(((n-1)*n)) }; + final double[] params = new double[]{ ybar }; + return new RegressionResults( + params, new double[][]{vcv}, true, n, 1, + sumY, _syy+_mean, sumYY,true,false); + + }else if( variablesToInclude[0] == 1){ + //final double _syy = sumYY + sumY * sumY / ((double) n); + final double _sxx = sumXX + sumX * sumX / n; + final double _sxy = sumXY + sumX * sumY / n; + final double _sse = FastMath.max(0d, _syy - _sxy * _sxy / _sxx); + final double _mse = _sse/((n-1)); + if( !Double.isNaN(_sxx) ){ + final double[] vcv = new double[]{ _mse / _sxx }; + final double[] params = new double[]{ _sxy/_sxx }; + return new RegressionResults( + params, new double[][]{vcv}, true, n, 1, + sumY, _syy, _sse,false,false); + }else{ + final double[] vcv = new double[]{Double.NaN }; + final double[] params = new double[]{ Double.NaN }; + return new RegressionResults( + params, new double[][]{vcv}, true, n, 1, + Double.NaN, Double.NaN, Double.NaN,false,false); + } + } + } + }else{ + if( variablesToInclude[0] != 0 ){ + throw new OutOfRangeException(variablesToInclude[0],0,0); + } + return regress(); + } + + return null; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java new file mode 100644 index 000000000..4ac5e209a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.stat.regression; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; + +/** + * An interface for regression models allowing for dynamic updating of the data. + * That is, the entire data set need not be loaded into memory. As observations + * become available, they can be added to the regression model and an updated + * estimate regression statistics can be calculated. + * + * @since 3.0 + */ +public interface UpdatingMultipleLinearRegression { + + /** + * Returns true if a constant has been included false otherwise. + * + * @return true if constant exists, false otherwise + */ + boolean hasIntercept(); + + /** + * Returns the number of observations added to the regression model. + * + * @return Number of observations + */ + long getN(); + + /** + * Adds one observation to the regression model. + * + * @param x the independent variables which form the design matrix + * @param y the dependent or response variable + * @throws ModelSpecificationException if the length of {@code x} does not equal + * the number of independent variables in the model + */ + void addObservation(double[] x, double y) throws ModelSpecificationException; + + /** + * Adds a series of observations to the regression model. The lengths of + * x and y must be the same and x must be rectangular. + * + * @param x a series of observations on the independent variables + * @param y a series of observations on the dependent variable + * The length of x and y must be the same + * @throws ModelSpecificationException if {@code x} is not rectangular, does not match + * the length of {@code y} or does not contain sufficient data to estimate the model + */ + void addObservations(double[][] x, double[] y) throws ModelSpecificationException; + + /** + * Clears internal buffers and resets the regression model. This means all + * data and derived values are initialized + */ + void clear(); + + + /** + * Performs a regression on data present in buffers and outputs a RegressionResults object + * @return RegressionResults acts as a container of regression output + * @throws ModelSpecificationException if the model is not correctly specified + * @throws NoDataException if there is not sufficient data in the model to + * estimate the regression parameters + */ + RegressionResults regress() throws ModelSpecificationException, NoDataException; + + /** + * Performs a regression on data present in buffers including only regressors + * indexed in variablesToInclude and outputs a RegressionResults object + * @param variablesToInclude an array of indices of regressors to include + * @return RegressionResults acts as a container of regression output + * @throws ModelSpecificationException if the model is not correctly specified + * @throws MathIllegalArgumentException if the variablesToInclude array is null or zero length + */ + RegressionResults regress(int[] variablesToInclude) throws ModelSpecificationException, MathIllegalArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/package-info.java new file mode 100644 index 000000000..c242fe64d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/stat/regression/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Statistical routines involving multivariate data. + * + */ +package com.fr.third.org.apache.commons.math3.stat.regression; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DctNormalization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DctNormalization.java new file mode 100644 index 000000000..b5ed1dc70 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DctNormalization.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 com.fr.third.org.apache.commons.math3.transform; + +/** + * This enumeration defines the various types of normalizations that can be + * applied to discrete cosine transforms (DCT). The exact definition of these + * normalizations is detailed below. + * + * @see FastCosineTransformer + * @since 3.0 + */ +public enum DctNormalization { + /** + * Should be passed to the constructor of {@link FastCosineTransformer} + * to use the standard normalization convention. The standard + * DCT-I normalization convention is defined as follows + *
          + *
        • forward transform: + * yn = (1/2) [x0 + (-1)nxN-1] + * + ∑k=1N-2 + * xk cos[π nk / (N - 1)],
        • + *
        • inverse transform: + * xk = [1 / (N - 1)] [y0 + * + (-1)kyN-1] + * + [2 / (N - 1)] ∑n=1N-2 + * yn cos[π nk / (N - 1)],
        • + *
        + * where N is the size of the data sample. + */ + STANDARD_DCT_I, + + /** + * Should be passed to the constructor of {@link FastCosineTransformer} + * to use the orthogonal normalization convention. The orthogonal + * DCT-I normalization convention is defined as follows + *
          + *
        • forward transform: + * yn = [2(N - 1)]-1/2 [x0 + * + (-1)nxN-1] + * + [2 / (N - 1)]1/2k=1N-2 + * xk cos[π nk / (N - 1)],
        • + *
        • inverse transform: + * xk = [2(N - 1)]-1/2 [y0 + * + (-1)kyN-1] + * + [2 / (N - 1)]1/2n=1N-2 + * yn cos[π nk / (N - 1)],
        • + *
        + * which makes the transform orthogonal. N is the size of the data sample. + */ + ORTHOGONAL_DCT_I; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DftNormalization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DftNormalization.java new file mode 100644 index 000000000..00b58f557 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DftNormalization.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 com.fr.third.org.apache.commons.math3.transform; + +/** + * This enumeration defines the various types of normalizations that can be + * applied to discrete Fourier transforms (DFT). The exact definition of these + * normalizations is detailed below. + * + * @see FastFourierTransformer + * @since 3.0 + */ +public enum DftNormalization { + /** + * Should be passed to the constructor of {@link FastFourierTransformer} + * to use the standard normalization convention. This normalization + * convention is defined as follows + *
          + *
        • forward transform: yn = ∑k=0N-1 + * xk exp(-2πi n k / N),
        • + *
        • inverse transform: xk = N-1 + * ∑n=0N-1 yn exp(2πi n k / N),
        • + *
        + * where N is the size of the data sample. + */ + STANDARD, + + /** + * Should be passed to the constructor of {@link FastFourierTransformer} + * to use the unitary normalization convention. This normalization + * convention is defined as follows + *
          + *
        • forward transform: yn = (1 / √N) + * ∑k=0N-1 xk + * exp(-2πi n k / N),
        • + *
        • inverse transform: xk = (1 / √N) + * ∑n=0N-1 yn exp(2πi n k / N),
        • + *
        + * which makes the transform unitary. N is the size of the data sample. + */ + UNITARY; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DstNormalization.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DstNormalization.java new file mode 100644 index 000000000..67f3fdffe --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/DstNormalization.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 com.fr.third.org.apache.commons.math3.transform; + +/** + * This enumeration defines the various types of normalizations that can be + * applied to discrete sine transforms (DST). The exact definition of these + * normalizations is detailed below. + * + * @see FastSineTransformer + * @since 3.0 + */ +public enum DstNormalization { + /** + * Should be passed to the constructor of {@link FastSineTransformer} to + * use the standard normalization convention. The standard DST-I + * normalization convention is defined as follows + *
          + *
        • forward transform: yn = ∑k=0N-1 + * xk sin(π nk / N),
        • + *
        • inverse transform: xk = (2 / N) + * ∑n=0N-1 yn sin(π nk / N),
        • + *
        + * where N is the size of the data sample, and x0 = 0. + */ + STANDARD_DST_I, + + /** + * Should be passed to the constructor of {@link FastSineTransformer} to + * use the orthogonal normalization convention. The orthogonal + * DCT-I normalization convention is defined as follows + *
          + *
        • Forward transform: yn = √(2 / N) + * ∑k=0N-1 xk sin(π nk / N),
        • + *
        • Inverse transform: xk = √(2 / N) + * ∑n=0N-1 yn sin(π nk / N),
        • + *
        + * which makes the transform orthogonal. N is the size of the data sample, + * and x0 = 0. + */ + ORTHOGONAL_DST_I +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastCosineTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastCosineTransformer.java new file mode 100644 index 000000000..e8946a808 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastCosineTransformer.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 com.fr.third.org.apache.commons.math3.transform; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the Fast Cosine Transform for transformation of one-dimensional + * real data sets. For reference, see James S. Walker, Fast Fourier + * Transforms, chapter 3 (ISBN 0849371635). + *

        + * There are several variants of the discrete cosine transform. The present + * implementation corresponds to DCT-I, with various normalization conventions, + * which are specified by the parameter {@link DctNormalization}. + *

        + * DCT-I is equivalent to DFT of an even extension of the data series. + * More precisely, if x0, …, xN-1 is the data set + * to be cosine transformed, the extended data set + * x0#, …, x2N-3# + * is defined as follows + *

          + *
        • xk# = xk if 0 ≤ k < N,
        • + *
        • xk# = x2N-2-k + * if N ≤ k < 2N - 2.
        • + *
        + *

        + * Then, the standard DCT-I y0, …, yN-1 of the real + * data set x0, …, xN-1 is equal to half + * of the N first elements of the DFT of the extended data set + * x0#, …, x2N-3# + *
        + * yn = (1 / 2) ∑k=02N-3 + * xk# exp[-2πi nk / (2N - 2)] + *     k = 0, …, N-1. + *

        + * The present implementation of the discrete cosine transform as a fast cosine + * transform requires the length of the data set to be a power of two plus one + * (N = 2n + 1). Besides, it implicitly assumes + * that the sampled function is even. + * + * @since 1.2 + */ +public class FastCosineTransformer implements RealTransformer, Serializable { + + /** Serializable version identifier. */ + static final long serialVersionUID = 20120212L; + + /** The type of DCT to be performed. */ + private final DctNormalization normalization; + + /** + * Creates a new instance of this class, with various normalization + * conventions. + * + * @param normalization the type of normalization to be applied to the + * transformed data + */ + public FastCosineTransformer(final DctNormalization normalization) { + this.normalization = normalization; + } + + /** + * {@inheritDoc} + * + * @throws MathIllegalArgumentException if the length of the data array is + * not a power of two plus one + */ + public double[] transform(final double[] f, final TransformType type) + throws MathIllegalArgumentException { + if (type == TransformType.FORWARD) { + if (normalization == DctNormalization.ORTHOGONAL_DCT_I) { + final double s = FastMath.sqrt(2.0 / (f.length - 1)); + return TransformUtils.scaleArray(fct(f), s); + } + return fct(f); + } + final double s2 = 2.0 / (f.length - 1); + final double s1; + if (normalization == DctNormalization.ORTHOGONAL_DCT_I) { + s1 = FastMath.sqrt(s2); + } else { + s1 = s2; + } + return TransformUtils.scaleArray(fct(f), s1); + } + + /** + * {@inheritDoc} + * + * @throws NonMonotonicSequenceException + * if the lower bound is greater than, or equal to the upper bound + * @throws NotStrictlyPositiveException + * if the number of sample points is negative + * @throws MathIllegalArgumentException if the number of sample points is + * not a power of two plus one + */ + public double[] transform(final UnivariateFunction f, + final double min, final double max, final int n, + final TransformType type) throws MathIllegalArgumentException { + + final double[] data = FunctionUtils.sample(f, min, max, n); + return transform(data, type); + } + + /** + * Perform the FCT algorithm (including inverse). + * + * @param f the real data array to be transformed + * @return the real transformed array + * @throws MathIllegalArgumentException if the length of the data array is + * not a power of two plus one + */ + protected double[] fct(double[] f) + throws MathIllegalArgumentException { + + final double[] transformed = new double[f.length]; + + final int n = f.length - 1; + if (!ArithmeticUtils.isPowerOfTwo(n)) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO_PLUS_ONE, + Integer.valueOf(f.length)); + } + if (n == 1) { // trivial case + transformed[0] = 0.5 * (f[0] + f[1]); + transformed[1] = 0.5 * (f[0] - f[1]); + return transformed; + } + + // construct a new array and perform FFT on it + final double[] x = new double[n]; + x[0] = 0.5 * (f[0] + f[n]); + x[n >> 1] = f[n >> 1]; + // temporary variable for transformed[1] + double t1 = 0.5 * (f[0] - f[n]); + for (int i = 1; i < (n >> 1); i++) { + final double a = 0.5 * (f[i] + f[n - i]); + final double b = FastMath.sin(i * FastMath.PI / n) * (f[i] - f[n - i]); + final double c = FastMath.cos(i * FastMath.PI / n) * (f[i] - f[n - i]); + x[i] = a - b; + x[n - i] = a + b; + t1 += c; + } + FastFourierTransformer transformer; + transformer = new FastFourierTransformer(DftNormalization.STANDARD); + Complex[] y = transformer.transform(x, TransformType.FORWARD); + + // reconstruct the FCT result for the original array + transformed[0] = y[0].getReal(); + transformed[1] = t1; + for (int i = 1; i < (n >> 1); i++) { + transformed[2 * i] = y[i].getReal(); + transformed[2 * i + 1] = transformed[2 * i - 1] - y[i].getImaginary(); + } + transformed[n] = y[n >> 1].getReal(); + + return transformed; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastFourierTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastFourierTransformer.java new file mode 100644 index 000000000..792168d9d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastFourierTransformer.java @@ -0,0 +1,675 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.transform; + +import java.io.Serializable; +import java.lang.reflect.Array; + +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; +import com.fr.third.org.apache.commons.math3.util.MathArrays; + +/** + * Implements the Fast Fourier Transform for transformation of one-dimensional + * real or complex data sets. For reference, see Applied Numerical Linear + * Algebra, ISBN 0898713897, chapter 6. + *

        + * There are several variants of the discrete Fourier transform, with various + * normalization conventions, which are specified by the parameter + * {@link DftNormalization}. + *

        + * The current implementation of the discrete Fourier transform as a fast + * Fourier transform requires the length of the data set to be a power of 2. + * This greatly simplifies and speeds up the code. Users can pad the data with + * zeros to meet this requirement. There are other flavors of FFT, for + * reference, see S. Winograd, + * On computing the discrete Fourier transform, Mathematics of + * Computation, 32 (1978), 175 - 199. + * + * @see DftNormalization + * @since 1.2 + */ +public class FastFourierTransformer implements Serializable { + + /** Serializable version identifier. */ + static final long serialVersionUID = 20120210L; + + /** + * {@code W_SUB_N_R[i]} is the real part of + * {@code exp(- 2 * i * pi / n)}: + * {@code W_SUB_N_R[i] = cos(2 * pi/ n)}, where {@code n = 2^i}. + */ + private static final double[] W_SUB_N_R = + { 0x1.0p0, -0x1.0p0, 0x1.1a62633145c07p-54, 0x1.6a09e667f3bcdp-1 + , 0x1.d906bcf328d46p-1, 0x1.f6297cff75cbp-1, 0x1.fd88da3d12526p-1, 0x1.ff621e3796d7ep-1 + , 0x1.ffd886084cd0dp-1, 0x1.fff62169b92dbp-1, 0x1.fffd8858e8a92p-1, 0x1.ffff621621d02p-1 + , 0x1.ffffd88586ee6p-1, 0x1.fffff62161a34p-1, 0x1.fffffd8858675p-1, 0x1.ffffff621619cp-1 + , 0x1.ffffffd885867p-1, 0x1.fffffff62161ap-1, 0x1.fffffffd88586p-1, 0x1.ffffffff62162p-1 + , 0x1.ffffffffd8858p-1, 0x1.fffffffff6216p-1, 0x1.fffffffffd886p-1, 0x1.ffffffffff621p-1 + , 0x1.ffffffffffd88p-1, 0x1.fffffffffff62p-1, 0x1.fffffffffffd9p-1, 0x1.ffffffffffff6p-1 + , 0x1.ffffffffffffep-1, 0x1.fffffffffffffp-1, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0 + , 0x1.0p0, 0x1.0p0, 0x1.0p0 }; + + /** + * {@code W_SUB_N_I[i]} is the imaginary part of + * {@code exp(- 2 * i * pi / n)}: + * {@code W_SUB_N_I[i] = -sin(2 * pi/ n)}, where {@code n = 2^i}. + */ + private static final double[] W_SUB_N_I = + { 0x1.1a62633145c07p-52, -0x1.1a62633145c07p-53, -0x1.0p0, -0x1.6a09e667f3bccp-1 + , -0x1.87de2a6aea963p-2, -0x1.8f8b83c69a60ap-3, -0x1.917a6bc29b42cp-4, -0x1.91f65f10dd814p-5 + , -0x1.92155f7a3667ep-6, -0x1.921d1fcdec784p-7, -0x1.921f0fe670071p-8, -0x1.921f8becca4bap-9 + , -0x1.921faaee6472dp-10, -0x1.921fb2aecb36p-11, -0x1.921fb49ee4ea6p-12, -0x1.921fb51aeb57bp-13 + , -0x1.921fb539ecf31p-14, -0x1.921fb541ad59ep-15, -0x1.921fb5439d73ap-16, -0x1.921fb544197ap-17 + , -0x1.921fb544387bap-18, -0x1.921fb544403c1p-19, -0x1.921fb544422c2p-20, -0x1.921fb54442a83p-21 + , -0x1.921fb54442c73p-22, -0x1.921fb54442cefp-23, -0x1.921fb54442d0ep-24, -0x1.921fb54442d15p-25 + , -0x1.921fb54442d17p-26, -0x1.921fb54442d18p-27, -0x1.921fb54442d18p-28, -0x1.921fb54442d18p-29 + , -0x1.921fb54442d18p-30, -0x1.921fb54442d18p-31, -0x1.921fb54442d18p-32, -0x1.921fb54442d18p-33 + , -0x1.921fb54442d18p-34, -0x1.921fb54442d18p-35, -0x1.921fb54442d18p-36, -0x1.921fb54442d18p-37 + , -0x1.921fb54442d18p-38, -0x1.921fb54442d18p-39, -0x1.921fb54442d18p-40, -0x1.921fb54442d18p-41 + , -0x1.921fb54442d18p-42, -0x1.921fb54442d18p-43, -0x1.921fb54442d18p-44, -0x1.921fb54442d18p-45 + , -0x1.921fb54442d18p-46, -0x1.921fb54442d18p-47, -0x1.921fb54442d18p-48, -0x1.921fb54442d18p-49 + , -0x1.921fb54442d18p-50, -0x1.921fb54442d18p-51, -0x1.921fb54442d18p-52, -0x1.921fb54442d18p-53 + , -0x1.921fb54442d18p-54, -0x1.921fb54442d18p-55, -0x1.921fb54442d18p-56, -0x1.921fb54442d18p-57 + , -0x1.921fb54442d18p-58, -0x1.921fb54442d18p-59, -0x1.921fb54442d18p-60 }; + + /** The type of DFT to be performed. */ + private final DftNormalization normalization; + + /** + * Creates a new instance of this class, with various normalization + * conventions. + * + * @param normalization the type of normalization to be applied to the + * transformed data + */ + public FastFourierTransformer(final DftNormalization normalization) { + this.normalization = normalization; + } + + /** + * Performs identical index bit reversal shuffles on two arrays of identical + * size. Each element in the array is swapped with another element based on + * the bit-reversal of the index. For example, in an array with length 16, + * item at binary index 0011 (decimal 3) would be swapped with the item at + * binary index 1100 (decimal 12). + * + * @param a the first array to be shuffled + * @param b the second array to be shuffled + */ + private static void bitReversalShuffle2(double[] a, double[] b) { + final int n = a.length; + assert b.length == n; + final int halfOfN = n >> 1; + + int j = 0; + for (int i = 0; i < n; i++) { + if (i < j) { + // swap indices i & j + double temp = a[i]; + a[i] = a[j]; + a[j] = temp; + + temp = b[i]; + b[i] = b[j]; + b[j] = temp; + } + + int k = halfOfN; + while (k <= j && k > 0) { + j -= k; + k >>= 1; + } + j += k; + } + } + + /** + * Applies the proper normalization to the specified transformed data. + * + * @param dataRI the unscaled transformed data + * @param normalization the normalization to be applied + * @param type the type of transform (forward, inverse) which resulted in the specified data + */ + private static void normalizeTransformedData(final double[][] dataRI, + final DftNormalization normalization, final TransformType type) { + + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + final int n = dataR.length; + assert dataI.length == n; + + switch (normalization) { + case STANDARD: + if (type == TransformType.INVERSE) { + final double scaleFactor = 1.0 / ((double) n); + for (int i = 0; i < n; i++) { + dataR[i] *= scaleFactor; + dataI[i] *= scaleFactor; + } + } + break; + case UNITARY: + final double scaleFactor = 1.0 / FastMath.sqrt(n); + for (int i = 0; i < n; i++) { + dataR[i] *= scaleFactor; + dataI[i] *= scaleFactor; + } + break; + default: + /* + * This should never occur in normal conditions. However this + * clause has been added as a safeguard if other types of + * normalizations are ever implemented, and the corresponding + * test is forgotten in the present switch. + */ + throw new MathIllegalStateException(); + } + } + + /** + * Computes the standard transform of the specified complex data. The + * computation is done in place. The input data is laid out as follows + *

          + *
        • {@code dataRI[0][i]} is the real part of the {@code i}-th data point,
        • + *
        • {@code dataRI[1][i]} is the imaginary part of the {@code i}-th data point.
        • + *
        + * + * @param dataRI the two dimensional array of real and imaginary parts of the data + * @param normalization the normalization to be applied to the transformed data + * @param type the type of transform (forward, inverse) to be performed + * @throws DimensionMismatchException if the number of rows of the specified + * array is not two, or the array is not rectangular + * @throws MathIllegalArgumentException if the number of data points is not + * a power of two + */ + public static void transformInPlace(final double[][] dataRI, + final DftNormalization normalization, final TransformType type) { + + if (dataRI.length != 2) { + throw new DimensionMismatchException(dataRI.length, 2); + } + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + if (dataR.length != dataI.length) { + throw new DimensionMismatchException(dataI.length, dataR.length); + } + + final int n = dataR.length; + if (!ArithmeticUtils.isPowerOfTwo(n)) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, + Integer.valueOf(n)); + } + + if (n == 1) { + return; + } else if (n == 2) { + final double srcR0 = dataR[0]; + final double srcI0 = dataI[0]; + final double srcR1 = dataR[1]; + final double srcI1 = dataI[1]; + + // X_0 = x_0 + x_1 + dataR[0] = srcR0 + srcR1; + dataI[0] = srcI0 + srcI1; + // X_1 = x_0 - x_1 + dataR[1] = srcR0 - srcR1; + dataI[1] = srcI0 - srcI1; + + normalizeTransformedData(dataRI, normalization, type); + return; + } + + bitReversalShuffle2(dataR, dataI); + + // Do 4-term DFT. + if (type == TransformType.INVERSE) { + for (int i0 = 0; i0 < n; i0 += 4) { + final int i1 = i0 + 1; + final int i2 = i0 + 2; + final int i3 = i0 + 3; + + final double srcR0 = dataR[i0]; + final double srcI0 = dataI[i0]; + final double srcR1 = dataR[i2]; + final double srcI1 = dataI[i2]; + final double srcR2 = dataR[i1]; + final double srcI2 = dataI[i1]; + final double srcR3 = dataR[i3]; + final double srcI3 = dataI[i3]; + + // 4-term DFT + // X_0 = x_0 + x_1 + x_2 + x_3 + dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3; + dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3; + // X_1 = x_0 - x_2 + j * (x_3 - x_1) + dataR[i1] = srcR0 - srcR2 + (srcI3 - srcI1); + dataI[i1] = srcI0 - srcI2 + (srcR1 - srcR3); + // X_2 = x_0 - x_1 + x_2 - x_3 + dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3; + dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3; + // X_3 = x_0 - x_2 + j * (x_1 - x_3) + dataR[i3] = srcR0 - srcR2 + (srcI1 - srcI3); + dataI[i3] = srcI0 - srcI2 + (srcR3 - srcR1); + } + } else { + for (int i0 = 0; i0 < n; i0 += 4) { + final int i1 = i0 + 1; + final int i2 = i0 + 2; + final int i3 = i0 + 3; + + final double srcR0 = dataR[i0]; + final double srcI0 = dataI[i0]; + final double srcR1 = dataR[i2]; + final double srcI1 = dataI[i2]; + final double srcR2 = dataR[i1]; + final double srcI2 = dataI[i1]; + final double srcR3 = dataR[i3]; + final double srcI3 = dataI[i3]; + + // 4-term DFT + // X_0 = x_0 + x_1 + x_2 + x_3 + dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3; + dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3; + // X_1 = x_0 - x_2 + j * (x_3 - x_1) + dataR[i1] = srcR0 - srcR2 + (srcI1 - srcI3); + dataI[i1] = srcI0 - srcI2 + (srcR3 - srcR1); + // X_2 = x_0 - x_1 + x_2 - x_3 + dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3; + dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3; + // X_3 = x_0 - x_2 + j * (x_1 - x_3) + dataR[i3] = srcR0 - srcR2 + (srcI3 - srcI1); + dataI[i3] = srcI0 - srcI2 + (srcR1 - srcR3); + } + } + + int lastN0 = 4; + int lastLogN0 = 2; + while (lastN0 < n) { + int n0 = lastN0 << 1; + int logN0 = lastLogN0 + 1; + double wSubN0R = W_SUB_N_R[logN0]; + double wSubN0I = W_SUB_N_I[logN0]; + if (type == TransformType.INVERSE) { + wSubN0I = -wSubN0I; + } + + // Combine even/odd transforms of size lastN0 into a transform of size N0 (lastN0 * 2). + for (int destEvenStartIndex = 0; destEvenStartIndex < n; destEvenStartIndex += n0) { + int destOddStartIndex = destEvenStartIndex + lastN0; + + double wSubN0ToRR = 1; + double wSubN0ToRI = 0; + + for (int r = 0; r < lastN0; r++) { + double grR = dataR[destEvenStartIndex + r]; + double grI = dataI[destEvenStartIndex + r]; + double hrR = dataR[destOddStartIndex + r]; + double hrI = dataI[destOddStartIndex + r]; + + // dest[destEvenStartIndex + r] = Gr + WsubN0ToR * Hr + dataR[destEvenStartIndex + r] = grR + wSubN0ToRR * hrR - wSubN0ToRI * hrI; + dataI[destEvenStartIndex + r] = grI + wSubN0ToRR * hrI + wSubN0ToRI * hrR; + // dest[destOddStartIndex + r] = Gr - WsubN0ToR * Hr + dataR[destOddStartIndex + r] = grR - (wSubN0ToRR * hrR - wSubN0ToRI * hrI); + dataI[destOddStartIndex + r] = grI - (wSubN0ToRR * hrI + wSubN0ToRI * hrR); + + // WsubN0ToR *= WsubN0R + double nextWsubN0ToRR = wSubN0ToRR * wSubN0R - wSubN0ToRI * wSubN0I; + double nextWsubN0ToRI = wSubN0ToRR * wSubN0I + wSubN0ToRI * wSubN0R; + wSubN0ToRR = nextWsubN0ToRR; + wSubN0ToRI = nextWsubN0ToRI; + } + } + + lastN0 = n0; + lastLogN0 = logN0; + } + + normalizeTransformedData(dataRI, normalization, type); + } + + /** + * Returns the (forward, inverse) transform of the specified real data set. + * + * @param f the real data array to be transformed + * @param type the type of transform (forward, inverse) to be performed + * @return the complex transformed array + * @throws MathIllegalArgumentException if the length of the data array is not a power of two + */ + public Complex[] transform(final double[] f, final TransformType type) { + final double[][] dataRI = new double[][] { + MathArrays.copyOf(f, f.length), new double[f.length] + }; + + transformInPlace(dataRI, normalization, type); + + return TransformUtils.createComplexArray(dataRI); + } + + /** + * Returns the (forward, inverse) transform of the specified real function, + * sampled on the specified interval. + * + * @param f the function to be sampled and transformed + * @param min the (inclusive) lower bound for the interval + * @param max the (exclusive) upper bound for the interval + * @param n the number of sample points + * @param type the type of transform (forward, inverse) to be performed + * @return the complex transformed array + * @throws NumberIsTooLargeException + * if the lower bound is greater than, or equal to the upper bound + * @throws NotStrictlyPositiveException + * if the number of sample points {@code n} is negative + * @throws MathIllegalArgumentException if the number of sample points + * {@code n} is not a power of two + */ + public Complex[] transform(final UnivariateFunction f, + final double min, final double max, final int n, + final TransformType type) { + + final double[] data = FunctionUtils.sample(f, min, max, n); + return transform(data, type); + } + + /** + * Returns the (forward, inverse) transform of the specified complex data set. + * + * @param f the complex data array to be transformed + * @param type the type of transform (forward, inverse) to be performed + * @return the complex transformed array + * @throws MathIllegalArgumentException if the length of the data array is not a power of two + */ + public Complex[] transform(final Complex[] f, final TransformType type) { + final double[][] dataRI = TransformUtils.createRealImaginaryArray(f); + + transformInPlace(dataRI, normalization, type); + + return TransformUtils.createComplexArray(dataRI); + } + + /** + * Performs a multi-dimensional Fourier transform on a given array. Use + * {@link #transform(Complex[], TransformType)} in a row-column + * implementation in any number of dimensions with + * O(N×log(N)) complexity with + * N = n1 × n2 ×n3 × ... + * × nd, where nk is the number of elements in + * dimension k, and d is the total number of dimensions. + * + * @param mdca Multi-Dimensional Complex Array, i.e. {@code Complex[][][][]} + * @param type the type of transform (forward, inverse) to be performed + * @return transform of {@code mdca} as a Multi-Dimensional Complex Array, i.e. {@code Complex[][][][]} + * @throws IllegalArgumentException if any dimension is not a power of two + * @deprecated see MATH-736 + */ + @Deprecated + public Object mdfft(Object mdca, TransformType type) { + MultiDimensionalComplexMatrix mdcm = (MultiDimensionalComplexMatrix) + new MultiDimensionalComplexMatrix(mdca).clone(); + int[] dimensionSize = mdcm.getDimensionSizes(); + //cycle through each dimension + for (int i = 0; i < dimensionSize.length; i++) { + mdfft(mdcm, type, i, new int[0]); + } + return mdcm.getArray(); + } + + /** + * Performs one dimension of a multi-dimensional Fourier transform. + * + * @param mdcm input matrix + * @param type the type of transform (forward, inverse) to be performed + * @param d index of the dimension to process + * @param subVector recursion subvector + * @throws IllegalArgumentException if any dimension is not a power of two + * @deprecated see MATH-736 + */ + @Deprecated + private void mdfft(MultiDimensionalComplexMatrix mdcm, + TransformType type, int d, int[] subVector) { + + int[] dimensionSize = mdcm.getDimensionSizes(); + //if done + if (subVector.length == dimensionSize.length) { + Complex[] temp = new Complex[dimensionSize[d]]; + for (int i = 0; i < dimensionSize[d]; i++) { + //fft along dimension d + subVector[d] = i; + temp[i] = mdcm.get(subVector); + } + + temp = transform(temp, type); + + for (int i = 0; i < dimensionSize[d]; i++) { + subVector[d] = i; + mdcm.set(temp[i], subVector); + } + } else { + int[] vector = new int[subVector.length + 1]; + System.arraycopy(subVector, 0, vector, 0, subVector.length); + if (subVector.length == d) { + //value is not important once the recursion is done. + //then an fft will be applied along the dimension d. + vector[d] = 0; + mdfft(mdcm, type, d, vector); + } else { + for (int i = 0; i < dimensionSize[subVector.length]; i++) { + vector[subVector.length] = i; + //further split along the next dimension + mdfft(mdcm, type, d, vector); + } + } + } + } + + /** + * Complex matrix implementation. Not designed for synchronized access may + * eventually be replaced by jsr-83 of the java community process + * http://jcp.org/en/jsr/detail?id=83 + * may require additional exception throws for other basic requirements. + * + * @deprecated see MATH-736 + */ + @Deprecated + private static class MultiDimensionalComplexMatrix + implements Cloneable { + + /** Size in all dimensions. */ + protected int[] dimensionSize; + + /** Storage array. */ + protected Object multiDimensionalComplexArray; + + /** + * Simple constructor. + * + * @param multiDimensionalComplexArray array containing the matrix + * elements + */ + MultiDimensionalComplexMatrix(Object multiDimensionalComplexArray) { + + this.multiDimensionalComplexArray = multiDimensionalComplexArray; + + // count dimensions + int numOfDimensions = 0; + for (Object lastDimension = multiDimensionalComplexArray; + lastDimension instanceof Object[];) { + final Object[] array = (Object[]) lastDimension; + numOfDimensions++; + lastDimension = array[0]; + } + + // allocate array with exact count + dimensionSize = new int[numOfDimensions]; + + // fill array + numOfDimensions = 0; + for (Object lastDimension = multiDimensionalComplexArray; + lastDimension instanceof Object[];) { + final Object[] array = (Object[]) lastDimension; + dimensionSize[numOfDimensions++] = array.length; + lastDimension = array[0]; + } + + } + + /** + * Get a matrix element. + * + * @param vector indices of the element + * @return matrix element + * @exception DimensionMismatchException if dimensions do not match + */ + public Complex get(int... vector) + throws DimensionMismatchException { + + if (vector == null) { + if (dimensionSize.length > 0) { + throw new DimensionMismatchException( + 0, + dimensionSize.length); + } + return null; + } + if (vector.length != dimensionSize.length) { + throw new DimensionMismatchException( + vector.length, + dimensionSize.length); + } + + Object lastDimension = multiDimensionalComplexArray; + + for (int i = 0; i < dimensionSize.length; i++) { + lastDimension = ((Object[]) lastDimension)[vector[i]]; + } + return (Complex) lastDimension; + } + + /** + * Set a matrix element. + * + * @param magnitude magnitude of the element + * @param vector indices of the element + * @return the previous value + * @exception DimensionMismatchException if dimensions do not match + */ + public Complex set(Complex magnitude, int... vector) + throws DimensionMismatchException { + + if (vector == null) { + if (dimensionSize.length > 0) { + throw new DimensionMismatchException( + 0, + dimensionSize.length); + } + return null; + } + if (vector.length != dimensionSize.length) { + throw new DimensionMismatchException( + vector.length, + dimensionSize.length); + } + + Object[] lastDimension = (Object[]) multiDimensionalComplexArray; + for (int i = 0; i < dimensionSize.length - 1; i++) { + lastDimension = (Object[]) lastDimension[vector[i]]; + } + + Complex lastValue = (Complex) lastDimension[vector[dimensionSize.length - 1]]; + lastDimension[vector[dimensionSize.length - 1]] = magnitude; + + return lastValue; + } + + /** + * Get the size in all dimensions. + * + * @return size in all dimensions + */ + public int[] getDimensionSizes() { + return dimensionSize.clone(); + } + + /** + * Get the underlying storage array. + * + * @return underlying storage array + */ + public Object getArray() { + return multiDimensionalComplexArray; + } + + /** {@inheritDoc} */ + @Override + public Object clone() { + MultiDimensionalComplexMatrix mdcm = + new MultiDimensionalComplexMatrix(Array.newInstance( + Complex.class, dimensionSize)); + clone(mdcm); + return mdcm; + } + + /** + * Copy contents of current array into mdcm. + * + * @param mdcm array where to copy data + */ + private void clone(MultiDimensionalComplexMatrix mdcm) { + + int[] vector = new int[dimensionSize.length]; + int size = 1; + for (int i = 0; i < dimensionSize.length; i++) { + size *= dimensionSize[i]; + } + int[][] vectorList = new int[size][dimensionSize.length]; + for (int[] nextVector : vectorList) { + System.arraycopy(vector, 0, nextVector, 0, + dimensionSize.length); + for (int i = 0; i < dimensionSize.length; i++) { + vector[i]++; + if (vector[i] < dimensionSize[i]) { + break; + } else { + vector[i] = 0; + } + } + } + + for (int[] nextVector : vectorList) { + mdcm.set(get(nextVector), nextVector); + } + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastHadamardTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastHadamardTransformer.java new file mode 100644 index 000000000..9029b5ef4 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastHadamardTransformer.java @@ -0,0 +1,325 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.transform; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; + +/** + * Implements the Fast Hadamard Transform (FHT). + * Transformation of an input vector x to the output vector y. + *

        + * In addition to transformation of real vectors, the Hadamard transform can + * transform integer vectors into integer vectors. However, this integer transform + * cannot be inverted directly. Due to a scaling factor it may lead to rational results. + * As an example, the inverse transform of integer vector (0, 1, 0, 1) is rational + * vector (1/2, -1/2, 0, 0). + * + * @since 2.0 + */ +public class FastHadamardTransformer implements RealTransformer, Serializable { + + /** Serializable version identifier. */ + static final long serialVersionUID = 20120211L; + + /** + * {@inheritDoc} + * + * @throws MathIllegalArgumentException if the length of the data array is + * not a power of two + */ + public double[] transform(final double[] f, final TransformType type) { + if (type == TransformType.FORWARD) { + return fht(f); + } + return TransformUtils.scaleArray(fht(f), 1.0 / f.length); + } + + /** + * {@inheritDoc} + * + * @throws NonMonotonicSequenceException + * if the lower bound is greater than, or equal to the upper bound + * @throws NotStrictlyPositiveException + * if the number of sample points is negative + * @throws MathIllegalArgumentException if the number of sample points is not a power of two + */ + public double[] transform(final UnivariateFunction f, + final double min, final double max, final int n, + final TransformType type) { + + return transform(FunctionUtils.sample(f, min, max, n), type); + } + + /** + * Returns the forward transform of the specified integer data set.The + * integer transform cannot be inverted directly, due to a scaling factor + * which may lead to double results. + * + * @param f the integer data array to be transformed (signal) + * @return the integer transformed array (spectrum) + * @throws MathIllegalArgumentException if the length of the data array is not a power of two + */ + public int[] transform(final int[] f) { + return fht(f); + } + + /** + * The FHT (Fast Hadamard Transformation) which uses only subtraction and + * addition. Requires {@code N * log2(N) = n * 2^n} additions. + * + *

        Short Table of manual calculation for N=8

        + *
          + *
        1. x is the input vector to be transformed,
        2. + *
        3. y is the output vector (Fast Hadamard transform of x),
        4. + *
        5. a and b are helper rows.
        6. + *
        + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        xaby
        x0a0 = x0 + x1b0 = a0 + a1y0 = b0+ b1
        x1a1 = x2 + x3b0 = a2 + a3y0 = b2 + b3
        x2a2 = x4 + x5b0 = a4 + a5y0 = b4 + b5
        x3a3 = x6 + x7b0 = a6 + a7y0 = b6 + b7
        x4a0 = x0 - x1b0 = a0 - a1y0 = b0 - b1
        x5a1 = x2 - x3b0 = a2 - a3y0 = b2 - b3
        x6a2 = x4 - x5b0 = a4 - a5y0 = b4 - b5
        x7a3 = x6 - x7b0 = a6 - a7y0 = b6 - b7
        + * + *

        How it works

        + *
          + *
        1. Construct a matrix with {@code N} rows and {@code n + 1} columns, + * {@code hadm[n+1][N]}.
          + * (If I use [x][y] it always means [row-offset][column-offset] of a + * Matrix with n rows and m columns. Its entries go from M[0][0] + * to M[n][N])
        2. + *
        3. Place the input vector {@code x[N]} in the first column of the + * matrix {@code hadm}.
        4. + *
        5. The entries of the submatrix {@code D_top} are calculated as follows + *
            + *
          • {@code D_top} goes from entry {@code [0][1]} to + * {@code [N / 2 - 1][n + 1]},
          • + *
          • the columns of {@code D_top} are the pairwise mutually + * exclusive sums of the previous column.
          • + *
          + *
        6. + *
        7. The entries of the submatrix {@code D_bottom} are calculated as + * follows + *
            + *
          • {@code D_bottom} goes from entry {@code [N / 2][1]} to + * {@code [N][n + 1]},
          • + *
          • the columns of {@code D_bottom} are the pairwise differences + * of the previous column.
          • + *
          + *
        8. + *
        9. The consputation of {@code D_top} and {@code D_bottom} are best + * understood with the above example (for {@code N = 8}). + *
        10. The output vector {@code y} is now in the last column of + * {@code hadm}.
        11. + *
        12. Algorithm from chipcenter.
        13. + *
        + *

        Visually

        + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        0123n + 1
        0x0 + * ↑
        + * ← Dtop
        + * ↓ + *
        1x1
        2x2
        N / 2 - 1xN/2-1
        N / 2xN/2 + * ↑
        + * ← Dbottom
        + * ↓ + *
        N / 2 + 1xN/2+1
        N / 2 + 2xN/2+2
        NxN
        + * + * @param x the real data array to be transformed + * @return the real transformed array, {@code y} + * @throws MathIllegalArgumentException if the length of the data array is not a power of two + */ + protected double[] fht(double[] x) throws MathIllegalArgumentException { + + final int n = x.length; + final int halfN = n / 2; + + if (!ArithmeticUtils.isPowerOfTwo(n)) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO, + Integer.valueOf(n)); + } + + /* + * Instead of creating a matrix with p+1 columns and n rows, we use two + * one dimension arrays which we are used in an alternating way. + */ + double[] yPrevious = new double[n]; + double[] yCurrent = x.clone(); + + // iterate from left to right (column) + for (int j = 1; j < n; j <<= 1) { + + // switch columns + final double[] yTmp = yCurrent; + yCurrent = yPrevious; + yPrevious = yTmp; + + // iterate from top to bottom (row) + for (int i = 0; i < halfN; ++i) { + // Dtop: the top part works with addition + final int twoI = 2 * i; + yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1]; + } + for (int i = halfN; i < n; ++i) { + // Dbottom: the bottom part works with subtraction + final int twoI = 2 * i; + yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1]; + } + } + + return yCurrent; + + } + + /** + * Returns the forward transform of the specified integer data set. The FHT + * (Fast Hadamard Transform) uses only subtraction and addition. + * + * @param x the integer data array to be transformed + * @return the integer transformed array, {@code y} + * @throws MathIllegalArgumentException if the length of the data array is not a power of two + */ + protected int[] fht(int[] x) throws MathIllegalArgumentException { + + final int n = x.length; + final int halfN = n / 2; + + if (!ArithmeticUtils.isPowerOfTwo(n)) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO, + Integer.valueOf(n)); + } + + /* + * Instead of creating a matrix with p+1 columns and n rows, we use two + * one dimension arrays which we are used in an alternating way. + */ + int[] yPrevious = new int[n]; + int[] yCurrent = x.clone(); + + // iterate from left to right (column) + for (int j = 1; j < n; j <<= 1) { + + // switch columns + final int[] yTmp = yCurrent; + yCurrent = yPrevious; + yPrevious = yTmp; + + // iterate from top to bottom (row) + for (int i = 0; i < halfN; ++i) { + // Dtop: the top part works with addition + final int twoI = 2 * i; + yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1]; + } + for (int i = halfN; i < n; ++i) { + // Dbottom: the bottom part works with subtraction + final int twoI = 2 * i; + yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1]; + } + } + + // return the last computed output vector y + return yCurrent; + + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastSineTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastSineTransformer.java new file mode 100644 index 000000000..2a1e12d10 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/FastSineTransformer.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 com.fr.third.org.apache.commons.math3.transform; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.analysis.FunctionUtils; +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.util.ArithmeticUtils; +import com.fr.third.org.apache.commons.math3.util.FastMath; + +/** + * Implements the Fast Sine Transform for transformation of one-dimensional real + * data sets. For reference, see James S. Walker, Fast Fourier + * Transforms, chapter 3 (ISBN 0849371635). + *

        + * There are several variants of the discrete sine transform. The present + * implementation corresponds to DST-I, with various normalization conventions, + * which are specified by the parameter {@link DstNormalization}. + * It should be noted that regardless to the convention, the first + * element of the dataset to be transformed must be zero. + *

        + * DST-I is equivalent to DFT of an odd extension of the data series. + * More precisely, if x0, …, xN-1 is the data set + * to be sine transformed, the extended data set x0#, + * …, x2N-1# is defined as follows + *

          + *
        • x0# = x0 = 0,
        • + *
        • xk# = xk if 1 ≤ k < N,
        • + *
        • xN# = 0,
        • + *
        • xk# = -x2N-k if N + 1 ≤ k < + * 2N.
        • + *
        + *

        + * Then, the standard DST-I y0, …, yN-1 of the real + * data set x0, …, xN-1 is equal to half + * of i (the pure imaginary number) times the N first elements of the DFT of the + * extended data set x0#, …, + * x2N-1#
        + * yn = (i / 2) ∑k=02N-1 + * xk# exp[-2πi nk / (2N)] + *     k = 0, …, N-1. + *

        + * The present implementation of the discrete sine transform as a fast sine + * transform requires the length of the data to be a power of two. Besides, + * it implicitly assumes that the sampled function is odd. In particular, the + * first element of the data set must be 0, which is enforced in + * {@link #transform(UnivariateFunction, double, double, int, TransformType)}, + * after sampling. + * + * @since 1.2 + */ +public class FastSineTransformer implements RealTransformer, Serializable { + + /** Serializable version identifier. */ + static final long serialVersionUID = 20120211L; + + /** The type of DST to be performed. */ + private final DstNormalization normalization; + + /** + * Creates a new instance of this class, with various normalization conventions. + * + * @param normalization the type of normalization to be applied to the transformed data + */ + public FastSineTransformer(final DstNormalization normalization) { + this.normalization = normalization; + } + + /** + * {@inheritDoc} + * + * The first element of the specified data set is required to be {@code 0}. + * + * @throws MathIllegalArgumentException if the length of the data array is + * not a power of two, or the first element of the data array is not zero + */ + public double[] transform(final double[] f, final TransformType type) { + if (normalization == DstNormalization.ORTHOGONAL_DST_I) { + final double s = FastMath.sqrt(2.0 / f.length); + return TransformUtils.scaleArray(fst(f), s); + } + if (type == TransformType.FORWARD) { + return fst(f); + } + final double s = 2.0 / f.length; + return TransformUtils.scaleArray(fst(f), s); + } + + /** + * {@inheritDoc} + * + * This implementation enforces {@code f(x) = 0.0} at {@code x = 0.0}. + * + * @throws NonMonotonicSequenceException + * if the lower bound is greater than, or equal to the upper bound + * @throws NotStrictlyPositiveException + * if the number of sample points is negative + * @throws MathIllegalArgumentException if the number of sample points is not a power of two + */ + public double[] transform(final UnivariateFunction f, + final double min, final double max, final int n, + final TransformType type) { + + final double[] data = FunctionUtils.sample(f, min, max, n); + data[0] = 0.0; + return transform(data, type); + } + + /** + * Perform the FST algorithm (including inverse). The first element of the + * data set is required to be {@code 0}. + * + * @param f the real data array to be transformed + * @return the real transformed array + * @throws MathIllegalArgumentException if the length of the data array is + * not a power of two, or the first element of the data array is not zero + */ + protected double[] fst(double[] f) throws MathIllegalArgumentException { + + final double[] transformed = new double[f.length]; + + if (!ArithmeticUtils.isPowerOfTwo(f.length)) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, + Integer.valueOf(f.length)); + } + if (f[0] != 0.0) { + throw new MathIllegalArgumentException( + LocalizedFormats.FIRST_ELEMENT_NOT_ZERO, + Double.valueOf(f[0])); + } + final int n = f.length; + if (n == 1) { // trivial case + transformed[0] = 0.0; + return transformed; + } + + // construct a new array and perform FFT on it + final double[] x = new double[n]; + x[0] = 0.0; + x[n >> 1] = 2.0 * f[n >> 1]; + for (int i = 1; i < (n >> 1); i++) { + final double a = FastMath.sin(i * FastMath.PI / n) * (f[i] + f[n - i]); + final double b = 0.5 * (f[i] - f[n - i]); + x[i] = a + b; + x[n - i] = a - b; + } + FastFourierTransformer transformer; + transformer = new FastFourierTransformer(DftNormalization.STANDARD); + Complex[] y = transformer.transform(x, TransformType.FORWARD); + + // reconstruct the FST result for the original array + transformed[0] = 0.0; + transformed[1] = 0.5 * y[0].getReal(); + for (int i = 1; i < (n >> 1); i++) { + transformed[2 * i] = -y[i].getImaginary(); + transformed[2 * i + 1] = y[i].getReal() + transformed[2 * i - 1]; + } + + return transformed; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/RealTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/RealTransformer.java new file mode 100644 index 000000000..2286d493a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/RealTransformer.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 com.fr.third.org.apache.commons.math3.transform; + +import com.fr.third.org.apache.commons.math3.analysis.UnivariateFunction; +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; + +/** + * Interface for one-dimensional data sets transformations producing real results. + *

        + * 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 Complex} + * results instead of real ones. + * + * @since 2.0 + */ +public interface RealTransformer { + + /** + * Returns the (forward, inverse) transform of the specified real data set. + * + * @param f the real data array to be transformed (signal) + * @param type the type of transform (forward, inverse) to be performed + * @return the real transformed array (spectrum) + * @throws MathIllegalArgumentException if the array cannot be transformed + * with the given type (this may be for example due to array size, which is + * constrained in some transforms) + */ + double[] transform(double[] f, TransformType type) throws MathIllegalArgumentException; + + /** + * Returns the (forward, inverse) transform of the specified real function, + * sampled on the specified interval. + * + * @param f the function to be sampled and transformed + * @param min the (inclusive) lower bound for the interval + * @param max the (exclusive) upper bound for the interval + * @param n the number of sample points + * @param type the type of transform (forward, inverse) to be performed + * @return the real transformed array + * @throws NonMonotonicSequenceException if the lower bound is greater than, or equal to the upper bound + * @throws NotStrictlyPositiveException if the number of sample points is negative + * @throws MathIllegalArgumentException if the sample cannot be transformed + * with the given type (this may be for example due to sample size, which is + * constrained in some transforms) + */ + double[] transform(UnivariateFunction f, double min, double max, int n, + TransformType type) + throws NonMonotonicSequenceException, NotStrictlyPositiveException, MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformType.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformType.java new file mode 100644 index 000000000..244d6c095 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformType.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.transform; + +/** + * This enumeration defines the type of transform which is to be computed. + * + * @since 3.0 + */ +public enum TransformType { + /** The type to be specified for forward transforms. */ + FORWARD, + + /** The type to be specified for inverse transforms. */ + INVERSE; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformUtils.java new file mode 100644 index 000000000..8dd263068 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/TransformUtils.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 com.fr.third.org.apache.commons.math3.transform; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.complex.Complex; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Useful functions for the implementation of various transforms. + * + * @since 3.0 + */ +public class TransformUtils { + /** + * Table of the powers of 2 to facilitate binary search lookup. + * + * @see #exactLog2(int) + */ + private static final int[] POWERS_OF_TWO = { + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, + 0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000800, + 0x00001000, 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000, + 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, + 0x40000000 + }; + + /** Private constructor. */ + private TransformUtils() { + super(); + } + + /** + * Multiply every component in the given real array by the + * given real number. The change is made in place. + * + * @param f the real array to be scaled + * @param d the real scaling coefficient + * @return a reference to the scaled array + */ + public static double[] scaleArray(double[] f, double d) { + + for (int i = 0; i < f.length; i++) { + f[i] *= d; + } + return f; + } + + /** + * Multiply every component in the given complex array by the + * given real number. The change is made in place. + * + * @param f the complex array to be scaled + * @param d the real scaling coefficient + * @return a reference to the scaled array + */ + public static Complex[] scaleArray(Complex[] f, double d) { + + for (int i = 0; i < f.length; i++) { + f[i] = new Complex(d * f[i].getReal(), d * f[i].getImaginary()); + } + return f; + } + + + /** + * Builds a new two dimensional array of {@code double} filled with the real + * and imaginary parts of the specified {@link Complex} numbers. In the + * returned array {@code dataRI}, the data is laid out as follows + *

          + *
        • {@code dataRI[0][i] = dataC[i].getReal()},
        • + *
        • {@code dataRI[1][i] = dataC[i].getImaginary()}.
        • + *
        + * + * @param dataC the array of {@link Complex} data to be transformed + * @return a two dimensional array filled with the real and imaginary parts + * of the specified complex input + */ + public static double[][] createRealImaginaryArray(final Complex[] dataC) { + final double[][] dataRI = new double[2][dataC.length]; + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + for (int i = 0; i < dataC.length; i++) { + final Complex c = dataC[i]; + dataR[i] = c.getReal(); + dataI[i] = c.getImaginary(); + } + return dataRI; + } + + /** + * Builds a new array of {@link Complex} from the specified two dimensional + * array of real and imaginary parts. In the returned array {@code dataC}, + * the data is laid out as follows + *
          + *
        • {@code dataC[i].getReal() = dataRI[0][i]},
        • + *
        • {@code dataC[i].getImaginary() = dataRI[1][i]}.
        • + *
        + * + * @param dataRI the array of real and imaginary parts to be transformed + * @return an array of {@link Complex} with specified real and imaginary parts. + * @throws DimensionMismatchException if the number of rows of the specified + * array is not two, or the array is not rectangular + */ + public static Complex[] createComplexArray(final double[][] dataRI) + throws DimensionMismatchException{ + + if (dataRI.length != 2) { + throw new DimensionMismatchException(dataRI.length, 2); + } + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + if (dataR.length != dataI.length) { + throw new DimensionMismatchException(dataI.length, dataR.length); + } + + final int n = dataR.length; + final Complex[] c = new Complex[n]; + for (int i = 0; i < n; i++) { + c[i] = new Complex(dataR[i], dataI[i]); + } + return c; + } + + + /** + * Returns the base-2 logarithm of the specified {@code int}. Throws an + * exception if {@code n} is not a power of two. + * + * @param n the {@code int} whose base-2 logarithm is to be evaluated + * @return the base-2 logarithm of {@code n} + * @throws MathIllegalArgumentException if {@code n} is not a power of two + */ + public static int exactLog2(final int n) + throws MathIllegalArgumentException { + + int index = Arrays.binarySearch(TransformUtils.POWERS_OF_TWO, n); + if (index < 0) { + throw new MathIllegalArgumentException( + LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, + Integer.valueOf(n)); + } + return index; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/package-info.java new file mode 100644 index 000000000..db9994292 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/transform/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * Implementations of transform methods, including Fast Fourier transforms. + * + */ +package com.fr.third.org.apache.commons.math3.transform; diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ArithmeticUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ArithmeticUtils.java new file mode 100644 index 000000000..66c2b81d5 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ArithmeticUtils.java @@ -0,0 +1,907 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.math.BigInteger; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Some useful, arithmetics related, additions to the built-in functions in + * {@link Math}. + * + */ +public final class ArithmeticUtils { + + /** Private constructor. */ + private ArithmeticUtils() { + super(); + } + + /** + * Add two integers, checking for overflow. + * + * @param x an addend + * @param y an addend + * @return the sum {@code x+y} + * @throws MathArithmeticException if the result can not be represented + * as an {@code int}. + * @since 1.1 + */ + public static int addAndCheck(int x, int y) + throws MathArithmeticException { + long s = (long)x + (long)y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, x, y); + } + return (int)s; + } + + /** + * Add two long integers, checking for overflow. + * + * @param a an addend + * @param b an addend + * @return the sum {@code a+b} + * @throws MathArithmeticException if the result can not be represented as an long + * @since 1.2 + */ + public static long addAndCheck(long a, long b) throws MathArithmeticException { + return addAndCheck(a, b, LocalizedFormats.OVERFLOW_IN_ADDITION); + } + + /** + * Returns an exact representation of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code IllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code long}. The + * largest value of {@code n} for which all coefficients are + * {@code < Long.MAX_VALUE} is 66. If the computed value exceeds + * {@code Long.MAX_VALUE} an {@code ArithMeticException} is + * thrown.
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + * @deprecated use {@link CombinatoricsUtils#binomialCoefficient(int, int)} + */ + @Deprecated + public static long binomialCoefficient(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + return CombinatoricsUtils.binomialCoefficient(n, k); + } + + /** + * Returns a {@code double} representation of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code IllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code double}. The + * largest value of {@code n} for which all coefficients are < + * Double.MAX_VALUE is 1029. If the computed value exceeds Double.MAX_VALUE, + * Double.POSITIVE_INFINITY is returned
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + * @deprecated use {@link CombinatoricsUtils#binomialCoefficientDouble(int, int)} + */ + @Deprecated + public static double binomialCoefficientDouble(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + return CombinatoricsUtils.binomialCoefficientDouble(n, k); + } + + /** + * Returns the natural {@code log} of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code IllegalArgumentException} is thrown)
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + * @deprecated use {@link CombinatoricsUtils#binomialCoefficientLog(int, int)} + */ + @Deprecated + public static double binomialCoefficientLog(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + return CombinatoricsUtils.binomialCoefficientLog(n, k); + } + + /** + * Returns n!. Shorthand for {@code n} Factorial, the + * product of the numbers {@code 1,...,n}. + *

        + * Preconditions: + *

          + *
        • {@code n >= 0} (otherwise + * {@code IllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code long}. The + * largest value of {@code n} for which {@code n!} < + * Long.MAX_VALUE} is 20. If the computed value exceeds {@code Long.MAX_VALUE} + * an {@code ArithMeticException } is thrown.
        • + *
        + *

        + * + * @param n argument + * @return {@code n!} + * @throws MathArithmeticException if the result is too large to be represented + * by a {@code long}. + * @throws NotPositiveException if {@code n < 0}. + * @throws MathArithmeticException if {@code n > 20}: The factorial value is too + * large to fit in a {@code long}. + * @deprecated use {@link CombinatoricsUtils#factorial(int)} + */ + @Deprecated + public static long factorial(final int n) throws NotPositiveException, MathArithmeticException { + return CombinatoricsUtils.factorial(n); + } + + /** + * Compute n!, the + * factorial of {@code n} (the product of the numbers 1 to n), as a + * {@code double}. + * The result should be small enough to fit into a {@code double}: The + * largest {@code n} for which {@code n! < Double.MAX_VALUE} is 170. + * If the computed value exceeds {@code Double.MAX_VALUE}, + * {@code Double.POSITIVE_INFINITY} is returned. + * + * @param n Argument. + * @return {@code n!} + * @throws NotPositiveException if {@code n < 0}. + * @deprecated use {@link CombinatoricsUtils#factorialDouble(int)} + */ + @Deprecated + public static double factorialDouble(final int n) throws NotPositiveException { + return CombinatoricsUtils.factorialDouble(n); + } + + /** + * Compute the natural logarithm of the factorial of {@code n}. + * + * @param n Argument. + * @return {@code n!} + * @throws NotPositiveException if {@code n < 0}. + * @deprecated use {@link CombinatoricsUtils#factorialLog(int)} + */ + @Deprecated + public static double factorialLog(final int n) throws NotPositiveException { + return CombinatoricsUtils.factorialLog(n); + } + + /** + * Computes the greatest common divisor of the absolute value of two + * numbers, using a modified version of the "binary gcd" method. + * See Knuth 4.5.2 algorithm B. + * The algorithm is due to Josef Stein (1961). + *
        + * Special cases: + *
          + *
        • The invocations + * {@code gcd(Integer.MIN_VALUE, Integer.MIN_VALUE)}, + * {@code gcd(Integer.MIN_VALUE, 0)} and + * {@code gcd(0, Integer.MIN_VALUE)} throw an + * {@code ArithmeticException}, because the result would be 2^31, which + * is too large for an int value.
        • + *
        • The result of {@code gcd(x, x)}, {@code gcd(0, x)} and + * {@code gcd(x, 0)} is the absolute value of {@code x}, except + * for the special cases above.
        • + *
        • The invocation {@code gcd(0, 0)} is the only one which returns + * {@code 0}.
        • + *
        + * + * @param p Number. + * @param q Number. + * @return the greatest common divisor (never negative). + * @throws MathArithmeticException if the result cannot be represented as + * a non-negative {@code int} value. + * @since 1.1 + */ + public static int gcd(int p, int q) throws MathArithmeticException { + int a = p; + int b = q; + if (a == 0 || + b == 0) { + if (a == Integer.MIN_VALUE || + b == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, + p, q); + } + return FastMath.abs(a + b); + } + + long al = a; + long bl = b; + boolean useLong = false; + if (a < 0) { + if(Integer.MIN_VALUE == a) { + useLong = true; + } else { + a = -a; + } + al = -al; + } + if (b < 0) { + if (Integer.MIN_VALUE == b) { + useLong = true; + } else { + b = -b; + } + bl = -bl; + } + if (useLong) { + if(al == bl) { + throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, + p, q); + } + long blbu = bl; + bl = al; + al = blbu % al; + if (al == 0) { + if (bl > Integer.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, + p, q); + } + return (int) bl; + } + blbu = bl; + + // Now "al" and "bl" fit in an "int". + b = (int) al; + a = (int) (blbu % al); + } + + return gcdPositive(a, b); + } + + /** + * Computes the greatest common divisor of two positive numbers + * (this precondition is not checked and the result is undefined + * if not fulfilled) using the "binary gcd" method which avoids division + * and modulo operations. + * See Knuth 4.5.2 algorithm B. + * The algorithm is due to Josef Stein (1961). + *
        + * Special cases: + *
          + *
        • The result of {@code gcd(x, x)}, {@code gcd(0, x)} and + * {@code gcd(x, 0)} is the value of {@code x}.
        • + *
        • The invocation {@code gcd(0, 0)} is the only one which returns + * {@code 0}.
        • + *
        + * + * @param a Positive number. + * @param b Positive number. + * @return the greatest common divisor. + */ + private static int gcdPositive(int a, int b) { + if (a == 0) { + return b; + } + else if (b == 0) { + return a; + } + + // Make "a" and "b" odd, keeping track of common power of 2. + final int aTwos = Integer.numberOfTrailingZeros(a); + a >>= aTwos; + final int bTwos = Integer.numberOfTrailingZeros(b); + b >>= bTwos; + final int shift = FastMath.min(aTwos, bTwos); + + // "a" and "b" are positive. + // If a > b then "gdc(a, b)" is equal to "gcd(a - b, b)". + // If a < b then "gcd(a, b)" is equal to "gcd(b - a, a)". + // Hence, in the successive iterations: + // "a" becomes the absolute difference of the current values, + // "b" becomes the minimum of the current values. + while (a != b) { + final int delta = a - b; + b = Math.min(a, b); + a = Math.abs(delta); + + // Remove any power of 2 in "a" ("b" is guaranteed to be odd). + a >>= Integer.numberOfTrailingZeros(a); + } + + // Recover the common power of 2. + return a << shift; + } + + /** + *

        + * Gets the greatest common divisor of the absolute value of two numbers, + * using the "binary gcd" method which avoids division and modulo + * operations. See Knuth 4.5.2 algorithm B. This algorithm is due to Josef + * Stein (1961). + *

        + * Special cases: + *
          + *
        • The invocations + * {@code gcd(Long.MIN_VALUE, Long.MIN_VALUE)}, + * {@code gcd(Long.MIN_VALUE, 0L)} and + * {@code gcd(0L, Long.MIN_VALUE)} throw an + * {@code ArithmeticException}, because the result would be 2^63, which + * is too large for a long value.
        • + *
        • The result of {@code gcd(x, x)}, {@code gcd(0L, x)} and + * {@code gcd(x, 0L)} is the absolute value of {@code x}, except + * for the special cases above. + *
        • The invocation {@code gcd(0L, 0L)} is the only one which returns + * {@code 0L}.
        • + *
        + * + * @param p Number. + * @param q Number. + * @return the greatest common divisor, never negative. + * @throws MathArithmeticException if the result cannot be represented as + * a non-negative {@code long} value. + * @since 2.1 + */ + public static long gcd(final long p, final long q) throws MathArithmeticException { + long u = p; + long v = q; + if ((u == 0) || (v == 0)) { + if ((u == Long.MIN_VALUE) || (v == Long.MIN_VALUE)){ + throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_64_BITS, + p, q); + } + return FastMath.abs(u) + FastMath.abs(v); + } + // keep u and v negative, as negative integers range down to + // -2^63, while positive numbers can only be as large as 2^63-1 + // (i.e. we can't necessarily negate a negative number without + // overflow) + /* assert u!=0 && v!=0; */ + if (u > 0) { + u = -u; + } // make u negative + if (v > 0) { + v = -v; + } // make v negative + // B1. [Find power of 2] + int k = 0; + while ((u & 1) == 0 && (v & 1) == 0 && k < 63) { // while u and v are + // both even... + u /= 2; + v /= 2; + k++; // cast out twos. + } + if (k == 63) { + throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_64_BITS, + p, q); + } + // B2. Initialize: u and v have been divided by 2^k and at least + // one is odd. + long t = ((u & 1) == 1) ? v : -(u / 2)/* B3 */; + // t negative: u was odd, v may be even (t replaces v) + // t positive: u was even, v is odd (t replaces u) + do { + /* assert u<0 && v<0; */ + // B4/B3: cast out twos from t. + while ((t & 1) == 0) { // while t is even.. + t /= 2; // cast out twos + } + // B5 [reset max(u,v)] + if (t > 0) { + u = -t; + } else { + v = t; + } + // B6/B3. at this point both u and v should be odd. + t = (v - u) / 2; + // |u| larger: t positive (replace u) + // |v| larger: t negative (replace v) + } while (t != 0); + return -u * (1L << k); // gcd is u*2^k + } + + /** + *

        + * Returns the least common multiple of the absolute value of two numbers, + * using the formula {@code lcm(a,b) = (a / gcd(a,b)) * b}. + *

        + * Special cases: + *
          + *
        • The invocations {@code lcm(Integer.MIN_VALUE, n)} and + * {@code lcm(n, Integer.MIN_VALUE)}, where {@code abs(n)} is a + * power of 2, throw an {@code ArithmeticException}, because the result + * would be 2^31, which is too large for an int value.
        • + *
        • The result of {@code lcm(0, x)} and {@code lcm(x, 0)} is + * {@code 0} for any {@code x}. + *
        + * + * @param a Number. + * @param b Number. + * @return the least common multiple, never negative. + * @throws MathArithmeticException if the result cannot be represented as + * a non-negative {@code int} value. + * @since 1.1 + */ + public static int lcm(int a, int b) throws MathArithmeticException { + if (a == 0 || b == 0){ + return 0; + } + int lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b)); + if (lcm == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.LCM_OVERFLOW_32_BITS, + a, b); + } + return lcm; + } + + /** + *

        + * Returns the least common multiple of the absolute value of two numbers, + * using the formula {@code lcm(a,b) = (a / gcd(a,b)) * b}. + *

        + * Special cases: + *
          + *
        • The invocations {@code lcm(Long.MIN_VALUE, n)} and + * {@code lcm(n, Long.MIN_VALUE)}, where {@code abs(n)} is a + * power of 2, throw an {@code ArithmeticException}, because the result + * would be 2^63, which is too large for an int value.
        • + *
        • The result of {@code lcm(0L, x)} and {@code lcm(x, 0L)} is + * {@code 0L} for any {@code x}. + *
        + * + * @param a Number. + * @param b Number. + * @return the least common multiple, never negative. + * @throws MathArithmeticException if the result cannot be represented + * as a non-negative {@code long} value. + * @since 2.1 + */ + public static long lcm(long a, long b) throws MathArithmeticException { + if (a == 0 || b == 0){ + return 0; + } + long lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b)); + if (lcm == Long.MIN_VALUE){ + throw new MathArithmeticException(LocalizedFormats.LCM_OVERFLOW_64_BITS, + a, b); + } + return lcm; + } + + /** + * Multiply two integers, checking for overflow. + * + * @param x Factor. + * @param y Factor. + * @return the product {@code x * y}. + * @throws MathArithmeticException if the result can not be + * represented as an {@code int}. + * @since 1.1 + */ + public static int mulAndCheck(int x, int y) throws MathArithmeticException { + long m = ((long)x) * ((long)y); + if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) { + throw new MathArithmeticException(); + } + return (int)m; + } + + /** + * Multiply two long integers, checking for overflow. + * + * @param a Factor. + * @param b Factor. + * @return the product {@code a * b}. + * @throws MathArithmeticException if the result can not be represented + * as a {@code long}. + * @since 1.2 + */ + public static long mulAndCheck(long a, long b) throws MathArithmeticException { + long ret; + if (a > b) { + // use symmetry to reduce boundary cases + ret = mulAndCheck(b, a); + } else { + if (a < 0) { + if (b < 0) { + // check for positive overflow with negative a, negative b + if (a >= Long.MAX_VALUE / b) { + ret = a * b; + } else { + throw new MathArithmeticException(); + } + } else if (b > 0) { + // check for negative overflow with negative a, positive b + if (Long.MIN_VALUE / b <= a) { + ret = a * b; + } else { + throw new MathArithmeticException(); + + } + } else { + // assert b == 0 + ret = 0; + } + } else if (a > 0) { + // assert a > 0 + // assert b > 0 + + // check for positive overflow with positive a, positive b + if (a <= Long.MAX_VALUE / b) { + ret = a * b; + } else { + throw new MathArithmeticException(); + } + } else { + // assert a == 0 + ret = 0; + } + } + return ret; + } + + /** + * Subtract two integers, checking for overflow. + * + * @param x Minuend. + * @param y Subtrahend. + * @return the difference {@code x - y}. + * @throws MathArithmeticException if the result can not be represented + * as an {@code int}. + * @since 1.1 + */ + public static int subAndCheck(int x, int y) throws MathArithmeticException { + long s = (long)x - (long)y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, x, y); + } + return (int)s; + } + + /** + * Subtract two long integers, checking for overflow. + * + * @param a Value. + * @param b Value. + * @return the difference {@code a - b}. + * @throws MathArithmeticException if the result can not be represented as a + * {@code long}. + * @since 1.2 + */ + public static long subAndCheck(long a, long b) throws MathArithmeticException { + long ret; + if (b == Long.MIN_VALUE) { + if (a < 0) { + ret = a - b; + } else { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, -b); + } + } else { + // use additive inverse + ret = addAndCheck(a, -b, LocalizedFormats.OVERFLOW_IN_ADDITION); + } + return ret; + } + + /** + * Raise an int to an int power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return \( k^e \) + * @throws NotPositiveException if {@code e < 0}. + * @throws MathArithmeticException if the result would overflow. + */ + public static int pow(final int k, + final int e) + throws NotPositiveException, + MathArithmeticException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + try { + int exp = e; + int result = 1; + int k2p = k; + while (true) { + if ((exp & 0x1) != 0) { + result = mulAndCheck(result, k2p); + } + + exp >>= 1; + if (exp == 0) { + break; + } + + k2p = mulAndCheck(k2p, k2p); + } + + return result; + } catch (MathArithmeticException mae) { + // Add context information. + mae.getContext().addMessage(LocalizedFormats.OVERFLOW); + mae.getContext().addMessage(LocalizedFormats.BASE, k); + mae.getContext().addMessage(LocalizedFormats.EXPONENT, e); + + // Rethrow. + throw mae; + } + } + + /** + * Raise an int to a long power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return ke + * @throws NotPositiveException if {@code e < 0}. + * @deprecated As of 3.3. Please use {@link #pow(int,int)} instead. + */ + @Deprecated + public static int pow(final int k, long e) throws NotPositiveException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + int result = 1; + int k2p = k; + while (e != 0) { + if ((e & 0x1) != 0) { + result *= k2p; + } + k2p *= k2p; + e >>= 1; + } + + return result; + } + + /** + * Raise a long to an int power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return \( k^e \) + * @throws NotPositiveException if {@code e < 0}. + * @throws MathArithmeticException if the result would overflow. + */ + public static long pow(final long k, + final int e) + throws NotPositiveException, + MathArithmeticException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + try { + int exp = e; + long result = 1; + long k2p = k; + while (true) { + if ((exp & 0x1) != 0) { + result = mulAndCheck(result, k2p); + } + + exp >>= 1; + if (exp == 0) { + break; + } + + k2p = mulAndCheck(k2p, k2p); + } + + return result; + } catch (MathArithmeticException mae) { + // Add context information. + mae.getContext().addMessage(LocalizedFormats.OVERFLOW); + mae.getContext().addMessage(LocalizedFormats.BASE, k); + mae.getContext().addMessage(LocalizedFormats.EXPONENT, e); + + // Rethrow. + throw mae; + } + } + + /** + * Raise a long to a long power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return ke + * @throws NotPositiveException if {@code e < 0}. + * @deprecated As of 3.3. Please use {@link #pow(long,int)} instead. + */ + @Deprecated + public static long pow(final long k, long e) throws NotPositiveException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + long result = 1l; + long k2p = k; + while (e != 0) { + if ((e & 0x1) != 0) { + result *= k2p; + } + k2p *= k2p; + e >>= 1; + } + + return result; + } + + /** + * Raise a BigInteger to an int power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return ke + * @throws NotPositiveException if {@code e < 0}. + */ + public static BigInteger pow(final BigInteger k, int e) throws NotPositiveException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + return k.pow(e); + } + + /** + * Raise a BigInteger to a long power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return ke + * @throws NotPositiveException if {@code e < 0}. + */ + public static BigInteger pow(final BigInteger k, long e) throws NotPositiveException { + if (e < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + BigInteger result = BigInteger.ONE; + BigInteger k2p = k; + while (e != 0) { + if ((e & 0x1) != 0) { + result = result.multiply(k2p); + } + k2p = k2p.multiply(k2p); + e >>= 1; + } + + return result; + + } + + /** + * Raise a BigInteger to a BigInteger power. + * + * @param k Number to raise. + * @param e Exponent (must be positive or zero). + * @return ke + * @throws NotPositiveException if {@code e < 0}. + */ + public static BigInteger pow(final BigInteger k, BigInteger e) throws NotPositiveException { + if (e.compareTo(BigInteger.ZERO) < 0) { + throw new NotPositiveException(LocalizedFormats.EXPONENT, e); + } + + BigInteger result = BigInteger.ONE; + BigInteger k2p = k; + while (!BigInteger.ZERO.equals(e)) { + if (e.testBit(0)) { + result = result.multiply(k2p); + } + k2p = k2p.multiply(k2p); + e = e.shiftRight(1); + } + + return result; + } + + /** + * Returns the + * Stirling number of the second kind, "{@code S(n,k)}", the number of + * ways of partitioning an {@code n}-element set into {@code k} non-empty + * subsets. + *

        + * The preconditions are {@code 0 <= k <= n } (otherwise + * {@code NotPositiveException} is thrown) + *

        + * @param n the size of the set + * @param k the number of non-empty subsets + * @return {@code S(n,k)} + * @throws NotPositiveException if {@code k < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if some overflow happens, typically for n exceeding 25 and + * k between 20 and n-2 (S(n,n-1) is handled specifically and does not overflow) + * @since 3.1 + * @deprecated use {@link CombinatoricsUtils#stirlingS2(int, int)} + */ + @Deprecated + public static long stirlingS2(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + return CombinatoricsUtils.stirlingS2(n, k); + + } + + /** + * Add two long integers, checking for overflow. + * + * @param a Addend. + * @param b Addend. + * @param pattern Pattern to use for any thrown exception. + * @return the sum {@code a + b}. + * @throws MathArithmeticException if the result cannot be represented + * as a {@code long}. + * @since 1.2 + */ + private static long addAndCheck(long a, long b, Localizable pattern) throws MathArithmeticException { + final long result = a + b; + if (!((a ^ b) < 0 | (a ^ result) >= 0)) { + throw new MathArithmeticException(pattern, a, b); + } + return result; + } + + /** + * Returns true if the argument is a power of two. + * + * @param n the number to test + * @return true if the argument is a power of two + */ + public static boolean isPowerOfTwo(long n) { + return (n > 0) && ((n & (n - 1)) == 0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigReal.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigReal.java new file mode 100644 index 000000000..7999eeba6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigReal.java @@ -0,0 +1,325 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Arbitrary precision decimal number. + *

        + * This class is a simple wrapper around the standard BigDecimal + * in order to implement the {@link FieldElement} interface. + *

        + * @since 2.0 + */ +public class BigReal implements FieldElement, Comparable, Serializable { + + /** A big real representing 0. */ + public static final BigReal ZERO = new BigReal(BigDecimal.ZERO); + + /** A big real representing 1. */ + public static final BigReal ONE = new BigReal(BigDecimal.ONE); + + /** Serializable version identifier. */ + private static final long serialVersionUID = 4984534880991310382L; + + /** Underlying BigDecimal. */ + private final BigDecimal d; + + /** Rounding mode for divisions. **/ + private RoundingMode roundingMode = RoundingMode.HALF_UP; + + /*** BigDecimal scale ***/ + private int scale = 64; + + /** Build an instance from a BigDecimal. + * @param val value of the instance + */ + public BigReal(BigDecimal val) { + d = val; + } + + /** Build an instance from a BigInteger. + * @param val value of the instance + */ + public BigReal(BigInteger val) { + d = new BigDecimal(val); + } + + /** Build an instance from an unscaled BigInteger. + * @param unscaledVal unscaled value + * @param scale scale to use + */ + public BigReal(BigInteger unscaledVal, int scale) { + d = new BigDecimal(unscaledVal, scale); + } + + /** Build an instance from an unscaled BigInteger. + * @param unscaledVal unscaled value + * @param scale scale to use + * @param mc to used + */ + public BigReal(BigInteger unscaledVal, int scale, MathContext mc) { + d = new BigDecimal(unscaledVal, scale, mc); + } + + /** Build an instance from a BigInteger. + * @param val value of the instance + * @param mc context to use + */ + public BigReal(BigInteger val, MathContext mc) { + d = new BigDecimal(val, mc); + } + + /** Build an instance from a characters representation. + * @param in character representation of the value + */ + public BigReal(char[] in) { + d = new BigDecimal(in); + } + + /** Build an instance from a characters representation. + * @param in character representation of the value + * @param offset offset of the first character to analyze + * @param len length of the array slice to analyze + */ + public BigReal(char[] in, int offset, int len) { + d = new BigDecimal(in, offset, len); + } + + /** Build an instance from a characters representation. + * @param in character representation of the value + * @param offset offset of the first character to analyze + * @param len length of the array slice to analyze + * @param mc context to use + */ + public BigReal(char[] in, int offset, int len, MathContext mc) { + d = new BigDecimal(in, offset, len, mc); + } + + /** Build an instance from a characters representation. + * @param in character representation of the value + * @param mc context to use + */ + public BigReal(char[] in, MathContext mc) { + d = new BigDecimal(in, mc); + } + + /** Build an instance from a double. + * @param val value of the instance + */ + public BigReal(double val) { + d = new BigDecimal(val); + } + + /** Build an instance from a double. + * @param val value of the instance + * @param mc context to use + */ + public BigReal(double val, MathContext mc) { + d = new BigDecimal(val, mc); + } + + /** Build an instance from an int. + * @param val value of the instance + */ + public BigReal(int val) { + d = new BigDecimal(val); + } + + /** Build an instance from an int. + * @param val value of the instance + * @param mc context to use + */ + public BigReal(int val, MathContext mc) { + d = new BigDecimal(val, mc); + } + + /** Build an instance from a long. + * @param val value of the instance + */ + public BigReal(long val) { + d = new BigDecimal(val); + } + + /** Build an instance from a long. + * @param val value of the instance + * @param mc context to use + */ + public BigReal(long val, MathContext mc) { + d = new BigDecimal(val, mc); + } + + /** Build an instance from a String representation. + * @param val character representation of the value + */ + public BigReal(String val) { + d = new BigDecimal(val); + } + + /** Build an instance from a String representation. + * @param val character representation of the value + * @param mc context to use + */ + public BigReal(String val, MathContext mc) { + d = new BigDecimal(val, mc); + } + + /*** + * Gets the rounding mode for division operations + * The default is {@code RoundingMode.HALF_UP} + * @return the rounding mode. + * @since 2.1 + */ + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /*** + * Sets the rounding mode for decimal divisions. + * @param roundingMode rounding mode for decimal divisions + * @since 2.1 + */ + public void setRoundingMode(RoundingMode roundingMode) { + this.roundingMode = roundingMode; + } + + /*** + * Sets the scale for division operations. + * The default is 64 + * @return the scale + * @since 2.1 + */ + public int getScale() { + return scale; + } + + /*** + * Sets the scale for division operations. + * @param scale scale for division operations + * @since 2.1 + */ + public void setScale(int scale) { + this.scale = scale; + } + + /** {@inheritDoc} */ + public BigReal add(BigReal a) { + return new BigReal(d.add(a.d)); + } + + /** {@inheritDoc} */ + public BigReal subtract(BigReal a) { + return new BigReal(d.subtract(a.d)); + } + + /** {@inheritDoc} */ + public BigReal negate() { + return new BigReal(d.negate()); + } + + /** + * {@inheritDoc} + * + * @throws MathArithmeticException if {@code a} is zero + */ + public BigReal divide(BigReal a) throws MathArithmeticException { + try { + return new BigReal(d.divide(a.d, scale, roundingMode)); + } catch (ArithmeticException e) { + // Division by zero has occurred + throw new MathArithmeticException(LocalizedFormats.ZERO_NOT_ALLOWED); + } + } + + /** + * {@inheritDoc} + * + * @throws MathArithmeticException if {@code this} is zero + */ + public BigReal reciprocal() throws MathArithmeticException { + try { + return new BigReal(BigDecimal.ONE.divide(d, scale, roundingMode)); + } catch (ArithmeticException e) { + // Division by zero has occurred + throw new MathArithmeticException(LocalizedFormats.ZERO_NOT_ALLOWED); + } + } + + /** {@inheritDoc} */ + public BigReal multiply(BigReal a) { + return new BigReal(d.multiply(a.d)); + } + + /** {@inheritDoc} */ + public BigReal multiply(final int n) { + return new BigReal(d.multiply(new BigDecimal(n))); + } + + /** {@inheritDoc} */ + public int compareTo(BigReal a) { + return d.compareTo(a.d); + } + + /** Get the double value corresponding to the instance. + * @return double value corresponding to the instance + */ + public double doubleValue() { + return d.doubleValue(); + } + + /** Get the BigDecimal value corresponding to the instance. + * @return BigDecimal value corresponding to the instance + */ + public BigDecimal bigDecimalValue() { + return d; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other){ + return true; + } + + if (other instanceof BigReal){ + return d.equals(((BigReal) other).d); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return d.hashCode(); + } + + /** {@inheritDoc} */ + public Field getField() { + return BigRealField.getInstance(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigRealField.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigRealField.java new file mode 100644 index 000000000..8b37043be --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/BigRealField.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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Representation of real numbers with arbitrary precision field. + *

        + * This class is a singleton. + *

        + * @see BigReal + * @since 2.0 + */ +public class BigRealField implements Field, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 4756431066541037559L; + + /** Private constructor for the singleton. + */ + private BigRealField() { + } + + /** Get the unique instance. + * @return the unique instance + */ + public static BigRealField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public BigReal getOne() { + return BigReal.ONE; + } + + /** {@inheritDoc} */ + public BigReal getZero() { + return BigReal.ZERO; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return BigReal.class; + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** Holder for the instance. + *

        We use here the Initialization On Demand Holder Idiom.

        + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final BigRealField INSTANCE = new BigRealField(); + } + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** Handle deserialization of the singleton. + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CentralPivotingStrategy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CentralPivotingStrategy.java new file mode 100644 index 000000000..9ab33b29c --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CentralPivotingStrategy.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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + + +/** + * A mid point strategy based on the average of begin and end indices. + * @since 3.4 + */ +public class CentralPivotingStrategy implements PivotingStrategyInterface, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140713L; + + /** + * {@inheritDoc} + * This in particular picks a average of begin and end indices + * @return The index corresponding to a simple average of + * the first and the last element indices of the array slice + * @throws MathIllegalArgumentException when indices exceeds range + */ + public int pivotIndex(final double[] work, final int begin, final int end) + throws MathIllegalArgumentException { + MathArrays.verifyValues(work, begin, end-begin); + return begin + (end - begin)/2; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Combinations.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Combinations.java new file mode 100644 index 000000000..6e151edee --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Combinations.java @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.util.Iterator; +import java.util.Comparator; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * Utility to create + * combinations {@code (n, k)} of {@code k} elements in a set of + * {@code n} elements. + * + * @since 3.3 + */ +public class Combinations implements Iterable { + /** Size of the set from which combinations are drawn. */ + private final int n; + /** Number of elements in each combination. */ + private final int k; + /** Iteration order. */ + private final IterationOrder iterationOrder; + + /** + * Describes the type of iteration performed by the + * {@link #iterator() iterator}. + */ + private enum IterationOrder { + /** Lexicographic order. */ + LEXICOGRAPHIC + } + + /** + * Creates an instance whose range is the k-element subsets of + * {0, ..., n - 1} represented as {@code int[]} arrays. + *

        + * The iteration order is lexicographic: the arrays returned by the + * {@link #iterator() iterator} are sorted in descending order and + * they are visited in lexicographic order with significance from + * right to left. + * For example, {@code new Combinations(4, 2).iterator()} returns + * an iterator that will generate the following sequence of arrays + * on successive calls to + * {@code next()}:
        + * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} + *

        + * If {@code k == 0} an iterator containing an empty array is returned; + * if {@code k == n} an iterator containing [0, ..., n - 1] is returned. + * + * @param n Size of the set from which subsets are selected. + * @param k Size of the subsets to be enumerated. + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + */ + public Combinations(int n, + int k) { + this(n, k, IterationOrder.LEXICOGRAPHIC); + } + + /** + * Creates an instance whose range is the k-element subsets of + * {0, ..., n - 1} represented as {@code int[]} arrays. + *

        + * If the {@code iterationOrder} argument is set to + * {@link IterationOrder#LEXICOGRAPHIC}, the arrays returned by the + * {@link #iterator() iterator} are sorted in descending order and + * they are visited in lexicographic order with significance from + * right to left. + * For example, {@code new Combinations(4, 2).iterator()} returns + * an iterator that will generate the following sequence of arrays + * on successive calls to + * {@code next()}:
        + * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} + *

        + * If {@code k == 0} an iterator containing an empty array is returned; + * if {@code k == n} an iterator containing [0, ..., n - 1] is returned. + * + * @param n Size of the set from which subsets are selected. + * @param k Size of the subsets to be enumerated. + * @param iterationOrder Specifies the {@link #iterator() iteration order}. + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + */ + private Combinations(int n, + int k, + IterationOrder iterationOrder) { + CombinatoricsUtils.checkBinomial(n, k); + this.n = n; + this.k = k; + this.iterationOrder = iterationOrder; + } + + /** + * Gets the size of the set from which combinations are drawn. + * + * @return the size of the universe. + */ + public int getN() { + return n; + } + + /** + * Gets the number of elements in each combination. + * + * @return the size of the subsets to be enumerated. + */ + public int getK() { + return k; + } + + /** {@inheritDoc} */ + public Iterator iterator() { + if (k == 0 || + k == n) { + return new SingletonIterator(MathArrays.natural(k)); + } + + switch (iterationOrder) { + case LEXICOGRAPHIC: + return new LexicographicIterator(n, k); + default: + throw new MathInternalError(); // Should never happen. + } + } + + /** + * Defines a lexicographic ordering of combinations. + * The returned comparator allows to compare any two combinations + * that can be produced by this instance's {@link #iterator() iterator}. + * Its {@code compare(int[],int[])} method will throw exceptions if + * passed combinations that are inconsistent with this instance: + *
          + *
        • {@code DimensionMismatchException} if the array lengths are not + * equal to {@code k},
        • + *
        • {@code OutOfRangeException} if an element of the array is not + * within the interval [0, {@code n}).
        • + *
        + * @return a lexicographic comparator. + */ + public Comparator comparator() { + return new LexicographicComparator(n, k); + } + + /** + * Lexicographic combinations iterator. + *

        + * Implementation follows Algorithm T in The Art of Computer Programming + * Internet Draft (PRE-FASCICLE 3A), "A Draft of Section 7.2.1.3 Generating All + * Combinations, D. Knuth, 2004.

        + *

        + * The degenerate cases {@code k == 0} and {@code k == n} are NOT handled by this + * implementation. If constructor arguments satisfy {@code k == 0} + * or {@code k >= n}, no exception is generated, but the iterator is empty. + *

        + * + */ + private static class LexicographicIterator implements Iterator { + /** Size of subsets returned by the iterator */ + private final int k; + + /** + * c[1], ..., c[k] stores the next combination; c[k + 1], c[k + 2] are + * sentinels. + *

        + * Note that c[0] is "wasted" but this makes it a little easier to + * follow the code. + *

        + */ + private final int[] c; + + /** Return value for {@link #hasNext()} */ + private boolean more = true; + + /** Marker: smallest index such that c[j + 1] > j */ + private int j; + + /** + * Construct a CombinationIterator to enumerate k-sets from n. + *

        + * NOTE: If {@code k === 0} or {@code k >= n}, the Iterator will be empty + * (that is, {@link #hasNext()} will return {@code false} immediately. + *

        + * + * @param n size of the set from which subsets are enumerated + * @param k size of the subsets to enumerate + */ + LexicographicIterator(int n, int k) { + this.k = k; + c = new int[k + 3]; + if (k == 0 || k >= n) { + more = false; + return; + } + // Initialize c to start with lexicographically first k-set + for (int i = 1; i <= k; i++) { + c[i] = i - 1; + } + // Initialize sentinels + c[k + 1] = n; + c[k + 2] = 0; + j = k; // Set up invariant: j is smallest index such that c[j + 1] > j + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return more; + } + + /** + * {@inheritDoc} + */ + public int[] next() { + if (!more) { + throw new NoSuchElementException(); + } + // Copy return value (prepared by last activation) + final int[] ret = new int[k]; + System.arraycopy(c, 1, ret, 0, k); + + // Prepare next iteration + // T2 and T6 loop + int x = 0; + if (j > 0) { + x = j; + c[j] = x; + j--; + return ret; + } + // T3 + if (c[1] + 1 < c[2]) { + c[1]++; + return ret; + } else { + j = 2; + } + // T4 + boolean stepDone = false; + while (!stepDone) { + c[j - 1] = j - 2; + x = c[j] + 1; + if (x == c[j + 1]) { + j++; + } else { + stepDone = true; + } + } + // T5 + if (j > k) { + more = false; + return ret; + } + // T6 + c[j] = x; + j--; + return ret; + } + + /** + * Not supported. + */ + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Iterator with just one element to handle degenerate cases (full array, + * empty array) for combination iterator. + */ + private static class SingletonIterator implements Iterator { + /** Singleton array */ + private final int[] singleton; + /** True on initialization, false after first call to next */ + private boolean more = true; + /** + * Create a singleton iterator providing the given array. + * @param singleton array returned by the iterator + */ + SingletonIterator(final int[] singleton) { + this.singleton = singleton; + } + /** @return True until next is called the first time, then false */ + public boolean hasNext() { + return more; + } + /** @return the singleton in first activation; throws NSEE thereafter */ + public int[] next() { + if (more) { + more = false; + return singleton; + } else { + throw new NoSuchElementException(); + } + } + /** Not supported */ + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Defines the lexicographic ordering of combinations, using + * the {@link #lexNorm(int[])} method. + */ + private static class LexicographicComparator + implements Comparator, Serializable { + /** Serializable version identifier. */ + private static final long serialVersionUID = 20130906L; + /** Size of the set from which combinations are drawn. */ + private final int n; + /** Number of elements in each combination. */ + private final int k; + + /** + * @param n Size of the set from which subsets are selected. + * @param k Size of the subsets to be enumerated. + */ + LexicographicComparator(int n, int k) { + this.n = n; + this.k = k; + } + + /** + * {@inheritDoc} + * + * @throws DimensionMismatchException if the array lengths are not + * equal to {@code k}. + * @throws OutOfRangeException if an element of the array is not + * within the interval [0, {@code n}). + */ + public int compare(int[] c1, + int[] c2) { + if (c1.length != k) { + throw new DimensionMismatchException(c1.length, k); + } + if (c2.length != k) { + throw new DimensionMismatchException(c2.length, k); + } + + // Method "lexNorm" works with ordered arrays. + final int[] c1s = MathArrays.copyOf(c1); + Arrays.sort(c1s); + final int[] c2s = MathArrays.copyOf(c2); + Arrays.sort(c2s); + + final long v1 = lexNorm(c1s); + final long v2 = lexNorm(c2s); + + if (v1 < v2) { + return -1; + } else if (v1 > v2) { + return 1; + } else { + return 0; + } + } + + /** + * Computes the value (in base 10) represented by the digit + * (interpreted in base {@code n}) in the input array in reverse + * order. + * For example if {@code c} is {@code {3, 2, 1}}, and {@code n} + * is 3, the method will return 18. + * + * @param c Input array. + * @return the lexicographic norm. + * @throws OutOfRangeException if an element of the array is not + * within the interval [0, {@code n}). + */ + private long lexNorm(int[] c) { + long ret = 0; + for (int i = 0; i < c.length; i++) { + final int digit = c[i]; + if (digit < 0 || + digit >= n) { + throw new OutOfRangeException(digit, 0, n - 1); + } + + ret += c[i] * ArithmeticUtils.pow(n, i); + } + return ret; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CombinatoricsUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CombinatoricsUtils.java new file mode 100644 index 000000000..fc285ddae --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CombinatoricsUtils.java @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Combinatorial utilities. + * + * @since 3.3 + */ +public final class CombinatoricsUtils { + + /** All long-representable factorials */ + static final long[] FACTORIALS = new long[] { + 1l, 1l, 2l, + 6l, 24l, 120l, + 720l, 5040l, 40320l, + 362880l, 3628800l, 39916800l, + 479001600l, 6227020800l, 87178291200l, + 1307674368000l, 20922789888000l, 355687428096000l, + 6402373705728000l, 121645100408832000l, 2432902008176640000l }; + + /** Stirling numbers of the second kind. */ + static final AtomicReference STIRLING_S2 = new AtomicReference (null); + + /** Private constructor (class contains only static methods). */ + private CombinatoricsUtils() {} + + + /** + * Returns an exact representation of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code MathIllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code long}. The + * largest value of {@code n} for which all coefficients are + * {@code < Long.MAX_VALUE} is 66. If the computed value exceeds + * {@code Long.MAX_VALUE} a {@code MathArithMeticException} is + * thrown.
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + */ + public static long binomialCoefficient(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + CombinatoricsUtils.checkBinomial(n, k); + if ((n == k) || (k == 0)) { + return 1; + } + if ((k == 1) || (k == n - 1)) { + return n; + } + // Use symmetry for large k + if (k > n / 2) { + return binomialCoefficient(n, n - k); + } + + // We use the formula + // (n choose k) = n! / (n-k)! / k! + // (n choose k) == ((n-k+1)*...*n) / (1*...*k) + // which could be written + // (n choose k) == (n-1 choose k-1) * n / k + long result = 1; + if (n <= 61) { + // For n <= 61, the naive implementation cannot overflow. + int i = n - k + 1; + for (int j = 1; j <= k; j++) { + result = result * i / j; + i++; + } + } else if (n <= 66) { + // For n > 61 but n <= 66, the result cannot overflow, + // but we must take care not to overflow intermediate values. + int i = n - k + 1; + for (int j = 1; j <= k; j++) { + // We know that (result * i) is divisible by j, + // but (result * i) may overflow, so we split j: + // Filter out the gcd, d, so j/d and i/d are integer. + // result is divisible by (j/d) because (j/d) + // is relative prime to (i/d) and is a divisor of + // result * (i/d). + final long d = ArithmeticUtils.gcd(i, j); + result = (result / (j / d)) * (i / d); + i++; + } + } else { + // For n > 66, a result overflow might occur, so we check + // the multiplication, taking care to not overflow + // unnecessary. + int i = n - k + 1; + for (int j = 1; j <= k; j++) { + final long d = ArithmeticUtils.gcd(i, j); + result = ArithmeticUtils.mulAndCheck(result / (j / d), i / d); + i++; + } + } + return result; + } + + /** + * Returns a {@code double} representation of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code IllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code double}. The + * largest value of {@code n} for which all coefficients are less than + * Double.MAX_VALUE is 1029. If the computed value exceeds Double.MAX_VALUE, + * Double.POSITIVE_INFINITY is returned
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + */ + public static double binomialCoefficientDouble(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + CombinatoricsUtils.checkBinomial(n, k); + if ((n == k) || (k == 0)) { + return 1d; + } + if ((k == 1) || (k == n - 1)) { + return n; + } + if (k > n/2) { + return binomialCoefficientDouble(n, n - k); + } + if (n < 67) { + return binomialCoefficient(n,k); + } + + double result = 1d; + for (int i = 1; i <= k; i++) { + result *= (double)(n - k + i) / (double)i; + } + + return FastMath.floor(result + 0.5); + } + + /** + * Returns the natural {@code log} of the Binomial + * Coefficient, "{@code n choose k}", the number of + * {@code k}-element subsets that can be selected from an + * {@code n}-element set. + *

        + * Preconditions: + *

          + *
        • {@code 0 <= k <= n } (otherwise + * {@code MathIllegalArgumentException} is thrown)
        • + *

        + * + * @param n the size of the set + * @param k the size of the subsets to be counted + * @return {@code n choose k} + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if the result is too large to be + * represented by a long integer. + */ + public static double binomialCoefficientLog(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + CombinatoricsUtils.checkBinomial(n, k); + if ((n == k) || (k == 0)) { + return 0; + } + if ((k == 1) || (k == n - 1)) { + return FastMath.log(n); + } + + /* + * For values small enough to do exact integer computation, + * return the log of the exact value + */ + if (n < 67) { + return FastMath.log(binomialCoefficient(n,k)); + } + + /* + * Return the log of binomialCoefficientDouble for values that will not + * overflow binomialCoefficientDouble + */ + if (n < 1030) { + return FastMath.log(binomialCoefficientDouble(n, k)); + } + + if (k > n / 2) { + return binomialCoefficientLog(n, n - k); + } + + /* + * Sum logs for values that could overflow + */ + double logSum = 0; + + // n!/(n-k)! + for (int i = n - k + 1; i <= n; i++) { + logSum += FastMath.log(i); + } + + // divide by k! + for (int i = 2; i <= k; i++) { + logSum -= FastMath.log(i); + } + + return logSum; + } + + /** + * Returns n!. Shorthand for {@code n} Factorial, the + * product of the numbers {@code 1,...,n}. + *

        + * Preconditions: + *

          + *
        • {@code n >= 0} (otherwise + * {@code MathIllegalArgumentException} is thrown)
        • + *
        • The result is small enough to fit into a {@code long}. The + * largest value of {@code n} for which {@code n!} does not exceed + * Long.MAX_VALUE} is 20. If the computed value exceeds {@code Long.MAX_VALUE} + * an {@code MathArithMeticException } is thrown.
        • + *
        + *

        + * + * @param n argument + * @return {@code n!} + * @throws MathArithmeticException if the result is too large to be represented + * by a {@code long}. + * @throws NotPositiveException if {@code n < 0}. + * @throws MathArithmeticException if {@code n > 20}: The factorial value is too + * large to fit in a {@code long}. + */ + public static long factorial(final int n) throws NotPositiveException, MathArithmeticException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, + n); + } + if (n > 20) { + throw new MathArithmeticException(); + } + return FACTORIALS[n]; + } + + /** + * Compute n!, the + * factorial of {@code n} (the product of the numbers 1 to n), as a + * {@code double}. + * The result should be small enough to fit into a {@code double}: The + * largest {@code n} for which {@code n!} does not exceed + * {@code Double.MAX_VALUE} is 170. If the computed value exceeds + * {@code Double.MAX_VALUE}, {@code Double.POSITIVE_INFINITY} is returned. + * + * @param n Argument. + * @return {@code n!} + * @throws NotPositiveException if {@code n < 0}. + */ + public static double factorialDouble(final int n) throws NotPositiveException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, + n); + } + if (n < 21) { + return FACTORIALS[n]; + } + return FastMath.floor(FastMath.exp(CombinatoricsUtils.factorialLog(n)) + 0.5); + } + + /** + * Compute the natural logarithm of the factorial of {@code n}. + * + * @param n Argument. + * @return {@code n!} + * @throws NotPositiveException if {@code n < 0}. + */ + public static double factorialLog(final int n) throws NotPositiveException { + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, + n); + } + if (n < 21) { + return FastMath.log(FACTORIALS[n]); + } + double logSum = 0; + for (int i = 2; i <= n; i++) { + logSum += FastMath.log(i); + } + return logSum; + } + + /** + * Returns the + * Stirling number of the second kind, "{@code S(n,k)}", the number of + * ways of partitioning an {@code n}-element set into {@code k} non-empty + * subsets. + *

        + * The preconditions are {@code 0 <= k <= n } (otherwise + * {@code NotPositiveException} is thrown) + *

        + * @param n the size of the set + * @param k the number of non-empty subsets + * @return {@code S(n,k)} + * @throws NotPositiveException if {@code k < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + * @throws MathArithmeticException if some overflow happens, typically for n exceeding 25 and + * k between 20 and n-2 (S(n,n-1) is handled specifically and does not overflow) + * @since 3.1 + */ + public static long stirlingS2(final int n, final int k) + throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException { + if (k < 0) { + throw new NotPositiveException(k); + } + if (k > n) { + throw new NumberIsTooLargeException(k, n, true); + } + + long[][] stirlingS2 = STIRLING_S2.get(); + + if (stirlingS2 == null) { + // the cache has never been initialized, compute the first numbers + // by direct recurrence relation + + // as S(26,9) = 11201516780955125625 is larger than Long.MAX_VALUE + // we must stop computation at row 26 + final int maxIndex = 26; + stirlingS2 = new long[maxIndex][]; + stirlingS2[0] = new long[] { 1l }; + for (int i = 1; i < stirlingS2.length; ++i) { + stirlingS2[i] = new long[i + 1]; + stirlingS2[i][0] = 0; + stirlingS2[i][1] = 1; + stirlingS2[i][i] = 1; + for (int j = 2; j < i; ++j) { + stirlingS2[i][j] = j * stirlingS2[i - 1][j] + stirlingS2[i - 1][j - 1]; + } + } + + // atomically save the cache + STIRLING_S2.compareAndSet(null, stirlingS2); + + } + + if (n < stirlingS2.length) { + // the number is in the small cache + return stirlingS2[n][k]; + } else { + // use explicit formula to compute the number without caching it + if (k == 0) { + return 0; + } else if (k == 1 || k == n) { + return 1; + } else if (k == 2) { + return (1l << (n - 1)) - 1l; + } else if (k == n - 1) { + return binomialCoefficient(n, 2); + } else { + // definition formula: note that this may trigger some overflow + long sum = 0; + long sign = ((k & 0x1) == 0) ? 1 : -1; + for (int j = 1; j <= k; ++j) { + sign = -sign; + sum += sign * binomialCoefficient(k, j) * ArithmeticUtils.pow(j, n); + if (sum < 0) { + // there was an overflow somewhere + throw new MathArithmeticException(LocalizedFormats.ARGUMENT_OUTSIDE_DOMAIN, + n, 0, stirlingS2.length - 1); + } + } + return sum / factorial(k); + } + } + + } + + /** + * Returns an iterator whose range is the k-element subsets of {0, ..., n - 1} + * represented as {@code int[]} arrays. + *

        + * The arrays returned by the iterator are sorted in descending order and + * they are visited in lexicographic order with significance from right to + * left. For example, combinationsIterator(4, 2) returns an Iterator that + * will generate the following sequence of arrays on successive calls to + * {@code next()}:

        + * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} + *

        + * If {@code k == 0} an Iterator containing an empty array is returned and + * if {@code k == n} an Iterator containing [0, ..., n -1] is returned.

        + * + * @param n Size of the set from which subsets are selected. + * @param k Size of the subsets to be enumerated. + * @return an {@link Iterator iterator} over the k-sets in n. + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + */ + public static Iterator combinationsIterator(int n, int k) { + return new Combinations(n, k).iterator(); + } + + /** + * Check binomial preconditions. + * + * @param n Size of the set. + * @param k Size of the subsets to be counted. + * @throws NotPositiveException if {@code n < 0}. + * @throws NumberIsTooLargeException if {@code k > n}. + */ + public static void checkBinomial(final int n, + final int k) + throws NumberIsTooLargeException, + NotPositiveException { + if (n < k) { + throw new NumberIsTooLargeException(LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER, + k, n, true); + } + if (n < 0) { + throw new NotPositiveException(LocalizedFormats.BINOMIAL_NEGATIVE_PARAMETER, n); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CompositeFormat.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CompositeFormat.java new file mode 100644 index 000000000..879488dc1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/CompositeFormat.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 com.fr.third.org.apache.commons.math3.util; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * Base class for formatters of composite objects (complex numbers, vectors ...). + * + */ +public class CompositeFormat { + + /** + * Class contains only static methods. + */ + private CompositeFormat() {} + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getInstance()} with the only customizing that the + * maximum number of fraction digits is set to 10. + * @return the default number format. + */ + public static NumberFormat getDefaultNumberFormat() { + return getDefaultNumberFormat(Locale.getDefault()); + } + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getInstance(java.util.Locale)} with the only + * customizing that the maximum number of fraction digits is set to 10. + * @param locale the specific locale used by the format. + * @return the default number format specific to the given locale. + */ + public static NumberFormat getDefaultNumberFormat(final Locale locale) { + final NumberFormat nf = NumberFormat.getInstance(locale); + nf.setMaximumFractionDigits(10); + return nf; + } + + /** + * Parses source until a non-whitespace character is found. + * + * @param source the string to parse + * @param pos input/output parsing parameter. On output, pos + * holds the index of the next non-whitespace character. + */ + public static void parseAndIgnoreWhitespace(final String source, + final ParsePosition pos) { + parseNextCharacter(source, pos); + pos.setIndex(pos.getIndex() - 1); + } + + /** + * Parses source until a non-whitespace character is found. + * + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the first non-whitespace character. + */ + public static char parseNextCharacter(final String source, + final ParsePosition pos) { + int index = pos.getIndex(); + final int n = source.length(); + char ret = 0; + + if (index < n) { + char c; + do { + c = source.charAt(index++); + } while (Character.isWhitespace(c) && index < n); + pos.setIndex(index); + + if (index < n) { + ret = c; + } + } + + return ret; + } + + /** + * Parses source for special double values. These values + * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY. + * + * @param source the string to parse + * @param value the special value to parse. + * @param pos input/output parsing parameter. + * @return the special number. + */ + private static Number parseNumber(final String source, final double value, + final ParsePosition pos) { + Number ret = null; + + StringBuilder sb = new StringBuilder(); + sb.append('('); + sb.append(value); + sb.append(')'); + + final int n = sb.length(); + final int startIndex = pos.getIndex(); + final int endIndex = startIndex + n; + if (endIndex < source.length() && + source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) { + ret = Double.valueOf(value); + pos.setIndex(endIndex); + } + + return ret; + } + + /** + * Parses source for a number. This method can parse normal, + * numeric values as well as special values. These special values include + * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY. + * + * @param source the string to parse + * @param format the number format used to parse normal, numeric values. + * @param pos input/output parsing parameter. + * @return the parsed number. + */ + public static Number parseNumber(final String source, final NumberFormat format, + final ParsePosition pos) { + final int startIndex = pos.getIndex(); + Number number = format.parse(source, pos); + final int endIndex = pos.getIndex(); + + // check for error parsing number + if (startIndex == endIndex) { + // try parsing special numbers + final double[] special = { + Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY + }; + for (int i = 0; i < special.length; ++i) { + number = parseNumber(source, special[i], pos); + if (number != null) { + break; + } + } + } + + return number; + } + + /** + * Parse source for an expected fixed string. + * @param source the string to parse + * @param expected expected string + * @param pos input/output parsing parameter. + * @return true if the expected string was there + */ + public static boolean parseFixedstring(final String source, + final String expected, + final ParsePosition pos) { + + final int startIndex = pos.getIndex(); + final int endIndex = startIndex + expected.length(); + if ((startIndex >= source.length()) || + (endIndex > source.length()) || + (source.substring(startIndex, endIndex).compareTo(expected) != 0)) { + // set index back to start, error index should be the start index + pos.setIndex(startIndex); + pos.setErrorIndex(startIndex); + return false; + } + + // the string was here + pos.setIndex(endIndex); + return true; + } + + /** + * Formats a double value to produce a string. In general, the value is + * formatted using the formatting rules of format. There are + * three exceptions to this: + *
          + *
        1. NaN is formatted as '(NaN)'
        2. + *
        3. Positive infinity is formatted as '(Infinity)'
        4. + *
        5. Negative infinity is formatted as '(-Infinity)'
        6. + *
        + * + * @param value the double to format. + * @param format the format used. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public static StringBuffer formatDouble(final double value, final NumberFormat format, + final StringBuffer toAppendTo, + final FieldPosition pos) { + if( Double.isNaN(value) || Double.isInfinite(value) ) { + toAppendTo.append('('); + toAppendTo.append(value); + toAppendTo.append(')'); + } else { + format.format(value, toAppendTo, pos); + } + return toAppendTo; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ContinuedFraction.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ContinuedFraction.java new file mode 100644 index 000000000..86ab500e1 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ContinuedFraction.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.exception.ConvergenceException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Provides a generic means to evaluate continued fractions. Subclasses simply + * provided the a and b coefficients to evaluate the continued fraction. + * + *

        + * References: + *

        + *

        + * + */ +public abstract class ContinuedFraction { + /** Maximum allowed numerical error. */ + private static final double DEFAULT_EPSILON = 10e-9; + + /** + * Default constructor. + */ + protected ContinuedFraction() { + super(); + } + + /** + * Access the n-th a coefficient of the continued fraction. Since a can be + * a function of the evaluation point, x, that is passed in as well. + * @param n the coefficient index to retrieve. + * @param x the evaluation point. + * @return the n-th a coefficient. + */ + protected abstract double getA(int n, double x); + + /** + * Access the n-th b coefficient of the continued fraction. Since b can be + * a function of the evaluation point, x, that is passed in as well. + * @param n the coefficient index to retrieve. + * @param x the evaluation point. + * @return the n-th b coefficient. + */ + protected abstract double getB(int n, double x); + + /** + * Evaluates the continued fraction at the value x. + * @param x the evaluation point. + * @return the value of the continued fraction evaluated at x. + * @throws ConvergenceException if the algorithm fails to converge. + */ + public double evaluate(double x) throws ConvergenceException { + return evaluate(x, DEFAULT_EPSILON, Integer.MAX_VALUE); + } + + /** + * Evaluates the continued fraction at the value x. + * @param x the evaluation point. + * @param epsilon maximum error allowed. + * @return the value of the continued fraction evaluated at x. + * @throws ConvergenceException if the algorithm fails to converge. + */ + public double evaluate(double x, double epsilon) throws ConvergenceException { + return evaluate(x, epsilon, Integer.MAX_VALUE); + } + + /** + * Evaluates the continued fraction at the value x. + * @param x the evaluation point. + * @param maxIterations maximum number of convergents + * @return the value of the continued fraction evaluated at x. + * @throws ConvergenceException if the algorithm fails to converge. + * @throws MaxCountExceededException if maximal number of iterations is reached + */ + public double evaluate(double x, int maxIterations) + throws ConvergenceException, MaxCountExceededException { + return evaluate(x, DEFAULT_EPSILON, maxIterations); + } + + /** + * Evaluates the continued fraction at the value x. + *

        + * The implementation of this method is based on the modified Lentz algorithm as described + * on page 18 ff. in: + *

        + * Note: the implementation uses the terms ai and bi as defined in + * Continued Fraction @ MathWorld. + *

        + * + * @param x the evaluation point. + * @param epsilon maximum error allowed. + * @param maxIterations maximum number of convergents + * @return the value of the continued fraction evaluated at x. + * @throws ConvergenceException if the algorithm fails to converge. + * @throws MaxCountExceededException if maximal number of iterations is reached + */ + public double evaluate(double x, double epsilon, int maxIterations) + throws ConvergenceException, MaxCountExceededException { + final double small = 1e-50; + double hPrev = getA(0, x); + + // use the value of small as epsilon criteria for zero checks + if (Precision.equals(hPrev, 0.0, small)) { + hPrev = small; + } + + int n = 1; + double dPrev = 0.0; + double cPrev = hPrev; + double hN = hPrev; + + while (n < maxIterations) { + final double a = getA(n, x); + final double b = getB(n, x); + + double dN = a + b * dPrev; + if (Precision.equals(dN, 0.0, small)) { + dN = small; + } + double cN = a + b / cPrev; + if (Precision.equals(cN, 0.0, small)) { + cN = small; + } + + dN = 1 / dN; + final double deltaN = cN * dN; + hN = hPrev * deltaN; + + if (Double.isInfinite(hN)) { + throw new ConvergenceException(LocalizedFormats.CONTINUED_FRACTION_INFINITY_DIVERGENCE, + x); + } + if (Double.isNaN(hN)) { + throw new ConvergenceException(LocalizedFormats.CONTINUED_FRACTION_NAN_DIVERGENCE, + x); + } + + if (FastMath.abs(deltaN - 1.0) < epsilon) { + break; + } + + dPrev = dN; + cPrev = cN; + hPrev = hN; + n++; + } + + if (n >= maxIterations) { + throw new MaxCountExceededException(LocalizedFormats.NON_CONVERGENT_CONTINUED_FRACTION, + maxIterations, x); + } + + return hN; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64.java new file mode 100644 index 000000000..7c91ee627 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64.java @@ -0,0 +1,695 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.RealFieldElement; +import com.fr.third.org.apache.commons.math3.Field; + +/** + * This class wraps a {@code double} value in an object. It is similar to the + * standard class {@link Double}, while also implementing the + * {@link RealFieldElement} interface. + * + * @since 3.1 + */ +public class Decimal64 extends Number + implements RealFieldElement, Comparable { + + /** The constant value of {@code 0d} as a {@code Decimal64}. */ + public static final Decimal64 ZERO; + + /** The constant value of {@code 1d} as a {@code Decimal64}. */ + public static final Decimal64 ONE; + + /** + * The constant value of {@link Double#NEGATIVE_INFINITY} as a + * {@code Decimal64}. + */ + public static final Decimal64 NEGATIVE_INFINITY; + + /** + * The constant value of {@link Double#POSITIVE_INFINITY} as a + * {@code Decimal64}. + */ + public static final Decimal64 POSITIVE_INFINITY; + + /** The constant value of {@link Double#NaN} as a {@code Decimal64}. */ + public static final Decimal64 NAN; + + /** */ + private static final long serialVersionUID = 20120227L; + + static { + ZERO = new Decimal64(0d); + ONE = new Decimal64(1d); + NEGATIVE_INFINITY = new Decimal64(Double.NEGATIVE_INFINITY); + POSITIVE_INFINITY = new Decimal64(Double.POSITIVE_INFINITY); + NAN = new Decimal64(Double.NaN); + } + + /** The primitive {@code double} value of this object. */ + private final double value; + + /** + * Creates a new instance of this class. + * + * @param x the primitive {@code double} value of the object to be created + */ + public Decimal64(final double x) { + this.value = x; + } + + /* + * Methods from the FieldElement interface. + */ + + /** {@inheritDoc} */ + public Field getField() { + return Decimal64Field.getInstance(); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.add(a).equals(new Decimal64(this.doubleValue() + * + a.doubleValue()))}. + */ + public Decimal64 add(final Decimal64 a) { + return new Decimal64(this.value + a.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.subtract(a).equals(new Decimal64(this.doubleValue() + * - a.doubleValue()))}. + */ + public Decimal64 subtract(final Decimal64 a) { + return new Decimal64(this.value - a.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.negate().equals(new Decimal64(-this.doubleValue()))}. + */ + public Decimal64 negate() { + return new Decimal64(-this.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.multiply(a).equals(new Decimal64(this.doubleValue() + * * a.doubleValue()))}. + */ + public Decimal64 multiply(final Decimal64 a) { + return new Decimal64(this.value * a.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.multiply(n).equals(new Decimal64(n * this.doubleValue()))}. + */ + public Decimal64 multiply(final int n) { + return new Decimal64(n * this.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.divide(a).equals(new Decimal64(this.doubleValue() + * / a.doubleValue()))}. + * + */ + public Decimal64 divide(final Decimal64 a) { + return new Decimal64(this.value / a.value); + } + + /** + * {@inheritDoc} + * + * The current implementation strictly enforces + * {@code this.reciprocal().equals(new Decimal64(1.0 + * / this.doubleValue()))}. + */ + public Decimal64 reciprocal() { + return new Decimal64(1.0 / this.value); + } + + /* + * Methods from the Number abstract class + */ + + /** + * {@inheritDoc} + * + * The current implementation performs casting to a {@code byte}. + */ + @Override + public byte byteValue() { + return (byte) value; + } + + /** + * {@inheritDoc} + * + * The current implementation performs casting to a {@code short}. + */ + @Override + public short shortValue() { + return (short) value; + } + + /** + * {@inheritDoc} + * + * The current implementation performs casting to a {@code int}. + */ + @Override + public int intValue() { + return (int) value; + } + + /** + * {@inheritDoc} + * + * The current implementation performs casting to a {@code long}. + */ + @Override + public long longValue() { + return (long) value; + } + + /** + * {@inheritDoc} + * + * The current implementation performs casting to a {@code float}. + */ + @Override + public float floatValue() { + return (float) value; + } + + /** {@inheritDoc} */ + @Override + public double doubleValue() { + return value; + } + + /* + * Methods from the Comparable interface. + */ + + /** + * {@inheritDoc} + * + * The current implementation returns the same value as + *
        {@code new Double(this.doubleValue()).compareTo(new + * Double(o.doubleValue()))}
        + * + * @see Double#compareTo(Double) + */ + public int compareTo(final Decimal64 o) { + return Double.compare(this.value, o.value); + } + + /* + * Methods from the Object abstract class. + */ + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Decimal64) { + final Decimal64 that = (Decimal64) obj; + return Double.doubleToLongBits(this.value) == Double + .doubleToLongBits(that.value); + } + return false; + } + + /** + * {@inheritDoc} + * + * The current implementation returns the same value as + * {@code new Double(this.doubleValue()).hashCode()} + * + * @see Double#hashCode() + */ + @Override + public int hashCode() { + long v = Double.doubleToLongBits(value); + return (int) (v ^ (v >>> 32)); + } + + /** + * {@inheritDoc} + * + * The returned {@code String} is equal to + * {@code Double.toString(this.doubleValue())} + * + * @see Double#toString(double) + */ + @Override + public String toString() { + return Double.toString(value); + } + + /* + * Methods inspired by the Double class. + */ + + /** + * Returns {@code true} if {@code this} double precision number is infinite + * ({@link Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY}). + * + * @return {@code true} if {@code this} number is infinite + */ + public boolean isInfinite() { + return Double.isInfinite(value); + } + + /** + * Returns {@code true} if {@code this} double precision number is + * Not-a-Number ({@code NaN}), false otherwise. + * + * @return {@code true} if {@code this} is {@code NaN} + */ + public boolean isNaN() { + return Double.isNaN(value); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public double getReal() { + return value; + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 add(final double a) { + return new Decimal64(value + a); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 subtract(final double a) { + return new Decimal64(value - a); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 multiply(final double a) { + return new Decimal64(value * a); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 divide(final double a) { + return new Decimal64(value / a); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 remainder(final double a) { + return new Decimal64(FastMath.IEEEremainder(value, a)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 remainder(final Decimal64 a) { + return new Decimal64(FastMath.IEEEremainder(value, a.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 abs() { + return new Decimal64(FastMath.abs(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 ceil() { + return new Decimal64(FastMath.ceil(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 floor() { + return new Decimal64(FastMath.floor(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 rint() { + return new Decimal64(FastMath.rint(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public long round() { + return FastMath.round(value); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 signum() { + return new Decimal64(FastMath.signum(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 copySign(final Decimal64 sign) { + return new Decimal64(FastMath.copySign(value, sign.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 copySign(final double sign) { + return new Decimal64(FastMath.copySign(value, sign)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 scalb(final int n) { + return new Decimal64(FastMath.scalb(value, n)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 hypot(final Decimal64 y) { + return new Decimal64(FastMath.hypot(value, y.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 sqrt() { + return new Decimal64(FastMath.sqrt(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 cbrt() { + return new Decimal64(FastMath.cbrt(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 rootN(final int n) { + if (value < 0) { + return new Decimal64(-FastMath.pow(-value, 1.0 / n)); + } else { + return new Decimal64(FastMath.pow(value, 1.0 / n)); + } + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 pow(final double p) { + return new Decimal64(FastMath.pow(value, p)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 pow(final int n) { + return new Decimal64(FastMath.pow(value, n)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 pow(final Decimal64 e) { + return new Decimal64(FastMath.pow(value, e.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 exp() { + return new Decimal64(FastMath.exp(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 expm1() { + return new Decimal64(FastMath.expm1(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 log() { + return new Decimal64(FastMath.log(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 log1p() { + return new Decimal64(FastMath.log1p(value)); + } + + /** Base 10 logarithm. + * @return base 10 logarithm of the instance + * @since 3.2 + */ + public Decimal64 log10() { + return new Decimal64(FastMath.log10(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 cos() { + return new Decimal64(FastMath.cos(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 sin() { + return new Decimal64(FastMath.sin(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 tan() { + return new Decimal64(FastMath.tan(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 acos() { + return new Decimal64(FastMath.acos(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 asin() { + return new Decimal64(FastMath.asin(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 atan() { + return new Decimal64(FastMath.atan(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 atan2(final Decimal64 x) { + return new Decimal64(FastMath.atan2(value, x.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 cosh() { + return new Decimal64(FastMath.cosh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 sinh() { + return new Decimal64(FastMath.sinh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 tanh() { + return new Decimal64(FastMath.tanh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 acosh() { + return new Decimal64(FastMath.acosh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 asinh() { + return new Decimal64(FastMath.asinh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 atanh() { + return new Decimal64(FastMath.atanh(value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final Decimal64[] a, final Decimal64[] b) + throws DimensionMismatchException { + if (a.length != b.length) { + throw new DimensionMismatchException(a.length, b.length); + } + final double[] aDouble = new double[a.length]; + final double[] bDouble = new double[b.length]; + for (int i = 0; i < a.length; ++i) { + aDouble[i] = a[i].value; + bDouble[i] = b[i].value; + } + return new Decimal64(MathArrays.linearCombination(aDouble, bDouble)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final double[] a, final Decimal64[] b) + throws DimensionMismatchException { + if (a.length != b.length) { + throw new DimensionMismatchException(a.length, b.length); + } + final double[] bDouble = new double[b.length]; + for (int i = 0; i < a.length; ++i) { + bDouble[i] = b[i].value; + } + return new Decimal64(MathArrays.linearCombination(a, bDouble)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final Decimal64 a1, final Decimal64 b1, + final Decimal64 a2, final Decimal64 b2) { + return new Decimal64(MathArrays.linearCombination(a1.value, b1.value, + a2.value, b2.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final double a1, final Decimal64 b1, + final double a2, final Decimal64 b2) { + return new Decimal64(MathArrays.linearCombination(a1, b1.value, + a2, b2.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final Decimal64 a1, final Decimal64 b1, + final Decimal64 a2, final Decimal64 b2, + final Decimal64 a3, final Decimal64 b3) { + return new Decimal64(MathArrays.linearCombination(a1.value, b1.value, + a2.value, b2.value, + a3.value, b3.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final double a1, final Decimal64 b1, + final double a2, final Decimal64 b2, + final double a3, final Decimal64 b3) { + return new Decimal64(MathArrays.linearCombination(a1, b1.value, + a2, b2.value, + a3, b3.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final Decimal64 a1, final Decimal64 b1, + final Decimal64 a2, final Decimal64 b2, + final Decimal64 a3, final Decimal64 b3, + final Decimal64 a4, final Decimal64 b4) { + return new Decimal64(MathArrays.linearCombination(a1.value, b1.value, + a2.value, b2.value, + a3.value, b3.value, + a4.value, b4.value)); + } + + /** {@inheritDoc} + * @since 3.2 + */ + public Decimal64 linearCombination(final double a1, final Decimal64 b1, + final double a2, final Decimal64 b2, + final double a3, final Decimal64 b3, + final double a4, final Decimal64 b4) { + return new Decimal64(MathArrays.linearCombination(a1, b1.value, + a2, b2.value, + a3, b3.value, + a4, b4.value)); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64Field.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64Field.java new file mode 100644 index 000000000..08c4a1c9a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Decimal64Field.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * The field of double precision floating-point numbers. + * + * @since 3.1 + * @see Decimal64 + */ +public class Decimal64Field implements Field { + + /** The unique instance of this class. */ + private static final Decimal64Field INSTANCE = new Decimal64Field(); + + /** Default constructor. */ + private Decimal64Field() { + // Do nothing + } + + /** + * Returns the unique instance of this class. + * + * @return the unique instance of this class + */ + public static final Decimal64Field getInstance() { + return INSTANCE; + } + + /** {@inheritDoc} */ + public Decimal64 getZero() { + return Decimal64.ZERO; + } + + /** {@inheritDoc} */ + public Decimal64 getOne() { + return Decimal64.ONE; + } + + /** {@inheritDoc} */ + public Class> getRuntimeClass() { + return Decimal64.class; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DefaultTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DefaultTransformer.java new file mode 100644 index 000000000..1d842521d --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DefaultTransformer.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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.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. + * + */ +public class DefaultTransformer implements NumberTransformer, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 4019938025047800455L; + + /** + * @param o the object that gets transformed. + * @return a double primitive representation of the Object o. + * @throws NullArgumentException if Object o is {@code null}. + * @throws MathIllegalArgumentException if Object o + * cannot successfully be transformed + * @see Commons Collections Transformer + */ + public double transform(Object o) + throws NullArgumentException, MathIllegalArgumentException { + + if (o == null) { + throw new NullArgumentException(LocalizedFormats.OBJECT_TRANSFORMATION); + } + + if (o instanceof Number) { + return ((Number)o).doubleValue(); + } + + try { + return Double.parseDouble(o.toString()); + } catch (NumberFormatException e) { + throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_TRANSFORM_TO_DOUBLE, + o.toString()); + } + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + return other instanceof DefaultTransformer; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + // some arbitrary number ... + return 401993047; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DoubleArray.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DoubleArray.java new file mode 100644 index 000000000..14671176a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/DoubleArray.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 com.fr.third.org.apache.commons.math3.util; + + +/** + * Provides a standard interface for double arrays. Allows different + * array implementations to support various storage mechanisms + * such as automatic expansion, contraction, and array "rolling". + * + */ +public interface DoubleArray { + + /** + * Returns the number of elements currently in the array. Please note + * that this may be different from the length of the internal storage array. + * + * @return number of elements + */ + int getNumElements(); + + /** + * Returns the element at the specified index. Note that if an + * out of bounds index is supplied a ArrayIndexOutOfBoundsException + * will be thrown. + * + * @param index index to fetch a value from + * @return value stored at the specified index + * @throws ArrayIndexOutOfBoundsException if index is less than + * zero or is greater than getNumElements() - 1. + */ + double getElement(int index); + + /** + * Sets the element at the specified index. If the specified index is greater than + * getNumElements() - 1, the numElements property + * is increased to index +1 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 index is less than + * zero. + */ + void setElement(int index, double value); + + /** + * Adds an element to the end of this expandable array + * + * @param value to be added to end of array + */ + void addElement(double value); + + /** + * Adds elements to the end of this expandable array + * + * @param values to be added to end of array + */ + void addElements(double[] values); + + /** + *

        + * Adds an element to the end of the array and removes the first + * element in the array. Returns the discarded first element. + * The effect is similar to a push operation in a FIFO queue. + *

        + *

        + * Example: If the array contains the elements 1, 2, 3, 4 (in that order) + * and addElementRolling(5) is invoked, the result is an array containing + * the entries 2, 3, 4, 5 and the value returned is 1. + *

        + * + * @param value the value to be added to the array + * @return the value which has been discarded or "pushed" out of the array + * by this rolling insert + */ + double addElementRolling(double value); + + /** + * Returns a double[] array containing the elements of this + * DoubleArray. 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 DoubleArray. + * + * @return all elements added to the array + */ + double[] getElements(); + + /** + * Clear the double array + */ + void clear(); + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMath.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMath.java new file mode 100644 index 000000000..9662031de --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMath.java @@ -0,0 +1,4296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.io.PrintStream; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Faster, more accurate, portable alternative to {@link Math} and + * {@link StrictMath} for large scale computation. + *

        + * FastMath is a drop-in replacement for both Math and StrictMath. This + * means that for any method in Math (say {@code Math.sin(x)} or + * {@code Math.cbrt(y)}), user can directly change the class and use the + * methods as is (using {@code FastMath.sin(x)} or {@code FastMath.cbrt(y)} + * in the previous example). + *

        + *

        + * FastMath speed is achieved by relying heavily on optimizing compilers + * to native code present in many JVMs today and use of large tables. + * The larger tables are lazily initialised on first use, so that the setup + * time does not penalise methods that don't need them. + *

        + *

        + * Note that FastMath is + * extensively used inside Apache Commons Math, so by calling some algorithms, + * the overhead when the the tables need to be intialised will occur + * regardless of the end-user calling FastMath methods directly or not. + * Performance figures for a specific JVM and hardware can be evaluated by + * running the FastMathTestPerformance tests in the test directory of the source + * distribution. + *

        + *

        + * FastMath accuracy should be mostly independent of the JVM as it relies only + * on IEEE-754 basic operations and on embedded tables. Almost all operations + * are accurate to about 0.5 ulp throughout the domain range. This statement, + * of course is only a rough global observed behavior, it is not a + * guarantee for every double numbers input (see William Kahan's Table + * Maker's Dilemma). + *

        + *

        + * FastMath additionally implements the following methods not found in Math/StrictMath: + *

          + *
        • {@link #asinh(double)}
        • + *
        • {@link #acosh(double)}
        • + *
        • {@link #atanh(double)}
        • + *
        + * The following methods are found in Math/StrictMath since 1.6 only, they are provided + * by FastMath even in 1.5 Java virtual machines + *
          + *
        • {@link #copySign(double, double)}
        • + *
        • {@link #getExponent(double)}
        • + *
        • {@link #nextAfter(double,double)}
        • + *
        • {@link #nextUp(double)}
        • + *
        • {@link #scalb(double, int)}
        • + *
        • {@link #copySign(float, float)}
        • + *
        • {@link #getExponent(float)}
        • + *
        • {@link #nextAfter(float,double)}
        • + *
        • {@link #nextUp(float)}
        • + *
        • {@link #scalb(float, int)}
        • + *
        + *

        + * @since 2.2 + */ +public class FastMath { + /** Archimede's constant PI, ratio of circle circumference to diameter. */ + public static final double PI = 105414357.0 / 33554432.0 + 1.984187159361080883e-9; + + /** Napier's constant e, base of the natural logarithm. */ + public static final double E = 2850325.0 / 1048576.0 + 8.254840070411028747e-8; + + /** Index of exp(0) in the array of integer exponentials. */ + static final int EXP_INT_TABLE_MAX_INDEX = 750; + /** Length of the array of integer exponentials. */ + static final int EXP_INT_TABLE_LEN = EXP_INT_TABLE_MAX_INDEX * 2; + /** Logarithm table length. */ + static final int LN_MANT_LEN = 1024; + /** Exponential fractions table length. */ + static final int EXP_FRAC_TABLE_LEN = 1025; // 0, 1/1024, ... 1024/1024 + + /** StrictMath.log(Double.MAX_VALUE): {@value} */ + private static final double LOG_MAX_VALUE = StrictMath.log(Double.MAX_VALUE); + + /** Indicator for tables initialization. + *

        + * This compile-time constant should be set to true only if one explicitly + * wants to compute the tables at class loading time instead of using the + * already computed ones provided as literal arrays below. + *

        + */ + private static final boolean RECOMPUTE_TABLES_AT_RUNTIME = false; + + /** log(2) (high bits). */ + private static final double LN_2_A = 0.693147063255310059; + + /** log(2) (low bits). */ + private static final double LN_2_B = 1.17304635250823482e-7; + + /** Coefficients for log, when input 0.99 < x < 1.01. */ + private static final double LN_QUICK_COEF[][] = { + {1.0, 5.669184079525E-24}, + {-0.25, -0.25}, + {0.3333333134651184, 1.986821492305628E-8}, + {-0.25, -6.663542893624021E-14}, + {0.19999998807907104, 1.1921056801463227E-8}, + {-0.1666666567325592, -7.800414592973399E-9}, + {0.1428571343421936, 5.650007086920087E-9}, + {-0.12502530217170715, -7.44321345601866E-11}, + {0.11113807559013367, 9.219544613762692E-9}, + }; + + /** Coefficients for log in the range of 1.0 < x < 1.0 + 2^-10. */ + private static final double LN_HI_PREC_COEF[][] = { + {1.0, -6.032174644509064E-23}, + {-0.25, -0.25}, + {0.3333333134651184, 1.9868161777724352E-8}, + {-0.2499999701976776, -2.957007209750105E-8}, + {0.19999954104423523, 1.5830993332061267E-10}, + {-0.16624879837036133, -2.6033824355191673E-8} + }; + + /** Sine, Cosine, Tangent tables are for 0, 1/8, 2/8, ... 13/8 = PI/2 approx. */ + private static final int SINE_TABLE_LEN = 14; + + /** Sine table (high bits). */ + private static final double SINE_TABLE_A[] = + { + +0.0d, + +0.1246747374534607d, + +0.24740394949913025d, + +0.366272509098053d, + +0.4794255495071411d, + +0.5850973129272461d, + +0.6816387176513672d, + +0.7675435543060303d, + +0.8414709568023682d, + +0.902267575263977d, + +0.9489846229553223d, + +0.9808930158615112d, + +0.9974949359893799d, + +0.9985313415527344d, + }; + + /** Sine table (low bits). */ + private static final double SINE_TABLE_B[] = + { + +0.0d, + -4.068233003401932E-9d, + +9.755392680573412E-9d, + +1.9987994582857286E-8d, + -1.0902938113007961E-8d, + -3.9986783938944604E-8d, + +4.23719669792332E-8d, + -5.207000323380292E-8d, + +2.800552834259E-8d, + +1.883511811213715E-8d, + -3.5997360512765566E-9d, + +4.116164446561962E-8d, + +5.0614674548127384E-8d, + -1.0129027912496858E-9d, + }; + + /** Cosine table (high bits). */ + private static final double COSINE_TABLE_A[] = + { + +1.0d, + +0.9921976327896118d, + +0.9689123630523682d, + +0.9305076599121094d, + +0.8775825500488281d, + +0.8109631538391113d, + +0.7316888570785522d, + +0.6409968137741089d, + +0.5403022766113281d, + +0.4311765432357788d, + +0.3153223395347595d, + +0.19454771280288696d, + +0.07073719799518585d, + -0.05417713522911072d, + }; + + /** Cosine table (low bits). */ + private static final double COSINE_TABLE_B[] = + { + +0.0d, + +3.4439717236742845E-8d, + +5.865827662008209E-8d, + -3.7999795083850525E-8d, + +1.184154459111628E-8d, + -3.43338934259355E-8d, + +1.1795268640216787E-8d, + +4.438921624363781E-8d, + +2.925681159240093E-8d, + -2.6437112632041807E-8d, + +2.2860509143963117E-8d, + -4.813899778443457E-9d, + +3.6725170580355583E-9d, + +2.0217439756338078E-10d, + }; + + + /** Tangent table, used by atan() (high bits). */ + private static final double TANGENT_TABLE_A[] = + { + +0.0d, + +0.1256551444530487d, + +0.25534194707870483d, + +0.3936265707015991d, + +0.5463024377822876d, + +0.7214844226837158d, + +0.9315965175628662d, + +1.1974215507507324d, + +1.5574076175689697d, + +2.092571258544922d, + +3.0095696449279785d, + +5.041914939880371d, + +14.101419448852539d, + -18.430862426757812d, + }; + + /** Tangent table, used by atan() (low bits). */ + private static final double TANGENT_TABLE_B[] = + { + +0.0d, + -7.877917738262007E-9d, + -2.5857668567479893E-8d, + +5.2240336371356666E-9d, + +5.206150291559893E-8d, + +1.8307188599677033E-8d, + -5.7618793749770706E-8d, + +7.848361555046424E-8d, + +1.0708593250394448E-7d, + +1.7827257129423813E-8d, + +2.893485277253286E-8d, + +3.1660099222737955E-7d, + +4.983191803254889E-7d, + -3.356118100840571E-7d, + }; + + /** Bits of 1/(2*pi), need for reducePayneHanek(). */ + private static final long RECIP_2PI[] = new long[] { + (0x28be60dbL << 32) | 0x9391054aL, + (0x7f09d5f4L << 32) | 0x7d4d3770L, + (0x36d8a566L << 32) | 0x4f10e410L, + (0x7f9458eaL << 32) | 0xf7aef158L, + (0x6dc91b8eL << 32) | 0x909374b8L, + (0x01924bbaL << 32) | 0x82746487L, + (0x3f877ac7L << 32) | 0x2c4a69cfL, + (0xba208d7dL << 32) | 0x4baed121L, + (0x3a671c09L << 32) | 0xad17df90L, + (0x4e64758eL << 32) | 0x60d4ce7dL, + (0x272117e2L << 32) | 0xef7e4a0eL, + (0xc7fe25ffL << 32) | 0xf7816603L, + (0xfbcbc462L << 32) | 0xd6829b47L, + (0xdb4d9fb3L << 32) | 0xc9f2c26dL, + (0xd3d18fd9L << 32) | 0xa797fa8bL, + (0x5d49eeb1L << 32) | 0xfaf97c5eL, + (0xcf41ce7dL << 32) | 0xe294a4baL, + 0x9afed7ecL << 32 }; + + /** Bits of pi/4, need for reducePayneHanek(). */ + private static final long PI_O_4_BITS[] = new long[] { + (0xc90fdaa2L << 32) | 0x2168c234L, + (0xc4c6628bL << 32) | 0x80dc1cd1L }; + + /** Eighths. + * This is used by sinQ, because its faster to do a table lookup than + * a multiply in this time-critical routine + */ + private static final double EIGHTHS[] = {0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.125, 1.25, 1.375, 1.5, 1.625}; + + /** Table of 2^((n+2)/3) */ + private static final double CBRTTWO[] = { 0.6299605249474366, + 0.7937005259840998, + 1.0, + 1.2599210498948732, + 1.5874010519681994 }; + + /* + * There are 52 bits in the mantissa of a double. + * For additional precision, the code splits double numbers into two parts, + * by clearing the low order 30 bits if possible, and then performs the arithmetic + * on each half separately. + */ + + /** + * 0x40000000 - used to split a double into two parts, both with the low order bits cleared. + * Equivalent to 2^30. + */ + private static final long HEX_40000000 = 0x40000000L; // 1073741824L + + /** Mask used to clear low order 30 bits */ + private static final long MASK_30BITS = -1L - (HEX_40000000 -1); // 0xFFFFFFFFC0000000L; + + /** Mask used to clear the non-sign part of an int. */ + private static final int MASK_NON_SIGN_INT = 0x7fffffff; + + /** Mask used to clear the non-sign part of a long. */ + private static final long MASK_NON_SIGN_LONG = 0x7fffffffffffffffl; + + /** Mask used to extract exponent from double bits. */ + private static final long MASK_DOUBLE_EXPONENT = 0x7ff0000000000000L; + + /** Mask used to extract mantissa from double bits. */ + private static final long MASK_DOUBLE_MANTISSA = 0x000fffffffffffffL; + + /** Mask used to add implicit high order bit for normalized double. */ + private static final long IMPLICIT_HIGH_BIT = 0x0010000000000000L; + + /** 2^52 - double numbers this large must be integral (no fraction) or NaN or Infinite */ + private static final double TWO_POWER_52 = 4503599627370496.0; + + /** Constant: {@value}. */ + private static final double F_1_3 = 1d / 3d; + /** Constant: {@value}. */ + private static final double F_1_5 = 1d / 5d; + /** Constant: {@value}. */ + private static final double F_1_7 = 1d / 7d; + /** Constant: {@value}. */ + private static final double F_1_9 = 1d / 9d; + /** Constant: {@value}. */ + private static final double F_1_11 = 1d / 11d; + /** Constant: {@value}. */ + private static final double F_1_13 = 1d / 13d; + /** Constant: {@value}. */ + private static final double F_1_15 = 1d / 15d; + /** Constant: {@value}. */ + private static final double F_1_17 = 1d / 17d; + /** Constant: {@value}. */ + private static final double F_3_4 = 3d / 4d; + /** Constant: {@value}. */ + private static final double F_15_16 = 15d / 16d; + /** Constant: {@value}. */ + private static final double F_13_14 = 13d / 14d; + /** Constant: {@value}. */ + private static final double F_11_12 = 11d / 12d; + /** Constant: {@value}. */ + private static final double F_9_10 = 9d / 10d; + /** Constant: {@value}. */ + private static final double F_7_8 = 7d / 8d; + /** Constant: {@value}. */ + private static final double F_5_6 = 5d / 6d; + /** Constant: {@value}. */ + private static final double F_1_2 = 1d / 2d; + /** Constant: {@value}. */ + private static final double F_1_4 = 1d / 4d; + + /** + * Private Constructor + */ + private FastMath() {} + + // Generic helper methods + + /** + * Get the high order bits from the mantissa. + * Equivalent to adding and subtracting HEX_40000 but also works for very large numbers + * + * @param d the value to split + * @return the high order part of the mantissa + */ + private static double doubleHighPart(double d) { + if (d > -Precision.SAFE_MIN && d < Precision.SAFE_MIN){ + return d; // These are un-normalised - don't try to convert + } + long xl = Double.doubleToRawLongBits(d); // can take raw bits because just gonna convert it back + xl &= MASK_30BITS; // Drop low order bits + return Double.longBitsToDouble(xl); + } + + /** Compute the square root of a number. + *

        Note: this implementation currently delegates to {@link Math#sqrt} + * @param a number on which evaluation is done + * @return square root of a + */ + public static double sqrt(final double a) { + return Math.sqrt(a); + } + + /** Compute the hyperbolic cosine of a number. + * @param x number on which evaluation is done + * @return hyperbolic cosine of x + */ + public static double cosh(double x) { + if (x != x) { + return x; + } + + // cosh[z] = (exp(z) + exp(-z))/2 + + // for numbers with magnitude 20 or so, + // exp(-z) can be ignored in comparison with exp(z) + + if (x > 20) { + if (x >= LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(x); + } + } else if (x < -20) { + if (x <= -LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(-0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(-x); + } + } + + final double hiPrec[] = new double[2]; + if (x < 0.0) { + x = -x; + } + exp(x, 0.0, hiPrec); + + double ya = hiPrec[0] + hiPrec[1]; + double yb = -(ya - hiPrec[0] - hiPrec[1]); + + double temp = ya * HEX_40000000; + double yaa = ya + temp - temp; + double yab = ya - yaa; + + // recip = 1/y + double recip = 1.0/ya; + temp = recip * HEX_40000000; + double recipa = recip + temp - temp; + double recipb = recip - recipa; + + // Correct for rounding in division + recipb += (1.0 - yaa*recipa - yaa*recipb - yab*recipa - yab*recipb) * recip; + // Account for yb + recipb += -yb * recip * recip; + + // y = y + 1/y + temp = ya + recipa; + yb += -(temp - ya - recipa); + ya = temp; + temp = ya + recipb; + yb += -(temp - ya - recipb); + ya = temp; + + double result = ya + yb; + result *= 0.5; + return result; + } + + /** Compute the hyperbolic sine of a number. + * @param x number on which evaluation is done + * @return hyperbolic sine of x + */ + public static double sinh(double x) { + boolean negate = false; + if (x != x) { + return x; + } + + // sinh[z] = (exp(z) - exp(-z) / 2 + + // for values of z larger than about 20, + // exp(-z) can be ignored in comparison with exp(z) + + if (x > 20) { + if (x >= LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(x); + } + } else if (x < -20) { + if (x <= -LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(-0.5 * x); + return (-0.5 * t) * t; + } else { + return -0.5 * exp(-x); + } + } + + if (x == 0) { + return x; + } + + if (x < 0.0) { + x = -x; + negate = true; + } + + double result; + + if (x > 0.25) { + double hiPrec[] = new double[2]; + exp(x, 0.0, hiPrec); + + double ya = hiPrec[0] + hiPrec[1]; + double yb = -(ya - hiPrec[0] - hiPrec[1]); + + double temp = ya * HEX_40000000; + double yaa = ya + temp - temp; + double yab = ya - yaa; + + // recip = 1/y + double recip = 1.0/ya; + temp = recip * HEX_40000000; + double recipa = recip + temp - temp; + double recipb = recip - recipa; + + // Correct for rounding in division + recipb += (1.0 - yaa*recipa - yaa*recipb - yab*recipa - yab*recipb) * recip; + // Account for yb + recipb += -yb * recip * recip; + + recipa = -recipa; + recipb = -recipb; + + // y = y + 1/y + temp = ya + recipa; + yb += -(temp - ya - recipa); + ya = temp; + temp = ya + recipb; + yb += -(temp - ya - recipb); + ya = temp; + + result = ya + yb; + result *= 0.5; + } + else { + double hiPrec[] = new double[2]; + expm1(x, hiPrec); + + double ya = hiPrec[0] + hiPrec[1]; + double yb = -(ya - hiPrec[0] - hiPrec[1]); + + /* Compute expm1(-x) = -expm1(x) / (expm1(x) + 1) */ + double denom = 1.0 + ya; + double denomr = 1.0 / denom; + double denomb = -(denom - 1.0 - ya) + yb; + double ratio = ya * denomr; + double temp = ratio * HEX_40000000; + double ra = ratio + temp - temp; + double rb = ratio - ra; + + temp = denom * HEX_40000000; + double za = denom + temp - temp; + double zb = denom - za; + + rb += (ya - za*ra - za*rb - zb*ra - zb*rb) * denomr; + + // Adjust for yb + rb += yb*denomr; // numerator + rb += -ya * denomb * denomr * denomr; // denominator + + // y = y - 1/y + temp = ya + ra; + yb += -(temp - ya - ra); + ya = temp; + temp = ya + rb; + yb += -(temp - ya - rb); + ya = temp; + + result = ya + yb; + result *= 0.5; + } + + if (negate) { + result = -result; + } + + return result; + } + + /** Compute the hyperbolic tangent of a number. + * @param x number on which evaluation is done + * @return hyperbolic tangent of x + */ + public static double tanh(double x) { + boolean negate = false; + + if (x != x) { + return x; + } + + // tanh[z] = sinh[z] / cosh[z] + // = (exp(z) - exp(-z)) / (exp(z) + exp(-z)) + // = (exp(2x) - 1) / (exp(2x) + 1) + + // for magnitude > 20, sinh[z] == cosh[z] in double precision + + if (x > 20.0) { + return 1.0; + } + + if (x < -20) { + return -1.0; + } + + if (x == 0) { + return x; + } + + if (x < 0.0) { + x = -x; + negate = true; + } + + double result; + if (x >= 0.5) { + double hiPrec[] = new double[2]; + // tanh(x) = (exp(2x) - 1) / (exp(2x) + 1) + exp(x*2.0, 0.0, hiPrec); + + double ya = hiPrec[0] + hiPrec[1]; + double yb = -(ya - hiPrec[0] - hiPrec[1]); + + /* Numerator */ + double na = -1.0 + ya; + double nb = -(na + 1.0 - ya); + double temp = na + yb; + nb += -(temp - na - yb); + na = temp; + + /* Denominator */ + double da = 1.0 + ya; + double db = -(da - 1.0 - ya); + temp = da + yb; + db += -(temp - da - yb); + da = temp; + + temp = da * HEX_40000000; + double daa = da + temp - temp; + double dab = da - daa; + + // ratio = na/da + double ratio = na/da; + temp = ratio * HEX_40000000; + double ratioa = ratio + temp - temp; + double ratiob = ratio - ratioa; + + // Correct for rounding in division + ratiob += (na - daa*ratioa - daa*ratiob - dab*ratioa - dab*ratiob) / da; + + // Account for nb + ratiob += nb / da; + // Account for db + ratiob += -db * na / da / da; + + result = ratioa + ratiob; + } + else { + double hiPrec[] = new double[2]; + // tanh(x) = expm1(2x) / (expm1(2x) + 2) + expm1(x*2.0, hiPrec); + + double ya = hiPrec[0] + hiPrec[1]; + double yb = -(ya - hiPrec[0] - hiPrec[1]); + + /* Numerator */ + double na = ya; + double nb = yb; + + /* Denominator */ + double da = 2.0 + ya; + double db = -(da - 2.0 - ya); + double temp = da + yb; + db += -(temp - da - yb); + da = temp; + + temp = da * HEX_40000000; + double daa = da + temp - temp; + double dab = da - daa; + + // ratio = na/da + double ratio = na/da; + temp = ratio * HEX_40000000; + double ratioa = ratio + temp - temp; + double ratiob = ratio - ratioa; + + // Correct for rounding in division + ratiob += (na - daa*ratioa - daa*ratiob - dab*ratioa - dab*ratiob) / da; + + // Account for nb + ratiob += nb / da; + // Account for db + ratiob += -db * na / da / da; + + result = ratioa + ratiob; + } + + if (negate) { + result = -result; + } + + return result; + } + + /** Compute the inverse hyperbolic cosine of a number. + * @param a number on which evaluation is done + * @return inverse hyperbolic cosine of a + */ + public static double acosh(final double a) { + return FastMath.log(a + FastMath.sqrt(a * a - 1)); + } + + /** Compute the inverse hyperbolic sine of a number. + * @param a number on which evaluation is done + * @return inverse hyperbolic sine of a + */ + public static double asinh(double a) { + boolean negative = false; + if (a < 0) { + negative = true; + a = -a; + } + + double absAsinh; + if (a > 0.167) { + absAsinh = FastMath.log(FastMath.sqrt(a * a + 1) + a); + } else { + final double a2 = a * a; + if (a > 0.097) { + absAsinh = a * (1 - a2 * (F_1_3 - a2 * (F_1_5 - a2 * (F_1_7 - a2 * (F_1_9 - a2 * (F_1_11 - a2 * (F_1_13 - a2 * (F_1_15 - a2 * F_1_17 * F_15_16) * F_13_14) * F_11_12) * F_9_10) * F_7_8) * F_5_6) * F_3_4) * F_1_2); + } else if (a > 0.036) { + absAsinh = a * (1 - a2 * (F_1_3 - a2 * (F_1_5 - a2 * (F_1_7 - a2 * (F_1_9 - a2 * (F_1_11 - a2 * F_1_13 * F_11_12) * F_9_10) * F_7_8) * F_5_6) * F_3_4) * F_1_2); + } else if (a > 0.0036) { + absAsinh = a * (1 - a2 * (F_1_3 - a2 * (F_1_5 - a2 * (F_1_7 - a2 * F_1_9 * F_7_8) * F_5_6) * F_3_4) * F_1_2); + } else { + absAsinh = a * (1 - a2 * (F_1_3 - a2 * F_1_5 * F_3_4) * F_1_2); + } + } + + return negative ? -absAsinh : absAsinh; + } + + /** Compute the inverse hyperbolic tangent of a number. + * @param a number on which evaluation is done + * @return inverse hyperbolic tangent of a + */ + public static double atanh(double a) { + boolean negative = false; + if (a < 0) { + negative = true; + a = -a; + } + + double absAtanh; + if (a > 0.15) { + absAtanh = 0.5 * FastMath.log((1 + a) / (1 - a)); + } else { + final double a2 = a * a; + if (a > 0.087) { + absAtanh = a * (1 + a2 * (F_1_3 + a2 * (F_1_5 + a2 * (F_1_7 + a2 * (F_1_9 + a2 * (F_1_11 + a2 * (F_1_13 + a2 * (F_1_15 + a2 * F_1_17)))))))); + } else if (a > 0.031) { + absAtanh = a * (1 + a2 * (F_1_3 + a2 * (F_1_5 + a2 * (F_1_7 + a2 * (F_1_9 + a2 * (F_1_11 + a2 * F_1_13)))))); + } else if (a > 0.003) { + absAtanh = a * (1 + a2 * (F_1_3 + a2 * (F_1_5 + a2 * (F_1_7 + a2 * F_1_9)))); + } else { + absAtanh = a * (1 + a2 * (F_1_3 + a2 * F_1_5)); + } + } + + return negative ? -absAtanh : absAtanh; + } + + /** Compute the signum of a number. + * The signum is -1 for negative numbers, +1 for positive numbers and 0 otherwise + * @param a number on which evaluation is done + * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a + */ + public static double signum(final double a) { + return (a < 0.0) ? -1.0 : ((a > 0.0) ? 1.0 : a); // return +0.0/-0.0/NaN depending on a + } + + /** Compute the signum of a number. + * The signum is -1 for negative numbers, +1 for positive numbers and 0 otherwise + * @param a number on which evaluation is done + * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a + */ + public static float signum(final float a) { + return (a < 0.0f) ? -1.0f : ((a > 0.0f) ? 1.0f : a); // return +0.0/-0.0/NaN depending on a + } + + /** Compute next number towards positive infinity. + * @param a number to which neighbor should be computed + * @return neighbor of a towards positive infinity + */ + public static double nextUp(final double a) { + return nextAfter(a, Double.POSITIVE_INFINITY); + } + + /** Compute next number towards positive infinity. + * @param a number to which neighbor should be computed + * @return neighbor of a towards positive infinity + */ + public static float nextUp(final float a) { + return nextAfter(a, Float.POSITIVE_INFINITY); + } + + /** Compute next number towards negative infinity. + * @param a number to which neighbor should be computed + * @return neighbor of a towards negative infinity + * @since 3.4 + */ + public static double nextDown(final double a) { + return nextAfter(a, Double.NEGATIVE_INFINITY); + } + + /** Compute next number towards negative infinity. + * @param a number to which neighbor should be computed + * @return neighbor of a towards negative infinity + * @since 3.4 + */ + public static float nextDown(final float a) { + return nextAfter(a, Float.NEGATIVE_INFINITY); + } + + /** Returns a pseudo-random number between 0.0 and 1.0. + *

        Note: 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 ULP 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 ex + */ + public static double exp(double x) { + return exp(x, 0.0, null); + } + + /** + * Internal helper method for exponential function. + * @param x original argument of the exponential function + * @param extra extra bits of precision on input (To Be Confirmed) + * @param hiPrec extra bits of precision on output (To Be Confirmed) + * @return exp(x) + */ + private static double exp(double x, double extra, double[] hiPrec) { + double intPartA; + double intPartB; + int intVal = (int) x; + + /* Lookup exp(floor(x)). + * intPartA will have the upper 22 bits, intPartB will have the lower + * 52 bits. + */ + if (x < 0.0) { + + // We don't check against intVal here as conversion of large negative double values + // may be affected by a JIT bug. Subsequent comparisons can safely use intVal + if (x < -746d) { + if (hiPrec != null) { + hiPrec[0] = 0.0; + hiPrec[1] = 0.0; + } + return 0.0; + } + + if (intVal < -709) { + /* This will produce a subnormal output */ + final double result = exp(x+40.19140625, extra, hiPrec) / 285040095144011776.0; + if (hiPrec != null) { + hiPrec[0] /= 285040095144011776.0; + hiPrec[1] /= 285040095144011776.0; + } + return result; + } + + if (intVal == -709) { + /* exp(1.494140625) is nearly a machine number... */ + final double result = exp(x+1.494140625, extra, hiPrec) / 4.455505956692756620; + if (hiPrec != null) { + hiPrec[0] /= 4.455505956692756620; + hiPrec[1] /= 4.455505956692756620; + } + return result; + } + + intVal--; + + } else { + if (intVal > 709) { + if (hiPrec != null) { + hiPrec[0] = Double.POSITIVE_INFINITY; + hiPrec[1] = 0.0; + } + return Double.POSITIVE_INFINITY; + } + + } + + intPartA = ExpIntTable.EXP_INT_TABLE_A[EXP_INT_TABLE_MAX_INDEX+intVal]; + intPartB = ExpIntTable.EXP_INT_TABLE_B[EXP_INT_TABLE_MAX_INDEX+intVal]; + + /* Get the fractional part of x, find the greatest multiple of 2^-10 less than + * x and look up the exp function of it. + * fracPartA will have the upper 22 bits, fracPartB the lower 52 bits. + */ + final int intFrac = (int) ((x - intVal) * 1024.0); + final double fracPartA = ExpFracTable.EXP_FRAC_TABLE_A[intFrac]; + final double fracPartB = ExpFracTable.EXP_FRAC_TABLE_B[intFrac]; + + /* epsilon is the difference in x from the nearest multiple of 2^-10. It + * has a value in the range 0 <= epsilon < 2^-10. + * Do the subtraction from x as the last step to avoid possible loss of precision. + */ + final double epsilon = x - (intVal + intFrac / 1024.0); + + /* Compute z = exp(epsilon) - 1.0 via a minimax polynomial. z has + full double precision (52 bits). Since z < 2^-10, we will have + 62 bits of precision when combined with the constant 1. This will be + used in the last addition below to get proper rounding. */ + + /* Remez generated polynomial. Converges on the interval [0, 2^-10], error + is less than 0.5 ULP */ + double z = 0.04168701738764507; + z = z * epsilon + 0.1666666505023083; + z = z * epsilon + 0.5000000000042687; + z = z * epsilon + 1.0; + z = z * epsilon + -3.940510424527919E-20; + + /* Compute (intPartA+intPartB) * (fracPartA+fracPartB) by binomial + expansion. + tempA is exact since intPartA and intPartB only have 22 bits each. + tempB will have 52 bits of precision. + */ + double tempA = intPartA * fracPartA; + double tempB = intPartA * fracPartB + intPartB * fracPartA + intPartB * fracPartB; + + /* Compute the result. (1+z)(tempA+tempB). Order of operations is + important. For accuracy add by increasing size. tempA is exact and + much larger than the others. If there are extra bits specified from the + pow() function, use them. */ + final double tempC = tempB + tempA; + + // If tempC is positive infinite, the evaluation below could result in NaN, + // because z could be negative at the same time. + if (tempC == Double.POSITIVE_INFINITY) { + return Double.POSITIVE_INFINITY; + } + + final double result; + if (extra != 0.0) { + result = tempC*extra*z + tempC*extra + tempC*z + tempB + tempA; + } else { + result = tempC*z + tempB + tempA; + } + + if (hiPrec != null) { + // If requesting high precision + hiPrec[0] = tempA; + hiPrec[1] = tempC*extra*z + tempC*extra + tempC*z + tempB; + } + + return result; + } + + /** Compute exp(x) - 1 + * @param x number to compute shifted exponential + * @return exp(x) - 1 + */ + public static double expm1(double x) { + return expm1(x, null); + } + + /** Internal helper method for expm1 + * @param x number to compute shifted exponential + * @param hiPrecOut receive high precision result for -1.0 < x < 1.0 + * @return exp(x) - 1 + */ + private static double expm1(double x, double hiPrecOut[]) { + if (x != x || x == 0.0) { // NaN or zero + return x; + } + + if (x <= -1.0 || x >= 1.0) { + // If not between +/- 1.0 + //return exp(x) - 1.0; + double hiPrec[] = new double[2]; + exp(x, 0.0, hiPrec); + if (x > 0.0) { + return -1.0 + hiPrec[0] + hiPrec[1]; + } else { + final double ra = -1.0 + hiPrec[0]; + double rb = -(ra + 1.0 - hiPrec[0]); + rb += hiPrec[1]; + return ra + rb; + } + } + + double baseA; + double baseB; + double epsilon; + boolean negative = false; + + if (x < 0.0) { + x = -x; + negative = true; + } + + { + int intFrac = (int) (x * 1024.0); + double tempA = ExpFracTable.EXP_FRAC_TABLE_A[intFrac] - 1.0; + double tempB = ExpFracTable.EXP_FRAC_TABLE_B[intFrac]; + + double temp = tempA + tempB; + tempB = -(temp - tempA - tempB); + tempA = temp; + + temp = tempA * HEX_40000000; + baseA = tempA + temp - temp; + baseB = tempB + (tempA - baseA); + + epsilon = x - intFrac/1024.0; + } + + + /* Compute expm1(epsilon) */ + double zb = 0.008336750013465571; + zb = zb * epsilon + 0.041666663879186654; + zb = zb * epsilon + 0.16666666666745392; + zb = zb * epsilon + 0.49999999999999994; + zb *= epsilon; + zb *= epsilon; + + double za = epsilon; + double temp = za + zb; + zb = -(temp - za - zb); + za = temp; + + temp = za * HEX_40000000; + temp = za + temp - temp; + zb += za - temp; + za = temp; + + /* Combine the parts. expm1(a+b) = expm1(a) + expm1(b) + expm1(a)*expm1(b) */ + double ya = za * baseA; + //double yb = za*baseB + zb*baseA + zb*baseB; + temp = ya + za * baseB; + double yb = -(temp - ya - za * baseB); + ya = temp; + + temp = ya + zb * baseA; + yb += -(temp - ya - zb * baseA); + ya = temp; + + temp = ya + zb * baseB; + yb += -(temp - ya - zb*baseB); + ya = temp; + + //ya = ya + za + baseA; + //yb = yb + zb + baseB; + temp = ya + baseA; + yb += -(temp - baseA - ya); + ya = temp; + + temp = ya + za; + //yb += (ya > za) ? -(temp - ya - za) : -(temp - za - ya); + yb += -(temp - ya - za); + ya = temp; + + temp = ya + baseB; + //yb += (ya > baseB) ? -(temp - ya - baseB) : -(temp - baseB - ya); + yb += -(temp - ya - baseB); + ya = temp; + + temp = ya + zb; + //yb += (ya > zb) ? -(temp - ya - zb) : -(temp - zb - ya); + yb += -(temp - ya - zb); + ya = temp; + + if (negative) { + /* Compute expm1(-x) = -expm1(x) / (expm1(x) + 1) */ + double denom = 1.0 + ya; + double denomr = 1.0 / denom; + double denomb = -(denom - 1.0 - ya) + yb; + double ratio = ya * denomr; + temp = ratio * HEX_40000000; + final double ra = ratio + temp - temp; + double rb = ratio - ra; + + temp = denom * HEX_40000000; + za = denom + temp - temp; + zb = denom - za; + + rb += (ya - za * ra - za * rb - zb * ra - zb * rb) * denomr; + + // f(x) = x/1+x + // Compute f'(x) + // Product rule: d(uv) = du*v + u*dv + // Chain rule: d(f(g(x)) = f'(g(x))*f(g'(x)) + // d(1/x) = -1/(x*x) + // d(1/1+x) = -1/( (1+x)^2) * 1 = -1/((1+x)*(1+x)) + // d(x/1+x) = -x/((1+x)(1+x)) + 1/1+x = 1 / ((1+x)(1+x)) + + // Adjust for yb + rb += yb * denomr; // numerator + rb += -ya * denomb * denomr * denomr; // denominator + + // negate + ya = -ra; + yb = -rb; + } + + if (hiPrecOut != null) { + hiPrecOut[0] = ya; + hiPrecOut[1] = yb; + } + + return ya + yb; + } + + /** + * Natural logarithm. + * + * @param x a double + * @return log(x) + */ + public static double log(final double x) { + return log(x, null); + } + + /** + * Internal helper method for natural logarithm function. + * @param x original argument of the natural logarithm function + * @param hiPrec extra bits of precision on output (To Be Confirmed) + * @return log(x) + */ + private static double log(final double x, final double[] hiPrec) { + if (x==0) { // Handle special case of +0/-0 + return Double.NEGATIVE_INFINITY; + } + long bits = Double.doubleToRawLongBits(x); + + /* Handle special cases of negative input, and NaN */ + if (((bits & 0x8000000000000000L) != 0 || x != x) && x != 0.0) { + if (hiPrec != null) { + hiPrec[0] = Double.NaN; + } + + return Double.NaN; + } + + /* Handle special cases of Positive infinity. */ + if (x == Double.POSITIVE_INFINITY) { + if (hiPrec != null) { + hiPrec[0] = Double.POSITIVE_INFINITY; + } + + return Double.POSITIVE_INFINITY; + } + + /* Extract the exponent */ + int exp = (int)(bits >> 52)-1023; + + if ((bits & 0x7ff0000000000000L) == 0) { + // Subnormal! + if (x == 0) { + // Zero + if (hiPrec != null) { + hiPrec[0] = Double.NEGATIVE_INFINITY; + } + + return Double.NEGATIVE_INFINITY; + } + + /* Normalize the subnormal number. */ + bits <<= 1; + while ( (bits & 0x0010000000000000L) == 0) { + --exp; + bits <<= 1; + } + } + + + if ((exp == -1 || exp == 0) && x < 1.01 && x > 0.99 && hiPrec == null) { + /* The normal method doesn't work well in the range [0.99, 1.01], so call do a straight + polynomial expansion in higer precision. */ + + /* Compute x - 1.0 and split it */ + double xa = x - 1.0; + double xb = xa - x + 1.0; + double tmp = xa * HEX_40000000; + double aa = xa + tmp - tmp; + double ab = xa - aa; + xa = aa; + xb = ab; + + final double[] lnCoef_last = LN_QUICK_COEF[LN_QUICK_COEF.length - 1]; + double ya = lnCoef_last[0]; + double yb = lnCoef_last[1]; + + for (int i = LN_QUICK_COEF.length - 2; i >= 0; i--) { + /* Multiply a = y * x */ + aa = ya * xa; + ab = ya * xb + yb * xa + yb * xb; + /* split, so now y = a */ + tmp = aa * HEX_40000000; + ya = aa + tmp - tmp; + yb = aa - ya + ab; + + /* Add a = y + lnQuickCoef */ + final double[] lnCoef_i = LN_QUICK_COEF[i]; + aa = ya + lnCoef_i[0]; + ab = yb + lnCoef_i[1]; + /* Split y = a */ + tmp = aa * HEX_40000000; + ya = aa + tmp - tmp; + yb = aa - ya + ab; + } + + /* Multiply a = y * x */ + aa = ya * xa; + ab = ya * xb + yb * xa + yb * xb; + /* split, so now y = a */ + tmp = aa * HEX_40000000; + ya = aa + tmp - tmp; + yb = aa - ya + ab; + + return ya + yb; + } + + // lnm is a log of a number in the range of 1.0 - 2.0, so 0 <= lnm < ln(2) + final double[] lnm = lnMant.LN_MANT[(int)((bits & 0x000ffc0000000000L) >> 42)]; + + /* + double epsilon = x / Double.longBitsToDouble(bits & 0xfffffc0000000000L); + + epsilon -= 1.0; + */ + + // y is the most significant 10 bits of the mantissa + //double y = Double.longBitsToDouble(bits & 0xfffffc0000000000L); + //double epsilon = (x - y) / y; + final double epsilon = (bits & 0x3ffffffffffL) / (TWO_POWER_52 + (bits & 0x000ffc0000000000L)); + + double lnza = 0.0; + double lnzb = 0.0; + + if (hiPrec != null) { + /* split epsilon -> x */ + double tmp = epsilon * HEX_40000000; + double aa = epsilon + tmp - tmp; + double ab = epsilon - aa; + double xa = aa; + double xb = ab; + + /* Need a more accurate epsilon, so adjust the division. */ + final double numer = bits & 0x3ffffffffffL; + final double denom = TWO_POWER_52 + (bits & 0x000ffc0000000000L); + aa = numer - xa*denom - xb * denom; + xb += aa / denom; + + /* Remez polynomial evaluation */ + final double[] lnCoef_last = LN_HI_PREC_COEF[LN_HI_PREC_COEF.length-1]; + double ya = lnCoef_last[0]; + double yb = lnCoef_last[1]; + + for (int i = LN_HI_PREC_COEF.length - 2; i >= 0; i--) { + /* Multiply a = y * x */ + aa = ya * xa; + ab = ya * xb + yb * xa + yb * xb; + /* split, so now y = a */ + tmp = aa * HEX_40000000; + ya = aa + tmp - tmp; + yb = aa - ya + ab; + + /* Add a = y + lnHiPrecCoef */ + final double[] lnCoef_i = LN_HI_PREC_COEF[i]; + aa = ya + lnCoef_i[0]; + ab = yb + lnCoef_i[1]; + /* Split y = a */ + tmp = aa * HEX_40000000; + ya = aa + tmp - tmp; + yb = aa - ya + ab; + } + + /* Multiply a = y * x */ + aa = ya * xa; + ab = ya * xb + yb * xa + yb * xb; + + /* split, so now lnz = a */ + /* + tmp = aa * 1073741824.0; + lnza = aa + tmp - tmp; + lnzb = aa - lnza + ab; + */ + lnza = aa + ab; + lnzb = -(lnza - aa - ab); + } else { + /* High precision not required. Eval Remez polynomial + using standard double precision */ + lnza = -0.16624882440418567; + lnza = lnza * epsilon + 0.19999954120254515; + lnza = lnza * epsilon + -0.2499999997677497; + lnza = lnza * epsilon + 0.3333333333332802; + lnza = lnza * epsilon + -0.5; + lnza = lnza * epsilon + 1.0; + lnza *= epsilon; + } + + /* Relative sizes: + * lnzb [0, 2.33E-10] + * lnm[1] [0, 1.17E-7] + * ln2B*exp [0, 1.12E-4] + * lnza [0, 9.7E-4] + * lnm[0] [0, 0.692] + * ln2A*exp [0, 709] + */ + + /* Compute the following sum: + * lnzb + lnm[1] + ln2B*exp + lnza + lnm[0] + ln2A*exp; + */ + + //return lnzb + lnm[1] + ln2B*exp + lnza + lnm[0] + ln2A*exp; + double a = LN_2_A*exp; + double b = 0.0; + double c = a+lnm[0]; + double d = -(c-a-lnm[0]); + a = c; + b += d; + + c = a + lnza; + d = -(c - a - lnza); + a = c; + b += d; + + c = a + LN_2_B*exp; + d = -(c - a - LN_2_B*exp); + a = c; + b += d; + + c = a + lnm[1]; + d = -(c - a - lnm[1]); + a = c; + b += d; + + c = a + lnzb; + d = -(c - a - lnzb); + a = c; + b += d; + + if (hiPrec != null) { + hiPrec[0] = a; + hiPrec[1] = b; + } + + return a + b; + } + + /** + * Computes log(1 + x). + * + * @param x Number. + * @return {@code log(1 + x)}. + */ + public static double log1p(final double x) { + if (x == -1) { + return Double.NEGATIVE_INFINITY; + } + + if (x == Double.POSITIVE_INFINITY) { + return Double.POSITIVE_INFINITY; + } + + if (x > 1e-6 || + x < -1e-6) { + final double xpa = 1 + x; + final double xpb = -(xpa - 1 - x); + + final double[] hiPrec = new double[2]; + final double lores = log(xpa, hiPrec); + if (Double.isInfinite(lores)) { // Don't allow this to be converted to NaN + return lores; + } + + // Do a taylor series expansion around xpa: + // f(x+y) = f(x) + f'(x) y + f''(x)/2 y^2 + final double fx1 = xpb / xpa; + final double epsilon = 0.5 * fx1 + 1; + return epsilon * fx1 + hiPrec[1] + hiPrec[0]; + } else { + // Value is small |x| < 1e6, do a Taylor series centered on 1. + final double y = (x * F_1_3 - F_1_2) * x + 1; + return y * x; + } + } + + /** Compute the base 10 logarithm. + * @param x a number + * @return log10(x) + */ + public static double log10(final double x) { + final double hiPrec[] = new double[2]; + + final double lores = log(x, hiPrec); + if (Double.isInfinite(lores)){ // don't allow this to be converted to NaN + return lores; + } + + final double tmp = hiPrec[0] * HEX_40000000; + final double lna = hiPrec[0] + tmp - tmp; + final double lnb = hiPrec[0] - lna + hiPrec[1]; + + final double rln10a = 0.4342944622039795; + final double rln10b = 1.9699272335463627E-8; + + return rln10b * lnb + rln10b * lna + rln10a * lnb + rln10a * lna; + } + + /** + * Computes the + * logarithm in a given base. + * + * Returns {@code NaN} if either argument is negative. + * If {@code base} is 0 and {@code x} is positive, 0 is returned. + * If {@code base} is positive and {@code x} is 0, + * {@code Double.NEGATIVE_INFINITY} is returned. + * If both arguments are 0, the result is {@code NaN}. + * + * @param base Base of the logarithm, must be greater than 0. + * @param x Argument, must be greater than 0. + * @return the value of the logarithm, i.e. the number {@code y} such that + * basey = x. + * @since 1.2 (previously in {@code MathUtils}, moved as of version 3.0) + */ + public static double log(double base, double x) { + return log(x) / log(base); + } + + /** + * Power function. Compute x^y. + * + * @param x a double + * @param y a double + * @return double + */ + public static double pow(final double x, final double y) { + + if (y == 0) { + // y = -0 or y = +0 + return 1.0; + } else { + + final long yBits = Double.doubleToRawLongBits(y); + final int yRawExp = (int) ((yBits & MASK_DOUBLE_EXPONENT) >> 52); + final long yRawMantissa = yBits & MASK_DOUBLE_MANTISSA; + final long xBits = Double.doubleToRawLongBits(x); + final int xRawExp = (int) ((xBits & MASK_DOUBLE_EXPONENT) >> 52); + final long xRawMantissa = xBits & MASK_DOUBLE_MANTISSA; + + if (yRawExp > 1085) { + // y is either a very large integral value that does not fit in a long or it is a special number + + if ((yRawExp == 2047 && yRawMantissa != 0) || + (xRawExp == 2047 && xRawMantissa != 0)) { + // NaN + return Double.NaN; + } else if (xRawExp == 1023 && xRawMantissa == 0) { + // x = -1.0 or x = +1.0 + if (yRawExp == 2047) { + // y is infinite + return Double.NaN; + } else { + // y is a large even integer + return 1.0; + } + } else { + // the absolute value of x is either greater or smaller than 1.0 + + // if yRawExp == 2047 and mantissa is 0, y = -infinity or y = +infinity + // if 1085 < yRawExp < 2047, y is simply a large number, however, due to limited + // accuracy, at this magnitude it behaves just like infinity with regards to x + if ((y > 0) ^ (xRawExp < 1023)) { + // either y = +infinity (or large engouh) and abs(x) > 1.0 + // or y = -infinity (or large engouh) and abs(x) < 1.0 + return Double.POSITIVE_INFINITY; + } else { + // either y = +infinity (or large engouh) and abs(x) < 1.0 + // or y = -infinity (or large engouh) and abs(x) > 1.0 + return +0.0; + } + } + + } else { + // y is a regular non-zero number + + if (yRawExp >= 1023) { + // y may be an integral value, which should be handled specifically + final long yFullMantissa = IMPLICIT_HIGH_BIT | yRawMantissa; + if (yRawExp < 1075) { + // normal number with negative shift that may have a fractional part + final long integralMask = (-1L) << (1075 - yRawExp); + if ((yFullMantissa & integralMask) == yFullMantissa) { + // all fractional bits are 0, the number is really integral + final long l = yFullMantissa >> (1075 - yRawExp); + return FastMath.pow(x, (y < 0) ? -l : l); + } + } else { + // normal number with positive shift, always an integral value + // we know it fits in a primitive long because yRawExp > 1085 has been handled above + final long l = yFullMantissa << (yRawExp - 1075); + return FastMath.pow(x, (y < 0) ? -l : l); + } + } + + // y is a non-integral value + + if (x == 0) { + // x = -0 or x = +0 + // the integer powers have already been handled above + return y < 0 ? Double.POSITIVE_INFINITY : +0.0; + } else if (xRawExp == 2047) { + if (xRawMantissa == 0) { + // x = -infinity or x = +infinity + return (y < 0) ? +0.0 : Double.POSITIVE_INFINITY; + } else { + // NaN + return Double.NaN; + } + } else if (x < 0) { + // the integer powers have already been handled above + return Double.NaN; + } else { + + // this is the general case, for regular fractional numbers x and y + + // Split y into ya and yb such that y = ya+yb + final double tmp = y * HEX_40000000; + final double ya = (y + tmp) - tmp; + final double yb = y - ya; + + /* Compute ln(x) */ + final double lns[] = new double[2]; + final double lores = log(x, lns); + if (Double.isInfinite(lores)) { // don't allow this to be converted to NaN + return lores; + } + + double lna = lns[0]; + double lnb = lns[1]; + + /* resplit lns */ + final double tmp1 = lna * HEX_40000000; + final double tmp2 = (lna + tmp1) - tmp1; + lnb += lna - tmp2; + lna = tmp2; + + // y*ln(x) = (aa+ab) + final double aa = lna * ya; + final double ab = lna * yb + lnb * ya + lnb * yb; + + lna = aa+ab; + lnb = -(lna - aa - ab); + + double z = 1.0 / 120.0; + z = z * lnb + (1.0 / 24.0); + z = z * lnb + (1.0 / 6.0); + z = z * lnb + 0.5; + z = z * lnb + 1.0; + z *= lnb; + + final double result = exp(lna, z, null); + //result = result + result * z; + return result; + + } + } + + } + + } + + /** + * Raise a double to an int power. + * + * @param d Number to raise. + * @param e Exponent. + * @return de + * @since 3.1 + */ + public static double pow(double d, int e) { + return pow(d, (long) e); + } + + /** + * Raise a double to a long power. + * + * @param d Number to raise. + * @param e Exponent. + * @return de + * @since 3.6 + */ + public static double pow(double d, long e) { + if (e == 0) { + return 1.0; + } else if (e > 0) { + return new Split(d).pow(e).full; + } else { + return new Split(d).reciprocal().pow(-e).full; + } + } + + /** Class operator on double numbers split into one 26 bits number and one 27 bits number. */ + private static class Split { + + /** Split version of NaN. */ + public static final Split NAN = new Split(Double.NaN, 0); + + /** Split version of positive infinity. */ + public static final Split POSITIVE_INFINITY = new Split(Double.POSITIVE_INFINITY, 0); + + /** Split version of negative infinity. */ + public static final Split NEGATIVE_INFINITY = new Split(Double.NEGATIVE_INFINITY, 0); + + /** Full number. */ + private final double full; + + /** High order bits. */ + private final double high; + + /** Low order bits. */ + private final double low; + + /** Simple constructor. + * @param x number to split + */ + Split(final double x) { + full = x; + high = Double.longBitsToDouble(Double.doubleToRawLongBits(x) & ((-1L) << 27)); + low = x - high; + } + + /** Simple constructor. + * @param high high order bits + * @param low low order bits + */ + Split(final double high, final double low) { + this(high == 0.0 ? (low == 0.0 && Double.doubleToRawLongBits(high) == Long.MIN_VALUE /* negative zero */ ? -0.0 : low) : high + low, high, low); + } + + /** Simple constructor. + * @param full full number + * @param high high order bits + * @param low low order bits + */ + Split(final double full, final double high, final double low) { + this.full = full; + this.high = high; + this.low = low; + } + + /** Multiply the instance by another one. + * @param b other instance to multiply by + * @return product + */ + public Split multiply(final Split b) { + // beware the following expressions must NOT be simplified, they rely on floating point arithmetic properties + final Split mulBasic = new Split(full * b.full); + final double mulError = low * b.low - (((mulBasic.full - high * b.high) - low * b.high) - high * b.low); + return new Split(mulBasic.high, mulBasic.low + mulError); + } + + /** Compute the reciprocal of the instance. + * @return reciprocal of the instance + */ + public Split reciprocal() { + + final double approximateInv = 1.0 / full; + final Split splitInv = new Split(approximateInv); + + // if 1.0/d were computed perfectly, remultiplying it by d should give 1.0 + // we want to estimate the error so we can fix the low order bits of approximateInvLow + // beware the following expressions must NOT be simplified, they rely on floating point arithmetic properties + final Split product = multiply(splitInv); + final double error = (product.high - 1) + product.low; + + // better accuracy estimate of reciprocal + return Double.isNaN(error) ? splitInv : new Split(splitInv.high, splitInv.low - error / full); + + } + + /** Computes this^e. + * @param e exponent (beware, here it MUST be > 0; the only exclusion is Long.MIN_VALUE) + * @return d^e, split in high and low bits + * @since 3.6 + */ + private Split pow(final long e) { + + // prepare result + Split result = new Split(1); + + // d^(2p) + Split d2p = new Split(full, high, low); + + for (long p = e; p != 0; p >>>= 1) { + + if ((p & 0x1) != 0) { + // accurate multiplication result = result * d^(2p) using Veltkamp TwoProduct algorithm + result = result.multiply(d2p); + } + + // accurate squaring d^(2(p+1)) = d^(2p) * d^(2p) using Veltkamp TwoProduct algorithm + d2p = d2p.multiply(d2p); + + } + + if (Double.isNaN(result.full)) { + if (Double.isNaN(full)) { + return Split.NAN; + } else { + // some intermediate numbers exceeded capacity, + // and the low order bits became NaN (because infinity - infinity = NaN) + if (FastMath.abs(full) < 1) { + return new Split(FastMath.copySign(0.0, full), 0.0); + } else if (full < 0 && (e & 0x1) == 1) { + return Split.NEGATIVE_INFINITY; + } else { + return Split.POSITIVE_INFINITY; + } + } + } else { + return result; + } + + } + + } + + /** + * Computes sin(x) - x, where |x| < 1/16. + * Use a Remez polynomial approximation. + * @param x a number smaller than 1/16 + * @return sin(x) - x + */ + private static double polySine(final double x) + { + double x2 = x*x; + + double p = 2.7553817452272217E-6; + p = p * x2 + -1.9841269659586505E-4; + p = p * x2 + 0.008333333333329196; + p = p * x2 + -0.16666666666666666; + //p *= x2; + //p *= x; + p = p * x2 * x; + + return p; + } + + /** + * Computes cos(x) - 1, where |x| < 1/16. + * Use a Remez polynomial approximation. + * @param x a number smaller than 1/16 + * @return cos(x) - 1 + */ + private static double polyCosine(double x) { + double x2 = x*x; + + double p = 2.479773539153719E-5; + p = p * x2 + -0.0013888888689039883; + p = p * x2 + 0.041666666666621166; + p = p * x2 + -0.49999999999999994; + p *= x2; + + return p; + } + + /** + * Compute sine over the first quadrant (0 < x < pi/2). + * Use combination of table lookup and rational polynomial expansion. + * @param xa number from which sine is requested + * @param xb extra bits for x (may be 0.0) + * @return sin(xa + xb) + */ + private static double sinQ(double xa, double xb) { + int idx = (int) ((xa * 8.0) + 0.5); + final double epsilon = xa - EIGHTHS[idx]; //idx*0.125; + + // Table lookups + final double sintA = SINE_TABLE_A[idx]; + final double sintB = SINE_TABLE_B[idx]; + final double costA = COSINE_TABLE_A[idx]; + final double costB = COSINE_TABLE_B[idx]; + + // Polynomial eval of sin(epsilon), cos(epsilon) + double sinEpsA = epsilon; + double sinEpsB = polySine(epsilon); + final double cosEpsA = 1.0; + final double cosEpsB = polyCosine(epsilon); + + // Split epsilon xa + xb = x + final double temp = sinEpsA * HEX_40000000; + double temp2 = (sinEpsA + temp) - temp; + sinEpsB += sinEpsA - temp2; + sinEpsA = temp2; + + /* Compute sin(x) by angle addition formula */ + double result; + + /* Compute the following sum: + * + * result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB + + * sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB; + * + * Ranges of elements + * + * xxxtA 0 PI/2 + * xxxtB -1.5e-9 1.5e-9 + * sinEpsA -0.0625 0.0625 + * sinEpsB -6e-11 6e-11 + * cosEpsA 1.0 + * cosEpsB 0 -0.0625 + * + */ + + //result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB + + // sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB; + + //result = sintA + sintA*cosEpsB + sintB + sintB * cosEpsB; + //result += costA*sinEpsA + costA*sinEpsB + costB*sinEpsA + costB * sinEpsB; + double a = 0; + double b = 0; + + double t = sintA; + double c = a + t; + double d = -(c - a - t); + a = c; + b += d; + + t = costA * sinEpsA; + c = a + t; + d = -(c - a - t); + a = c; + b += d; + + b = b + sintA * cosEpsB + costA * sinEpsB; + /* + t = sintA*cosEpsB; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + + t = costA*sinEpsB; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + */ + + b = b + sintB + costB * sinEpsA + sintB * cosEpsB + costB * sinEpsB; + /* + t = sintB; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + + t = costB*sinEpsA; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + + t = sintB*cosEpsB; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + + t = costB*sinEpsB; + c = a + t; + d = -(c - a - t); + a = c; + b = b + d; + */ + + if (xb != 0.0) { + t = ((costA + costB) * (cosEpsA + cosEpsB) - + (sintA + sintB) * (sinEpsA + sinEpsB)) * xb; // approximate cosine*xb + c = a + t; + d = -(c - a - t); + a = c; + b += d; + } + + result = a + b; + + return result; + } + + /** + * Compute cosine in the first quadrant by subtracting input from PI/2 and + * then calling sinQ. This is more accurate as the input approaches PI/2. + * @param xa number from which cosine is requested + * @param xb extra bits for x (may be 0.0) + * @return cos(xa + xb) + */ + private static double cosQ(double xa, double xb) { + final double pi2a = 1.5707963267948966; + final double pi2b = 6.123233995736766E-17; + + final double a = pi2a - xa; + double b = -(a - pi2a + xa); + b += pi2b - xb; + + return sinQ(a, b); + } + + /** + * Compute tangent (or cotangent) over the first quadrant. 0 < x < pi/2 + * Use combination of table lookup and rational polynomial expansion. + * @param xa number from which sine is requested + * @param xb extra bits for x (may be 0.0) + * @param cotanFlag if true, compute the cotangent instead of the tangent + * @return tan(xa+xb) (or cotangent, depending on cotanFlag) + */ + private static double tanQ(double xa, double xb, boolean cotanFlag) { + + int idx = (int) ((xa * 8.0) + 0.5); + final double epsilon = xa - EIGHTHS[idx]; //idx*0.125; + + // Table lookups + final double sintA = SINE_TABLE_A[idx]; + final double sintB = SINE_TABLE_B[idx]; + final double costA = COSINE_TABLE_A[idx]; + final double costB = COSINE_TABLE_B[idx]; + + // Polynomial eval of sin(epsilon), cos(epsilon) + double sinEpsA = epsilon; + double sinEpsB = polySine(epsilon); + final double cosEpsA = 1.0; + final double cosEpsB = polyCosine(epsilon); + + // Split epsilon xa + xb = x + double temp = sinEpsA * HEX_40000000; + double temp2 = (sinEpsA + temp) - temp; + sinEpsB += sinEpsA - temp2; + sinEpsA = temp2; + + /* Compute sin(x) by angle addition formula */ + + /* Compute the following sum: + * + * result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB + + * sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB; + * + * Ranges of elements + * + * xxxtA 0 PI/2 + * xxxtB -1.5e-9 1.5e-9 + * sinEpsA -0.0625 0.0625 + * sinEpsB -6e-11 6e-11 + * cosEpsA 1.0 + * cosEpsB 0 -0.0625 + * + */ + + //result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB + + // sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB; + + //result = sintA + sintA*cosEpsB + sintB + sintB * cosEpsB; + //result += costA*sinEpsA + costA*sinEpsB + costB*sinEpsA + costB * sinEpsB; + double a = 0; + double b = 0; + + // Compute sine + double t = sintA; + double c = a + t; + double d = -(c - a - t); + a = c; + b += d; + + t = costA*sinEpsA; + c = a + t; + d = -(c - a - t); + a = c; + b += d; + + b += sintA*cosEpsB + costA*sinEpsB; + b += sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB; + + double sina = a + b; + double sinb = -(sina - a - b); + + // Compute cosine + + a = b = c = d = 0.0; + + t = costA*cosEpsA; + c = a + t; + d = -(c - a - t); + a = c; + b += d; + + t = -sintA*sinEpsA; + c = a + t; + d = -(c - a - t); + a = c; + b += d; + + b += costB*cosEpsA + costA*cosEpsB + costB*cosEpsB; + b -= sintB*sinEpsA + sintA*sinEpsB + sintB*sinEpsB; + + double cosa = a + b; + double cosb = -(cosa - a - b); + + if (cotanFlag) { + double tmp; + tmp = cosa; cosa = sina; sina = tmp; + tmp = cosb; cosb = sinb; sinb = tmp; + } + + + /* estimate and correct, compute 1.0/(cosa+cosb) */ + /* + double est = (sina+sinb)/(cosa+cosb); + double err = (sina - cosa*est) + (sinb - cosb*est); + est += err/(cosa+cosb); + err = (sina - cosa*est) + (sinb - cosb*est); + */ + + // f(x) = 1/x, f'(x) = -1/x^2 + + double est = sina/cosa; + + /* Split the estimate to get more accurate read on division rounding */ + temp = est * HEX_40000000; + double esta = (est + temp) - temp; + double estb = est - esta; + + temp = cosa * HEX_40000000; + double cosaa = (cosa + temp) - temp; + double cosab = cosa - cosaa; + + //double err = (sina - est*cosa)/cosa; // Correction for division rounding + double err = (sina - esta*cosaa - esta*cosab - estb*cosaa - estb*cosab)/cosa; // Correction for division rounding + err += sinb/cosa; // Change in est due to sinb + err += -sina * cosb / cosa / cosa; // Change in est due to cosb + + if (xb != 0.0) { + // tan' = 1 + tan^2 cot' = -(1 + cot^2) + // Approximate impact of xb + double xbadj = xb + est*est*xb; + if (cotanFlag) { + xbadj = -xbadj; + } + + err += xbadj; + } + + return est+err; + } + + /** Reduce the input argument using the Payne and Hanek method. + * This is good for all inputs 0.0 < x < inf + * Output is remainder after dividing by PI/2 + * The result array should contain 3 numbers. + * result[0] is the integer portion, so mod 4 this gives the quadrant. + * result[1] is the upper bits of the remainder + * result[2] is the lower bits of the remainder + * + * @param x number to reduce + * @param result placeholder where to put the result + */ + private static void reducePayneHanek(double x, double result[]) + { + /* Convert input double to bits */ + long inbits = Double.doubleToRawLongBits(x); + int exponent = (int) ((inbits >> 52) & 0x7ff) - 1023; + + /* Convert to fixed point representation */ + inbits &= 0x000fffffffffffffL; + inbits |= 0x0010000000000000L; + + /* Normalize input to be between 0.5 and 1.0 */ + exponent++; + inbits <<= 11; + + /* Based on the exponent, get a shifted copy of recip2pi */ + long shpi0; + long shpiA; + long shpiB; + int idx = exponent >> 6; + int shift = exponent - (idx << 6); + + if (shift != 0) { + shpi0 = (idx == 0) ? 0 : (RECIP_2PI[idx-1] << shift); + shpi0 |= RECIP_2PI[idx] >>> (64-shift); + shpiA = (RECIP_2PI[idx] << shift) | (RECIP_2PI[idx+1] >>> (64-shift)); + shpiB = (RECIP_2PI[idx+1] << shift) | (RECIP_2PI[idx+2] >>> (64-shift)); + } else { + shpi0 = (idx == 0) ? 0 : RECIP_2PI[idx-1]; + shpiA = RECIP_2PI[idx]; + shpiB = RECIP_2PI[idx+1]; + } + + /* Multiply input by shpiA */ + long a = inbits >>> 32; + long b = inbits & 0xffffffffL; + + long c = shpiA >>> 32; + long d = shpiA & 0xffffffffL; + + long ac = a * c; + long bd = b * d; + long bc = b * c; + long ad = a * d; + + long prodB = bd + (ad << 32); + long prodA = ac + (ad >>> 32); + + boolean bita = (bd & 0x8000000000000000L) != 0; + boolean bitb = (ad & 0x80000000L ) != 0; + boolean bitsum = (prodB & 0x8000000000000000L) != 0; + + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prodA++; + } + + bita = (prodB & 0x8000000000000000L) != 0; + bitb = (bc & 0x80000000L ) != 0; + + prodB += bc << 32; + prodA += bc >>> 32; + + bitsum = (prodB & 0x8000000000000000L) != 0; + + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prodA++; + } + + /* Multiply input by shpiB */ + c = shpiB >>> 32; + d = shpiB & 0xffffffffL; + ac = a * c; + bc = b * c; + ad = a * d; + + /* Collect terms */ + ac += (bc + ad) >>> 32; + + bita = (prodB & 0x8000000000000000L) != 0; + bitb = (ac & 0x8000000000000000L ) != 0; + prodB += ac; + bitsum = (prodB & 0x8000000000000000L) != 0; + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prodA++; + } + + /* Multiply by shpi0 */ + c = shpi0 >>> 32; + d = shpi0 & 0xffffffffL; + + bd = b * d; + bc = b * c; + ad = a * d; + + prodA += bd + ((bc + ad) << 32); + + /* + * prodA, prodB now contain the remainder as a fraction of PI. We want this as a fraction of + * PI/2, so use the following steps: + * 1.) multiply by 4. + * 2.) do a fixed point muliply by PI/4. + * 3.) Convert to floating point. + * 4.) Multiply by 2 + */ + + /* This identifies the quadrant */ + int intPart = (int)(prodA >>> 62); + + /* Multiply by 4 */ + prodA <<= 2; + prodA |= prodB >>> 62; + prodB <<= 2; + + /* Multiply by PI/4 */ + a = prodA >>> 32; + b = prodA & 0xffffffffL; + + c = PI_O_4_BITS[0] >>> 32; + d = PI_O_4_BITS[0] & 0xffffffffL; + + ac = a * c; + bd = b * d; + bc = b * c; + ad = a * d; + + long prod2B = bd + (ad << 32); + long prod2A = ac + (ad >>> 32); + + bita = (bd & 0x8000000000000000L) != 0; + bitb = (ad & 0x80000000L ) != 0; + bitsum = (prod2B & 0x8000000000000000L) != 0; + + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prod2A++; + } + + bita = (prod2B & 0x8000000000000000L) != 0; + bitb = (bc & 0x80000000L ) != 0; + + prod2B += bc << 32; + prod2A += bc >>> 32; + + bitsum = (prod2B & 0x8000000000000000L) != 0; + + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prod2A++; + } + + /* Multiply input by pio4bits[1] */ + c = PI_O_4_BITS[1] >>> 32; + d = PI_O_4_BITS[1] & 0xffffffffL; + ac = a * c; + bc = b * c; + ad = a * d; + + /* Collect terms */ + ac += (bc + ad) >>> 32; + + bita = (prod2B & 0x8000000000000000L) != 0; + bitb = (ac & 0x8000000000000000L ) != 0; + prod2B += ac; + bitsum = (prod2B & 0x8000000000000000L) != 0; + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prod2A++; + } + + /* Multiply inputB by pio4bits[0] */ + a = prodB >>> 32; + b = prodB & 0xffffffffL; + c = PI_O_4_BITS[0] >>> 32; + d = PI_O_4_BITS[0] & 0xffffffffL; + ac = a * c; + bc = b * c; + ad = a * d; + + /* Collect terms */ + ac += (bc + ad) >>> 32; + + bita = (prod2B & 0x8000000000000000L) != 0; + bitb = (ac & 0x8000000000000000L ) != 0; + prod2B += ac; + bitsum = (prod2B & 0x8000000000000000L) != 0; + /* Carry */ + if ( (bita && bitb) || + ((bita || bitb) && !bitsum) ) { + prod2A++; + } + + /* Convert to double */ + double tmpA = (prod2A >>> 12) / TWO_POWER_52; // High order 52 bits + double tmpB = (((prod2A & 0xfffL) << 40) + (prod2B >>> 24)) / TWO_POWER_52 / TWO_POWER_52; // Low bits + + double sumA = tmpA + tmpB; + double sumB = -(sumA - tmpA - tmpB); + + /* Multiply by PI/2 and return */ + result[0] = intPart; + result[1] = sumA * 2.0; + result[2] = sumB * 2.0; + } + + /** + * Sine function. + * + * @param x Argument. + * @return sin(x) + */ + public static double sin(double x) { + boolean negative = false; + int quadrant = 0; + double xa; + double xb = 0.0; + + /* Take absolute value of the input */ + xa = x; + if (x < 0) { + negative = true; + xa = -xa; + } + + /* Check for zero and negative zero */ + if (xa == 0.0) { + long bits = Double.doubleToRawLongBits(x); + if (bits < 0) { + return -0.0; + } + return 0.0; + } + + if (xa != xa || xa == Double.POSITIVE_INFINITY) { + return Double.NaN; + } + + /* Perform any argument reduction */ + if (xa > 3294198.0) { + // PI * (2**20) + // Argument too big for CodyWaite reduction. Must use + // PayneHanek. + double reduceResults[] = new double[3]; + reducePayneHanek(xa, reduceResults); + quadrant = ((int) reduceResults[0]) & 3; + xa = reduceResults[1]; + xb = reduceResults[2]; + } else if (xa > 1.5707963267948966) { + final CodyWaite cw = new CodyWaite(xa); + quadrant = cw.getK() & 3; + xa = cw.getRemA(); + xb = cw.getRemB(); + } + + if (negative) { + quadrant ^= 2; // Flip bit 1 + } + + switch (quadrant) { + case 0: + return sinQ(xa, xb); + case 1: + return cosQ(xa, xb); + case 2: + return -sinQ(xa, xb); + case 3: + return -cosQ(xa, xb); + default: + return Double.NaN; + } + } + + /** + * Cosine function. + * + * @param x Argument. + * @return cos(x) + */ + public static double cos(double x) { + int quadrant = 0; + + /* Take absolute value of the input */ + double xa = x; + if (x < 0) { + xa = -xa; + } + + if (xa != xa || xa == Double.POSITIVE_INFINITY) { + return Double.NaN; + } + + /* Perform any argument reduction */ + double xb = 0; + if (xa > 3294198.0) { + // PI * (2**20) + // Argument too big for CodyWaite reduction. Must use + // PayneHanek. + double reduceResults[] = new double[3]; + reducePayneHanek(xa, reduceResults); + quadrant = ((int) reduceResults[0]) & 3; + xa = reduceResults[1]; + xb = reduceResults[2]; + } else if (xa > 1.5707963267948966) { + final CodyWaite cw = new CodyWaite(xa); + quadrant = cw.getK() & 3; + xa = cw.getRemA(); + xb = cw.getRemB(); + } + + //if (negative) + // quadrant = (quadrant + 2) % 4; + + switch (quadrant) { + case 0: + return cosQ(xa, xb); + case 1: + return -sinQ(xa, xb); + case 2: + return -cosQ(xa, xb); + case 3: + return sinQ(xa, xb); + default: + return Double.NaN; + } + } + + /** + * Tangent function. + * + * @param x Argument. + * @return tan(x) + */ + public static double tan(double x) { + boolean negative = false; + int quadrant = 0; + + /* Take absolute value of the input */ + double xa = x; + if (x < 0) { + negative = true; + xa = -xa; + } + + /* Check for zero and negative zero */ + if (xa == 0.0) { + long bits = Double.doubleToRawLongBits(x); + if (bits < 0) { + return -0.0; + } + return 0.0; + } + + if (xa != xa || xa == Double.POSITIVE_INFINITY) { + return Double.NaN; + } + + /* Perform any argument reduction */ + double xb = 0; + if (xa > 3294198.0) { + // PI * (2**20) + // Argument too big for CodyWaite reduction. Must use + // PayneHanek. + double reduceResults[] = new double[3]; + reducePayneHanek(xa, reduceResults); + quadrant = ((int) reduceResults[0]) & 3; + xa = reduceResults[1]; + xb = reduceResults[2]; + } else if (xa > 1.5707963267948966) { + final CodyWaite cw = new CodyWaite(xa); + quadrant = cw.getK() & 3; + xa = cw.getRemA(); + xb = cw.getRemB(); + } + + if (xa > 1.5) { + // Accuracy suffers between 1.5 and PI/2 + final double pi2a = 1.5707963267948966; + final double pi2b = 6.123233995736766E-17; + + final double a = pi2a - xa; + double b = -(a - pi2a + xa); + b += pi2b - xb; + + xa = a + b; + xb = -(xa - a - b); + quadrant ^= 1; + negative ^= true; + } + + double result; + if ((quadrant & 1) == 0) { + result = tanQ(xa, xb, false); + } else { + result = -tanQ(xa, xb, true); + } + + if (negative) { + result = -result; + } + + return result; + } + + /** + * Arctangent function + * @param x a number + * @return atan(x) + */ + public static double atan(double x) { + return atan(x, 0.0, false); + } + + /** Internal helper function to compute arctangent. + * @param xa number from which arctangent is requested + * @param xb extra bits for x (may be 0.0) + * @param leftPlane if true, result angle must be put in the left half plane + * @return atan(xa + xb) (or angle shifted by {@code PI} if leftPlane is true) + */ + private static double atan(double xa, double xb, boolean leftPlane) { + if (xa == 0.0) { // Matches +/- 0.0; return correct sign + return leftPlane ? copySign(Math.PI, xa) : xa; + } + + final boolean negate; + if (xa < 0) { + // negative + xa = -xa; + xb = -xb; + negate = true; + } else { + negate = false; + } + + if (xa > 1.633123935319537E16) { // Very large input + return (negate ^ leftPlane) ? (-Math.PI * F_1_2) : (Math.PI * F_1_2); + } + + /* Estimate the closest tabulated arctan value, compute eps = xa-tangentTable */ + final int idx; + if (xa < 1) { + idx = (int) (((-1.7168146928204136 * xa * xa + 8.0) * xa) + 0.5); + } else { + final double oneOverXa = 1 / xa; + idx = (int) (-((-1.7168146928204136 * oneOverXa * oneOverXa + 8.0) * oneOverXa) + 13.07); + } + + final double ttA = TANGENT_TABLE_A[idx]; + final double ttB = TANGENT_TABLE_B[idx]; + + double epsA = xa - ttA; + double epsB = -(epsA - xa + ttA); + epsB += xb - ttB; + + double temp = epsA + epsB; + epsB = -(temp - epsA - epsB); + epsA = temp; + + /* Compute eps = eps / (1.0 + xa*tangent) */ + temp = xa * HEX_40000000; + double ya = xa + temp - temp; + double yb = xb + xa - ya; + xa = ya; + xb += yb; + + //if (idx > 8 || idx == 0) + if (idx == 0) { + /* If the slope of the arctan is gentle enough (< 0.45), this approximation will suffice */ + //double denom = 1.0 / (1.0 + xa*tangentTableA[idx] + xb*tangentTableA[idx] + xa*tangentTableB[idx] + xb*tangentTableB[idx]); + final double denom = 1d / (1d + (xa + xb) * (ttA + ttB)); + //double denom = 1.0 / (1.0 + xa*tangentTableA[idx]); + ya = epsA * denom; + yb = epsB * denom; + } else { + double temp2 = xa * ttA; + double za = 1d + temp2; + double zb = -(za - 1d - temp2); + temp2 = xb * ttA + xa * ttB; + temp = za + temp2; + zb += -(temp - za - temp2); + za = temp; + + zb += xb * ttB; + ya = epsA / za; + + temp = ya * HEX_40000000; + final double yaa = (ya + temp) - temp; + final double yab = ya - yaa; + + temp = za * HEX_40000000; + final double zaa = (za + temp) - temp; + final double zab = za - zaa; + + /* Correct for rounding in division */ + yb = (epsA - yaa * zaa - yaa * zab - yab * zaa - yab * zab) / za; + + yb += -epsA * zb / za / za; + yb += epsB / za; + } + + + epsA = ya; + epsB = yb; + + /* Evaluate polynomial */ + final double epsA2 = epsA * epsA; + + /* + yb = -0.09001346640161823; + yb = yb * epsA2 + 0.11110718400605211; + yb = yb * epsA2 + -0.1428571349122913; + yb = yb * epsA2 + 0.19999999999273194; + yb = yb * epsA2 + -0.33333333333333093; + yb = yb * epsA2 * epsA; + */ + + yb = 0.07490822288864472; + yb = yb * epsA2 - 0.09088450866185192; + yb = yb * epsA2 + 0.11111095942313305; + yb = yb * epsA2 - 0.1428571423679182; + yb = yb * epsA2 + 0.19999999999923582; + yb = yb * epsA2 - 0.33333333333333287; + yb = yb * epsA2 * epsA; + + + ya = epsA; + + temp = ya + yb; + yb = -(temp - ya - yb); + ya = temp; + + /* Add in effect of epsB. atan'(x) = 1/(1+x^2) */ + yb += epsB / (1d + epsA * epsA); + + final double eighths = EIGHTHS[idx]; + + //result = yb + eighths[idx] + ya; + double za = eighths + ya; + double zb = -(za - eighths - ya); + temp = za + yb; + zb += -(temp - za - yb); + za = temp; + + double result = za + zb; + + if (leftPlane) { + // Result is in the left plane + final double resultb = -(result - za - zb); + final double pia = 1.5707963267948966 * 2; + final double pib = 6.123233995736766E-17 * 2; + + za = pia - result; + zb = -(za - pia + result); + zb += pib - resultb; + + result = za + zb; + } + + + if (negate ^ leftPlane) { + result = -result; + } + + return result; + } + + /** + * Two arguments arctangent function + * @param y ordinate + * @param x abscissa + * @return phase angle of point (x,y) between {@code -PI} and {@code PI} + */ + public static double atan2(double y, double x) { + if (x != x || y != y) { + return Double.NaN; + } + + if (y == 0) { + final double result = x * y; + final double invx = 1d / x; + final double invy = 1d / y; + + if (invx == 0) { // X is infinite + if (x > 0) { + return y; // return +/- 0.0 + } else { + return copySign(Math.PI, y); + } + } + + if (x < 0 || invx < 0) { + if (y < 0 || invy < 0) { + return -Math.PI; + } else { + return Math.PI; + } + } else { + return result; + } + } + + // y cannot now be zero + + if (y == Double.POSITIVE_INFINITY) { + if (x == Double.POSITIVE_INFINITY) { + return Math.PI * F_1_4; + } + + if (x == Double.NEGATIVE_INFINITY) { + return Math.PI * F_3_4; + } + + return Math.PI * F_1_2; + } + + if (y == Double.NEGATIVE_INFINITY) { + if (x == Double.POSITIVE_INFINITY) { + return -Math.PI * F_1_4; + } + + if (x == Double.NEGATIVE_INFINITY) { + return -Math.PI * F_3_4; + } + + return -Math.PI * F_1_2; + } + + if (x == Double.POSITIVE_INFINITY) { + if (y > 0 || 1 / y > 0) { + return 0d; + } + + if (y < 0 || 1 / y < 0) { + return -0d; + } + } + + if (x == Double.NEGATIVE_INFINITY) + { + if (y > 0.0 || 1 / y > 0.0) { + return Math.PI; + } + + if (y < 0 || 1 / y < 0) { + return -Math.PI; + } + } + + // Neither y nor x can be infinite or NAN here + + if (x == 0) { + if (y > 0 || 1 / y > 0) { + return Math.PI * F_1_2; + } + + if (y < 0 || 1 / y < 0) { + return -Math.PI * F_1_2; + } + } + + // Compute ratio r = y/x + final double r = y / x; + if (Double.isInfinite(r)) { // bypass calculations that can create NaN + return atan(r, 0, x < 0); + } + + double ra = doubleHighPart(r); + double rb = r - ra; + + // Split x + final double xa = doubleHighPart(x); + final double xb = x - xa; + + rb += (y - ra * xa - ra * xb - rb * xa - rb * xb) / x; + + final double temp = ra + rb; + rb = -(temp - ra - rb); + ra = temp; + + if (ra == 0) { // Fix up the sign so atan works correctly + ra = copySign(0d, y); + } + + // Call atan + final double result = atan(ra, rb, x < 0); + + return result; + } + + /** Compute the arc sine of a number. + * @param x number on which evaluation is done + * @return arc sine of x + */ + public static double asin(double x) { + if (x != x) { + return Double.NaN; + } + + if (x > 1.0 || x < -1.0) { + return Double.NaN; + } + + if (x == 1.0) { + return Math.PI/2.0; + } + + if (x == -1.0) { + return -Math.PI/2.0; + } + + if (x == 0.0) { // Matches +/- 0.0; return correct sign + return x; + } + + /* Compute asin(x) = atan(x/sqrt(1-x*x)) */ + + /* Split x */ + double temp = x * HEX_40000000; + final double xa = x + temp - temp; + final double xb = x - xa; + + /* Square it */ + double ya = xa*xa; + double yb = xa*xb*2.0 + xb*xb; + + /* Subtract from 1 */ + ya = -ya; + yb = -yb; + + double za = 1.0 + ya; + double zb = -(za - 1.0 - ya); + + temp = za + yb; + zb += -(temp - za - yb); + za = temp; + + /* Square root */ + double y; + y = sqrt(za); + temp = y * HEX_40000000; + ya = y + temp - temp; + yb = y - ya; + + /* Extend precision of sqrt */ + yb += (za - ya*ya - 2*ya*yb - yb*yb) / (2.0*y); + + /* Contribution of zb to sqrt */ + double dx = zb / (2.0*y); + + // Compute ratio r = x/y + double r = x/y; + temp = r * HEX_40000000; + double ra = r + temp - temp; + double rb = r - ra; + + rb += (x - ra*ya - ra*yb - rb*ya - rb*yb) / y; // Correct for rounding in division + rb += -x * dx / y / y; // Add in effect additional bits of sqrt. + + temp = ra + rb; + rb = -(temp - ra - rb); + ra = temp; + + return atan(ra, rb, false); + } + + /** Compute the arc cosine of a number. + * @param x number on which evaluation is done + * @return arc cosine of x + */ + public static double acos(double x) { + if (x != x) { + return Double.NaN; + } + + if (x > 1.0 || x < -1.0) { + return Double.NaN; + } + + if (x == -1.0) { + return Math.PI; + } + + if (x == 1.0) { + return 0.0; + } + + if (x == 0) { + return Math.PI/2.0; + } + + /* Compute acos(x) = atan(sqrt(1-x*x)/x) */ + + /* Split x */ + double temp = x * HEX_40000000; + final double xa = x + temp - temp; + final double xb = x - xa; + + /* Square it */ + double ya = xa*xa; + double yb = xa*xb*2.0 + xb*xb; + + /* Subtract from 1 */ + ya = -ya; + yb = -yb; + + double za = 1.0 + ya; + double zb = -(za - 1.0 - ya); + + temp = za + yb; + zb += -(temp - za - yb); + za = temp; + + /* Square root */ + double y = sqrt(za); + temp = y * HEX_40000000; + ya = y + temp - temp; + yb = y - ya; + + /* Extend precision of sqrt */ + yb += (za - ya*ya - 2*ya*yb - yb*yb) / (2.0*y); + + /* Contribution of zb to sqrt */ + yb += zb / (2.0*y); + y = ya+yb; + yb = -(y - ya - yb); + + // Compute ratio r = y/x + double r = y/x; + + // Did r overflow? + if (Double.isInfinite(r)) { // x is effectively zero + return Math.PI/2; // so return the appropriate value + } + + double ra = doubleHighPart(r); + double rb = r - ra; + + rb += (y - ra*xa - ra*xb - rb*xa - rb*xb) / x; // Correct for rounding in division + rb += yb / x; // Add in effect additional bits of sqrt. + + temp = ra + rb; + rb = -(temp - ra - rb); + ra = temp; + + return atan(ra, rb, x<0); + } + + /** Compute the cubic root of a number. + * @param x number on which evaluation is done + * @return cubic root of x + */ + public static double cbrt(double x) { + /* Convert input double to bits */ + long inbits = Double.doubleToRawLongBits(x); + int exponent = (int) ((inbits >> 52) & 0x7ff) - 1023; + boolean subnormal = false; + + if (exponent == -1023) { + if (x == 0) { + return x; + } + + /* Subnormal, so normalize */ + subnormal = true; + x *= 1.8014398509481984E16; // 2^54 + inbits = Double.doubleToRawLongBits(x); + exponent = (int) ((inbits >> 52) & 0x7ff) - 1023; + } + + if (exponent == 1024) { + // Nan or infinity. Don't care which. + return x; + } + + /* Divide the exponent by 3 */ + int exp3 = exponent / 3; + + /* p2 will be the nearest power of 2 to x with its exponent divided by 3 */ + double p2 = Double.longBitsToDouble((inbits & 0x8000000000000000L) | + (long)(((exp3 + 1023) & 0x7ff)) << 52); + + /* This will be a number between 1 and 2 */ + final double mant = Double.longBitsToDouble((inbits & 0x000fffffffffffffL) | 0x3ff0000000000000L); + + /* Estimate the cube root of mant by polynomial */ + double est = -0.010714690733195933; + est = est * mant + 0.0875862700108075; + est = est * mant + -0.3058015757857271; + est = est * mant + 0.7249995199969751; + est = est * mant + 0.5039018405998233; + + est *= CBRTTWO[exponent % 3 + 2]; + + // est should now be good to about 15 bits of precision. Do 2 rounds of + // Newton's method to get closer, this should get us full double precision + // Scale down x for the purpose of doing newtons method. This avoids over/under flows. + final double xs = x / (p2*p2*p2); + est += (xs - est*est*est) / (3*est*est); + est += (xs - est*est*est) / (3*est*est); + + // Do one round of Newton's method in extended precision to get the last bit right. + double temp = est * HEX_40000000; + double ya = est + temp - temp; + double yb = est - ya; + + double za = ya * ya; + double zb = ya * yb * 2.0 + yb * yb; + temp = za * HEX_40000000; + double temp2 = za + temp - temp; + zb += za - temp2; + za = temp2; + + zb = za * yb + ya * zb + zb * yb; + za *= ya; + + double na = xs - za; + double nb = -(na - xs + za); + nb -= zb; + + est += (na+nb)/(3*est*est); + + /* Scale by a power of two, so this is exact. */ + est *= p2; + + if (subnormal) { + est *= 3.814697265625E-6; // 2^-18 + } + + return est; + } + + /** + * Convert degrees to radians, with error of less than 0.5 ULP + * @param x angle in degrees + * @return x converted into radians + */ + public static double toRadians(double x) + { + if (Double.isInfinite(x) || x == 0.0) { // Matches +/- 0.0; return correct sign + return x; + } + + // These are PI/180 split into high and low order bits + final double facta = 0.01745329052209854; + final double factb = 1.997844754509471E-9; + + double xa = doubleHighPart(x); + double xb = x - xa; + + double result = xb * factb + xb * facta + xa * factb + xa * facta; + if (result == 0) { + result *= x; // ensure correct sign if calculation underflows + } + return result; + } + + /** + * Convert radians to degrees, with error of less than 0.5 ULP + * @param x angle in radians + * @return x converted into degrees + */ + public static double toDegrees(double x) + { + if (Double.isInfinite(x) || x == 0.0) { // Matches +/- 0.0; return correct sign + return x; + } + + // These are 180/PI split into high and low order bits + final double facta = 57.2957763671875; + final double factb = 3.145894820876798E-6; + + double xa = doubleHighPart(x); + double xb = x - xa; + + return xb * factb + xb * facta + xa * factb + xa * facta; + } + + /** + * Absolute value. + * @param x number from which absolute value is requested + * @return abs(x) + */ + public static int abs(final int x) { + final int i = x >>> 31; + return (x ^ (~i + 1)) + i; + } + + /** + * Absolute value. + * @param x number from which absolute value is requested + * @return abs(x) + */ + public static long abs(final long x) { + final long l = x >>> 63; + // l is one if x negative zero else + // ~l+1 is zero if x is positive, -1 if x is negative + // x^(~l+1) is x is x is positive, ~x if x is negative + // add around + return (x ^ (~l + 1)) + l; + } + + /** + * Absolute value. + * @param x number from which absolute value is requested + * @return abs(x) + */ + public static float abs(final float x) { + return Float.intBitsToFloat(MASK_NON_SIGN_INT & Float.floatToRawIntBits(x)); + } + + /** + * Absolute value. + * @param x number from which absolute value is requested + * @return abs(x) + */ + public static double abs(double x) { + return Double.longBitsToDouble(MASK_NON_SIGN_LONG & Double.doubleToRawLongBits(x)); + } + + /** + * Compute least significant bit (Unit in Last Position) for a number. + * @param x number from which ulp is requested + * @return ulp(x) + */ + public static double ulp(double x) { + if (Double.isInfinite(x)) { + return Double.POSITIVE_INFINITY; + } + return abs(x - Double.longBitsToDouble(Double.doubleToRawLongBits(x) ^ 1)); + } + + /** + * Compute least significant bit (Unit in Last Position) for a number. + * @param x number from which ulp is requested + * @return ulp(x) + */ + public static float ulp(float x) { + if (Float.isInfinite(x)) { + return Float.POSITIVE_INFINITY; + } + return abs(x - Float.intBitsToFloat(Float.floatToIntBits(x) ^ 1)); + } + + /** + * Multiply a double number by a power of 2. + * @param d number to multiply + * @param n power of 2 + * @return d × 2n + */ + public static double scalb(final double d, final int n) { + + // first simple and fast handling when 2^n can be represented using normal numbers + if ((n > -1023) && (n < 1024)) { + return d * Double.longBitsToDouble(((long) (n + 1023)) << 52); + } + + // handle special cases + if (Double.isNaN(d) || Double.isInfinite(d) || (d == 0)) { + return d; + } + if (n < -2098) { + return (d > 0) ? 0.0 : -0.0; + } + if (n > 2097) { + return (d > 0) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + + // decompose d + final long bits = Double.doubleToRawLongBits(d); + final long sign = bits & 0x8000000000000000L; + int exponent = ((int) (bits >>> 52)) & 0x7ff; + long mantissa = bits & 0x000fffffffffffffL; + + // compute scaled exponent + int scaledExponent = exponent + n; + + if (n < 0) { + // we are really in the case n <= -1023 + if (scaledExponent > 0) { + // both the input and the result are normal numbers, we only adjust the exponent + return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa); + } else if (scaledExponent > -53) { + // the input is a normal number and the result is a subnormal number + + // recover the hidden mantissa bit + mantissa |= 1L << 52; + + // scales down complete mantissa, hence losing least significant bits + final long mostSignificantLostBit = mantissa & (1L << (-scaledExponent)); + mantissa >>>= 1 - scaledExponent; + if (mostSignificantLostBit != 0) { + // we need to add 1 bit to round up the result + mantissa++; + } + return Double.longBitsToDouble(sign | mantissa); + + } else { + // no need to compute the mantissa, the number scales down to 0 + return (sign == 0L) ? 0.0 : -0.0; + } + } else { + // we are really in the case n >= 1024 + if (exponent == 0) { + + // the input number is subnormal, normalize it + while ((mantissa >>> 52) != 1) { + mantissa <<= 1; + --scaledExponent; + } + ++scaledExponent; + mantissa &= 0x000fffffffffffffL; + + if (scaledExponent < 2047) { + return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa); + } else { + return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + + } else if (scaledExponent < 2047) { + return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa); + } else { + return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + } + + } + + /** + * Multiply a float number by a power of 2. + * @param f number to multiply + * @param n power of 2 + * @return f × 2n + */ + public static float scalb(final float f, final int n) { + + // first simple and fast handling when 2^n can be represented using normal numbers + if ((n > -127) && (n < 128)) { + return f * Float.intBitsToFloat((n + 127) << 23); + } + + // handle special cases + if (Float.isNaN(f) || Float.isInfinite(f) || (f == 0f)) { + return f; + } + if (n < -277) { + return (f > 0) ? 0.0f : -0.0f; + } + if (n > 276) { + return (f > 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY; + } + + // decompose f + final int bits = Float.floatToIntBits(f); + final int sign = bits & 0x80000000; + int exponent = (bits >>> 23) & 0xff; + int mantissa = bits & 0x007fffff; + + // compute scaled exponent + int scaledExponent = exponent + n; + + if (n < 0) { + // we are really in the case n <= -127 + if (scaledExponent > 0) { + // both the input and the result are normal numbers, we only adjust the exponent + return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa); + } else if (scaledExponent > -24) { + // the input is a normal number and the result is a subnormal number + + // recover the hidden mantissa bit + mantissa |= 1 << 23; + + // scales down complete mantissa, hence losing least significant bits + final int mostSignificantLostBit = mantissa & (1 << (-scaledExponent)); + mantissa >>>= 1 - scaledExponent; + if (mostSignificantLostBit != 0) { + // we need to add 1 bit to round up the result + mantissa++; + } + return Float.intBitsToFloat(sign | mantissa); + + } else { + // no need to compute the mantissa, the number scales down to 0 + return (sign == 0) ? 0.0f : -0.0f; + } + } else { + // we are really in the case n >= 128 + if (exponent == 0) { + + // the input number is subnormal, normalize it + while ((mantissa >>> 23) != 1) { + mantissa <<= 1; + --scaledExponent; + } + ++scaledExponent; + mantissa &= 0x007fffff; + + if (scaledExponent < 255) { + return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa); + } else { + return (sign == 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY; + } + + } else if (scaledExponent < 255) { + return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa); + } else { + return (sign == 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY; + } + } + + } + + /** + * Get the next machine representable number after a number, moving + * in the direction of another number. + *

        + * The ordering is as follows (increasing): + *

          + *
        • -INFINITY
        • + *
        • -MAX_VALUE
        • + *
        • -MIN_VALUE
        • + *
        • -0.0
        • + *
        • +0.0
        • + *
        • +MIN_VALUE
        • + *
        • +MAX_VALUE
        • + *
        • +INFINITY
        • + *
        • + *

          + * If arguments compare equal, then the second argument is returned. + *

          + * 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.

          + *

          + * If {@code d} is infinite and direction does not + * bring it back to finite numbers, it is returned unchanged.

          + * + * @param d base number + * @param direction (the only important thing is whether + * {@code direction} is greater or smaller than {@code d}) + * @return the next machine representable number in the specified direction + */ + public static double nextAfter(double d, double direction) { + + // handling of some important special cases + if (Double.isNaN(d) || Double.isNaN(direction)) { + return Double.NaN; + } else if (d == direction) { + return direction; + } else if (Double.isInfinite(d)) { + return (d < 0) ? -Double.MAX_VALUE : Double.MAX_VALUE; + } else if (d == 0) { + return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE; + } + // special cases MAX_VALUE to infinity and MIN_VALUE to 0 + // are handled just as normal numbers + // can use raw bits since already dealt with infinity and NaN + final long bits = Double.doubleToRawLongBits(d); + final long sign = bits & 0x8000000000000000L; + if ((direction < d) ^ (sign == 0L)) { + return Double.longBitsToDouble(sign | ((bits & 0x7fffffffffffffffL) + 1)); + } else { + return Double.longBitsToDouble(sign | ((bits & 0x7fffffffffffffffL) - 1)); + } + + } + + /** + * Get the next machine representable number after a number, moving + * in the direction of another number. + *

          + * The ordering is as follows (increasing): + *

            + *
          • -INFINITY
          • + *
          • -MAX_VALUE
          • + *
          • -MIN_VALUE
          • + *
          • -0.0
          • + *
          • +0.0
          • + *
          • +MIN_VALUE
          • + *
          • +MAX_VALUE
          • + *
          • +INFINITY
          • + *
          • + *

            + * If arguments compare equal, then the second argument is returned. + *

            + * 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.

            + *

            + * If {@code f} is infinite and direction does not + * bring it back to finite numbers, it is returned unchanged.

            + * + * @param f base number + * @param direction (the only important thing is whether + * {@code direction} is greater or smaller than {@code f}) + * @return the next machine representable number in the specified direction + */ + public static float nextAfter(final float f, final double direction) { + + // handling of some important special cases + if (Double.isNaN(f) || Double.isNaN(direction)) { + return Float.NaN; + } else if (f == direction) { + return (float) direction; + } else if (Float.isInfinite(f)) { + return (f < 0f) ? -Float.MAX_VALUE : Float.MAX_VALUE; + } else if (f == 0f) { + return (direction < 0) ? -Float.MIN_VALUE : Float.MIN_VALUE; + } + // special cases MAX_VALUE to infinity and MIN_VALUE to 0 + // are handled just as normal numbers + + final int bits = Float.floatToIntBits(f); + final int sign = bits & 0x80000000; + if ((direction < f) ^ (sign == 0)) { + return Float.intBitsToFloat(sign | ((bits & 0x7fffffff) + 1)); + } else { + return Float.intBitsToFloat(sign | ((bits & 0x7fffffff) - 1)); + } + + } + + /** Get the largest whole number smaller than x. + * @param x number from which floor is requested + * @return a double number f such that f is an integer f <= x < f + 1.0 + */ + public static double floor(double x) { + long y; + + if (x != x) { // NaN + return x; + } + + if (x >= TWO_POWER_52 || x <= -TWO_POWER_52) { + return x; + } + + y = (long) x; + if (x < 0 && y != x) { + y--; + } + + if (y == 0) { + return x*y; + } + + return y; + } + + /** Get the smallest whole number larger than x. + * @param x number from which ceil is requested + * @return a double number c such that c is an integer c - 1.0 < x <= c + */ + public static double ceil(double x) { + double y; + + if (x != x) { // NaN + return x; + } + + y = floor(x); + if (y == x) { + return y; + } + + y += 1.0; + + if (y == 0) { + return x*y; + } + + return y; + } + + /** Get the whole number that is the nearest to x, or the even one if x is exactly half way between two integers. + * @param x number from which nearest whole number is requested + * @return a double number r such that r is an integer r - 0.5 <= x <= r + 0.5 + */ + public static double rint(double x) { + double y = floor(x); + double d = x - y; + + if (d > 0.5) { + if (y == -1.0) { + return -0.0; // Preserve sign of operand + } + return y+1.0; + } + if (d < 0.5) { + return y; + } + + /* half way, round to even */ + long z = (long) y; + return (z & 1) == 0 ? y : y + 1.0; + } + + /** Get the closest long to x. + * @param x number from which closest long is requested + * @return closest long to x + */ + public static long round(double x) { + return (long) floor(x + 0.5); + } + + /** Get the closest int to x. + * @param x number from which closest int is requested + * @return closest int to x + */ + public static int round(final float x) { + return (int) floor(x + 0.5f); + } + + /** Compute the minimum of two values + * @param a first value + * @param b second value + * @return a if a is lesser or equal to b, b otherwise + */ + public static int min(final int a, final int b) { + return (a <= b) ? a : b; + } + + /** Compute the minimum of two values + * @param a first value + * @param b second value + * @return a if a is lesser or equal to b, b otherwise + */ + public static long min(final long a, final long b) { + return (a <= b) ? a : b; + } + + /** Compute the minimum of two values + * @param a first value + * @param b second value + * @return a if a is lesser or equal to b, b otherwise + */ + public static float min(final float a, final float b) { + if (a > b) { + return b; + } + if (a < b) { + return a; + } + /* if either arg is NaN, return NaN */ + if (a != b) { + return Float.NaN; + } + /* min(+0.0,-0.0) == -0.0 */ + /* 0x80000000 == Float.floatToRawIntBits(-0.0d) */ + int bits = Float.floatToRawIntBits(a); + if (bits == 0x80000000) { + return a; + } + return b; + } + + /** Compute the minimum of two values + * @param a first value + * @param b second value + * @return a if a is lesser or equal to b, b otherwise + */ + public static double min(final double a, final double b) { + if (a > b) { + return b; + } + if (a < b) { + return a; + } + /* if either arg is NaN, return NaN */ + if (a != b) { + return Double.NaN; + } + /* min(+0.0,-0.0) == -0.0 */ + /* 0x8000000000000000L == Double.doubleToRawLongBits(-0.0d) */ + long bits = Double.doubleToRawLongBits(a); + if (bits == 0x8000000000000000L) { + return a; + } + return b; + } + + /** Compute the maximum of two values + * @param a first value + * @param b second value + * @return b if a is lesser or equal to b, a otherwise + */ + public static int max(final int a, final int b) { + return (a <= b) ? b : a; + } + + /** Compute the maximum of two values + * @param a first value + * @param b second value + * @return b if a is lesser or equal to b, a otherwise + */ + public static long max(final long a, final long b) { + return (a <= b) ? b : a; + } + + /** Compute the maximum of two values + * @param a first value + * @param b second value + * @return b if a is lesser or equal to b, a otherwise + */ + public static float max(final float a, final float b) { + if (a > b) { + return a; + } + if (a < b) { + return b; + } + /* if either arg is NaN, return NaN */ + if (a != b) { + return Float.NaN; + } + /* min(+0.0,-0.0) == -0.0 */ + /* 0x80000000 == Float.floatToRawIntBits(-0.0d) */ + int bits = Float.floatToRawIntBits(a); + if (bits == 0x80000000) { + return b; + } + return a; + } + + /** Compute the maximum of two values + * @param a first value + * @param b second value + * @return b if a is lesser or equal to b, a otherwise + */ + public static double max(final double a, final double b) { + if (a > b) { + return a; + } + if (a < b) { + return b; + } + /* if either arg is NaN, return NaN */ + if (a != b) { + return Double.NaN; + } + /* min(+0.0,-0.0) == -0.0 */ + /* 0x8000000000000000L == Double.doubleToRawLongBits(-0.0d) */ + long bits = Double.doubleToRawLongBits(a); + if (bits == 0x8000000000000000L) { + return b; + } + return a; + } + + /** + * Returns the hypotenuse of a triangle with sides {@code x} and {@code y} + * - sqrt(x2 +y2)
            + * avoiding intermediate overflow or underflow. + * + *
              + *
            • If either argument is infinite, then the result is positive infinity.
            • + *
            • else, if either argument is NaN then the result is NaN.
            • + *
            + * + * @param x a value + * @param y a value + * @return sqrt(x2 +y2) + */ + 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. + *

            + *

              + *
            • If either operand is NaN, the result is NaN.
            • + *
            • If the result is not NaN, the sign of the result equals the sign of the dividend.
            • + *
            • If the dividend is an infinity, or the divisor is a zero, or both, the result is NaN.
            • + *
            • If the dividend is finite and the divisor is an infinity, the result equals the dividend.
            • + *
            • If the dividend is a zero and the divisor is finite, the result equals the dividend.
            • + *
            + *

            Note: this implementation currently delegates to {@link StrictMath#IEEEremainder} + * @param dividend the number to be divided + * @param divisor the number by which to divide + * @return the remainder, rounded + */ + public static double IEEEremainder(double dividend, double divisor) { + return StrictMath.IEEEremainder(dividend, divisor); // TODO provide our own implementation + } + + /** Convert a long to interger, detecting overflows + * @param n number to convert to int + * @return integer with same valie as n if no overflows occur + * @exception MathArithmeticException if n cannot fit into an int + * @since 3.4 + */ + public static int toIntExact(final long n) throws MathArithmeticException { + if (n < Integer.MIN_VALUE || n > Integer.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } + return (int) n; + } + + /** Increment a number, detecting overflows. + * @param n number to increment + * @return n+1 if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static int incrementExact(final int n) throws MathArithmeticException { + + if (n == Integer.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, n, 1); + } + + return n + 1; + + } + + /** Increment a number, detecting overflows. + * @param n number to increment + * @return n+1 if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static long incrementExact(final long n) throws MathArithmeticException { + + if (n == Long.MAX_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, n, 1); + } + + return n + 1; + + } + + /** Decrement a number, detecting overflows. + * @param n number to decrement + * @return n-1 if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static int decrementExact(final int n) throws MathArithmeticException { + + if (n == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, n, 1); + } + + return n - 1; + + } + + /** Decrement a number, detecting overflows. + * @param n number to decrement + * @return n-1 if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static long decrementExact(final long n) throws MathArithmeticException { + + if (n == Long.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, n, 1); + } + + return n - 1; + + } + + /** Add two numbers, detecting overflows. + * @param a first number to add + * @param b second number to add + * @return a+b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static int addExact(final int a, final int b) throws MathArithmeticException { + + // compute sum + final int sum = a + b; + + // check for overflow + if ((a ^ b) >= 0 && (sum ^ b) < 0) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, b); + } + + return sum; + + } + + /** Add two numbers, detecting overflows. + * @param a first number to add + * @param b second number to add + * @return a+b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static long addExact(final long a, final long b) throws MathArithmeticException { + + // compute sum + final long sum = a + b; + + // check for overflow + if ((a ^ b) >= 0 && (sum ^ b) < 0) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, b); + } + + return sum; + + } + + /** Subtract two numbers, detecting overflows. + * @param a first number + * @param b second number to subtract from a + * @return a-b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static int subtractExact(final int a, final int b) { + + // compute subtraction + final int sub = a - b; + + // check for overflow + if ((a ^ b) < 0 && (sub ^ b) >= 0) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, a, b); + } + + return sub; + + } + + /** Subtract two numbers, detecting overflows. + * @param a first number + * @param b second number to subtract from a + * @return a-b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static long subtractExact(final long a, final long b) { + + // compute subtraction + final long sub = a - b; + + // check for overflow + if ((a ^ b) < 0 && (sub ^ b) >= 0) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, a, b); + } + + return sub; + + } + + /** Multiply two numbers, detecting overflows. + * @param a first number to multiply + * @param b second number to multiply + * @return a*b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static int multiplyExact(final int a, final int b) { + if (((b > 0) && (a > Integer.MAX_VALUE / b || a < Integer.MIN_VALUE / b)) || + ((b < -1) && (a > Integer.MIN_VALUE / b || a < Integer.MAX_VALUE / b)) || + ((b == -1) && (a == Integer.MIN_VALUE))) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_MULTIPLICATION, a, b); + } + return a * b; + } + + /** Multiply two numbers, detecting overflows. + * @param a first number to multiply + * @param b second number to multiply + * @return a*b if no overflows occur + * @exception MathArithmeticException if an overflow occurs + * @since 3.4 + */ + public static long multiplyExact(final long a, final long b) { + if (((b > 0l) && (a > Long.MAX_VALUE / b || a < Long.MIN_VALUE / b)) || + ((b < -1l) && (a > Long.MIN_VALUE / b || a < Long.MAX_VALUE / b)) || + ((b == -1l) && (a == Long.MIN_VALUE))) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_MULTIPLICATION, a, b); + } + return a * b; + } + + /** Finds q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0. + *

            + * This methods returns the same value as integer division when + * a and b are same signs, but returns a different value when + * they are opposite (i.e. q is negative). + *

            + * @param a dividend + * @param b divisor + * @return q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0 + * @exception MathArithmeticException if b == 0 + * @see #floorMod(int, int) + * @since 3.4 + */ + public static int floorDiv(final int a, final int b) throws MathArithmeticException { + + if (b == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + + final int m = a % b; + if ((a ^ b) >= 0 || m == 0) { + // a an b have same sign, or division is exact + return a / b; + } else { + // a and b have opposite signs and division is not exact + return (a / b) - 1; + } + + } + + /** Finds q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0. + *

            + * This methods returns the same value as integer division when + * a and b are same signs, but returns a different value when + * they are opposite (i.e. q is negative). + *

            + * @param a dividend + * @param b divisor + * @return q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0 + * @exception MathArithmeticException if b == 0 + * @see #floorMod(long, long) + * @since 3.4 + */ + public static long floorDiv(final long a, final long b) throws MathArithmeticException { + + if (b == 0l) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + + final long m = a % b; + if ((a ^ b) >= 0l || m == 0l) { + // a an b have same sign, or division is exact + return a / b; + } else { + // a and b have opposite signs and division is not exact + return (a / b) - 1l; + } + + } + + /** Finds r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0. + *

            + * This methods returns the same value as integer modulo when + * a and b are same signs, but returns a different value when + * they are opposite (i.e. q is negative). + *

            + * @param a dividend + * @param b divisor + * @return r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0 + * @exception MathArithmeticException if b == 0 + * @see #floorDiv(int, int) + * @since 3.4 + */ + public static int floorMod(final int a, final int b) throws MathArithmeticException { + + if (b == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + + final int m = a % b; + if ((a ^ b) >= 0 || m == 0) { + // a an b have same sign, or division is exact + return m; + } else { + // a and b have opposite signs and division is not exact + return b + m; + } + + } + + /** Finds r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0. + *

            + * This methods returns the same value as integer modulo when + * a and b are same signs, but returns a different value when + * they are opposite (i.e. q is negative). + *

            + * @param a dividend + * @param b divisor + * @return r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0 + * @exception MathArithmeticException if b == 0 + * @see #floorDiv(long, long) + * @since 3.4 + */ + public static long floorMod(final long a, final long b) { + + if (b == 0l) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + + final long m = a % b; + if ((a ^ b) >= 0l || m == 0l) { + // a an b have same sign, or division is exact + return m; + } else { + // a and b have opposite signs and division is not exact + return b + m; + } + + } + + /** + * Returns the first argument with the sign of the second argument. + * A NaN {@code sign} argument is treated as positive. + * + * @param magnitude the value to return + * @param sign the sign for the returned value + * @return the magnitude with the same sign as the {@code sign} argument + */ + public static double copySign(double magnitude, double sign){ + // The highest order bit is going to be zero if the + // highest order bit of m and s is the same and one otherwise. + // So (m^s) will be positive if both m and s have the same sign + // and negative otherwise. + final long m = Double.doubleToRawLongBits(magnitude); // don't care about NaN + final long s = Double.doubleToRawLongBits(sign); + if ((m^s) >= 0) { + return magnitude; + } + return -magnitude; // flip sign + } + + /** + * Returns the first argument with the sign of the second argument. + * A NaN {@code sign} argument is treated as positive. + * + * @param magnitude the value to return + * @param sign the sign for the returned value + * @return the magnitude with the same sign as the {@code sign} argument + */ + public static float copySign(float magnitude, float sign){ + // The highest order bit is going to be zero if the + // highest order bit of m and s is the same and one otherwise. + // So (m^s) will be positive if both m and s have the same sign + // and negative otherwise. + final int m = Float.floatToRawIntBits(magnitude); + final int s = Float.floatToRawIntBits(sign); + if ((m^s) >= 0) { + return magnitude; + } + return -magnitude; // flip sign + } + + /** + * Return the exponent of a double number, removing the bias. + *

            + * For double numbers of the form 2x, the unbiased + * exponent is exactly x. + *

            + * @param d number from which exponent is requested + * @return exponent for d in IEEE754 representation, without bias + */ + public static int getExponent(final double d) { + // NaN and Infinite will return 1024 anywho so can use raw bits + return (int) ((Double.doubleToRawLongBits(d) >>> 52) & 0x7ff) - 1023; + } + + /** + * Return the exponent of a float number, removing the bias. + *

            + * For float numbers of the form 2x, the unbiased + * exponent is exactly x. + *

            + * @param f number from which exponent is requested + * @return exponent for d in IEEE754 representation, without bias + */ + public static int getExponent(final float f) { + // NaN and Infinite will return the same exponent anywho so can use raw bits + return ((Float.floatToRawIntBits(f) >>> 23) & 0xff) - 127; + } + + /** + * Print out contents of arrays, and check the length. + *

            used to generate the preset arrays originally.

            + * @param a unused + */ + public static void main(String[] a) { + PrintStream out = System.out; + FastMathCalc.printarray(out, "EXP_INT_TABLE_A", EXP_INT_TABLE_LEN, ExpIntTable.EXP_INT_TABLE_A); + FastMathCalc.printarray(out, "EXP_INT_TABLE_B", EXP_INT_TABLE_LEN, ExpIntTable.EXP_INT_TABLE_B); + FastMathCalc.printarray(out, "EXP_FRAC_TABLE_A", EXP_FRAC_TABLE_LEN, ExpFracTable.EXP_FRAC_TABLE_A); + FastMathCalc.printarray(out, "EXP_FRAC_TABLE_B", EXP_FRAC_TABLE_LEN, ExpFracTable.EXP_FRAC_TABLE_B); + FastMathCalc.printarray(out, "LN_MANT",LN_MANT_LEN, lnMant.LN_MANT); + FastMathCalc.printarray(out, "SINE_TABLE_A", SINE_TABLE_LEN, SINE_TABLE_A); + FastMathCalc.printarray(out, "SINE_TABLE_B", SINE_TABLE_LEN, SINE_TABLE_B); + FastMathCalc.printarray(out, "COSINE_TABLE_A", SINE_TABLE_LEN, COSINE_TABLE_A); + FastMathCalc.printarray(out, "COSINE_TABLE_B", SINE_TABLE_LEN, COSINE_TABLE_B); + FastMathCalc.printarray(out, "TANGENT_TABLE_A", SINE_TABLE_LEN, TANGENT_TABLE_A); + FastMathCalc.printarray(out, "TANGENT_TABLE_B", SINE_TABLE_LEN, TANGENT_TABLE_B); + } + + /** Enclose large data table in nested static class so it's only loaded on first access. */ + private static class ExpIntTable { + /** Exponential evaluated at integer values, + * exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX]. + */ + private static final double[] EXP_INT_TABLE_A; + /** Exponential evaluated at integer values, + * exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX] + */ + private static final double[] EXP_INT_TABLE_B; + + static { + if (RECOMPUTE_TABLES_AT_RUNTIME) { + EXP_INT_TABLE_A = new double[FastMath.EXP_INT_TABLE_LEN]; + EXP_INT_TABLE_B = new double[FastMath.EXP_INT_TABLE_LEN]; + + final double tmp[] = new double[2]; + final double recip[] = new double[2]; + + // Populate expIntTable + for (int i = 0; i < FastMath.EXP_INT_TABLE_MAX_INDEX; i++) { + FastMathCalc.expint(i, tmp); + EXP_INT_TABLE_A[i + FastMath.EXP_INT_TABLE_MAX_INDEX] = tmp[0]; + EXP_INT_TABLE_B[i + FastMath.EXP_INT_TABLE_MAX_INDEX] = tmp[1]; + + if (i != 0) { + // Negative integer powers + FastMathCalc.splitReciprocal(tmp, recip); + EXP_INT_TABLE_A[FastMath.EXP_INT_TABLE_MAX_INDEX - i] = recip[0]; + EXP_INT_TABLE_B[FastMath.EXP_INT_TABLE_MAX_INDEX - i] = recip[1]; + } + } + } else { + EXP_INT_TABLE_A = FastMathLiteralArrays.loadExpIntA(); + EXP_INT_TABLE_B = FastMathLiteralArrays.loadExpIntB(); + } + } + } + + /** Enclose large data table in nested static class so it's only loaded on first access. */ + private static class ExpFracTable { + /** Exponential over the range of 0 - 1 in increments of 2^-10 + * exp(x/1024) = expFracTableA[x] + expFracTableB[x]. + * 1024 = 2^10 + */ + private static final double[] EXP_FRAC_TABLE_A; + /** Exponential over the range of 0 - 1 in increments of 2^-10 + * exp(x/1024) = expFracTableA[x] + expFracTableB[x]. + */ + private static final double[] EXP_FRAC_TABLE_B; + + static { + if (RECOMPUTE_TABLES_AT_RUNTIME) { + EXP_FRAC_TABLE_A = new double[FastMath.EXP_FRAC_TABLE_LEN]; + EXP_FRAC_TABLE_B = new double[FastMath.EXP_FRAC_TABLE_LEN]; + + final double tmp[] = new double[2]; + + // Populate expFracTable + final double factor = 1d / (EXP_FRAC_TABLE_LEN - 1); + for (int i = 0; i < EXP_FRAC_TABLE_A.length; i++) { + FastMathCalc.slowexp(i * factor, tmp); + EXP_FRAC_TABLE_A[i] = tmp[0]; + EXP_FRAC_TABLE_B[i] = tmp[1]; + } + } else { + EXP_FRAC_TABLE_A = FastMathLiteralArrays.loadExpFracA(); + EXP_FRAC_TABLE_B = FastMathLiteralArrays.loadExpFracB(); + } + } + } + + /** Enclose large data table in nested static class so it's only loaded on first access. */ + private static class lnMant { + /** Extended precision logarithm table over the range 1 - 2 in increments of 2^-10. */ + private static final double[][] LN_MANT; + + static { + if (RECOMPUTE_TABLES_AT_RUNTIME) { + LN_MANT = new double[FastMath.LN_MANT_LEN][]; + + // Populate lnMant table + for (int i = 0; i < LN_MANT.length; i++) { + final double d = Double.longBitsToDouble( (((long) i) << 42) | 0x3ff0000000000000L ); + LN_MANT[i] = FastMathCalc.slowLog(d); + } + } else { + LN_MANT = FastMathLiteralArrays.loadLnMant(); + } + } + } + + /** Enclose the Cody/Waite reduction (used in "sin", "cos" and "tan"). */ + private static class CodyWaite { + /** k */ + private final int finalK; + /** remA */ + private final double finalRemA; + /** remB */ + private final double finalRemB; + + /** + * @param xa Argument. + */ + CodyWaite(double xa) { + // Estimate k. + //k = (int)(xa / 1.5707963267948966); + int k = (int)(xa * 0.6366197723675814); + + // Compute remainder. + double remA; + double remB; + while (true) { + double a = -k * 1.570796251296997; + remA = xa + a; + remB = -(remA - xa - a); + + a = -k * 7.549789948768648E-8; + double b = remA; + remA = a + b; + remB += -(remA - b - a); + + a = -k * 6.123233995736766E-17; + b = remA; + remA = a + b; + remB += -(remA - b - a); + + if (remA > 0) { + break; + } + + // Remainder is negative, so decrement k and try again. + // This should only happen if the input is very close + // to an even multiple of pi/2. + --k; + } + + this.finalK = k; + this.finalRemA = remA; + this.finalRemB = remB; + } + + /** + * @return k + */ + int getK() { + return finalK; + } + /** + * @return remA + */ + double getRemA() { + return finalRemA; + } + /** + * @return remB + */ + double getRemB() { + return finalRemB; + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathCalc.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathCalc.java new file mode 100644 index 000000000..b79747bc9 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathCalc.java @@ -0,0 +1,658 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.io.PrintStream; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; + +/** Class used to compute the classical functions tables. + * @since 3.0 + */ +class FastMathCalc { + + /** + * 0x40000000 - used to split a double into two parts, both with the low order bits cleared. + * Equivalent to 2^30. + */ + private static final long HEX_40000000 = 0x40000000L; // 1073741824L + + /** Factorial table, for Taylor series expansions. 0!, 1!, 2!, ... 19! */ + private static final double FACT[] = new double[] + { + +1.0d, // 0 + +1.0d, // 1 + +2.0d, // 2 + +6.0d, // 3 + +24.0d, // 4 + +120.0d, // 5 + +720.0d, // 6 + +5040.0d, // 7 + +40320.0d, // 8 + +362880.0d, // 9 + +3628800.0d, // 10 + +39916800.0d, // 11 + +479001600.0d, // 12 + +6227020800.0d, // 13 + +87178291200.0d, // 14 + +1307674368000.0d, // 15 + +20922789888000.0d, // 16 + +355687428096000.0d, // 17 + +6402373705728000.0d, // 18 + +121645100408832000.0d, // 19 + }; + + /** Coefficients for slowLog. */ + private static final double LN_SPLIT_COEF[][] = { + {2.0, 0.0}, + {0.6666666269302368, 3.9736429850260626E-8}, + {0.3999999761581421, 2.3841857910019882E-8}, + {0.2857142686843872, 1.7029898543501842E-8}, + {0.2222222089767456, 1.3245471311735498E-8}, + {0.1818181574344635, 2.4384203044354907E-8}, + {0.1538461446762085, 9.140260083262505E-9}, + {0.13333332538604736, 9.220590270857665E-9}, + {0.11764700710773468, 1.2393345855018391E-8}, + {0.10526403784751892, 8.251545029714408E-9}, + {0.0952233225107193, 1.2675934823758863E-8}, + {0.08713622391223907, 1.1430250008909141E-8}, + {0.07842259109020233, 2.404307984052299E-9}, + {0.08371849358081818, 1.176342548272881E-8}, + {0.030589580535888672, 1.2958646899018938E-9}, + {0.14982303977012634, 1.225743062930824E-8}, + }; + + /** Table start declaration. */ + private static final String TABLE_START_DECL = " {"; + + /** Table end declaration. */ + private static final String TABLE_END_DECL = " };"; + + /** + * Private Constructor. + */ + private FastMathCalc() { + } + + /** Build the sine and cosine tables. + * @param SINE_TABLE_A table of the most significant part of the sines + * @param SINE_TABLE_B table of the least significant part of the sines + * @param COSINE_TABLE_A table of the most significant part of the cosines + * @param COSINE_TABLE_B table of the most significant part of the cosines + * @param SINE_TABLE_LEN length of the tables + * @param TANGENT_TABLE_A table of the most significant part of the tangents + * @param TANGENT_TABLE_B table of the most significant part of the tangents + */ + @SuppressWarnings("unused") + private static void buildSinCosTables(double[] SINE_TABLE_A, double[] SINE_TABLE_B, + double[] COSINE_TABLE_A, double[] COSINE_TABLE_B, + int SINE_TABLE_LEN, double[] TANGENT_TABLE_A, double[] TANGENT_TABLE_B) { + final double result[] = new double[2]; + + /* Use taylor series for 0 <= x <= 6/8 */ + for (int i = 0; i < 7; i++) { + double x = i / 8.0; + + slowSin(x, result); + SINE_TABLE_A[i] = result[0]; + SINE_TABLE_B[i] = result[1]; + + slowCos(x, result); + COSINE_TABLE_A[i] = result[0]; + COSINE_TABLE_B[i] = result[1]; + } + + /* Use angle addition formula to complete table to 13/8, just beyond pi/2 */ + for (int i = 7; i < SINE_TABLE_LEN; i++) { + double xs[] = new double[2]; + double ys[] = new double[2]; + double as[] = new double[2]; + double bs[] = new double[2]; + double temps[] = new double[2]; + + if ( (i & 1) == 0) { + // Even, use double angle + xs[0] = SINE_TABLE_A[i/2]; + xs[1] = SINE_TABLE_B[i/2]; + ys[0] = COSINE_TABLE_A[i/2]; + ys[1] = COSINE_TABLE_B[i/2]; + + /* compute sine */ + splitMult(xs, ys, result); + SINE_TABLE_A[i] = result[0] * 2.0; + SINE_TABLE_B[i] = result[1] * 2.0; + + /* Compute cosine */ + splitMult(ys, ys, as); + splitMult(xs, xs, temps); + temps[0] = -temps[0]; + temps[1] = -temps[1]; + splitAdd(as, temps, result); + COSINE_TABLE_A[i] = result[0]; + COSINE_TABLE_B[i] = result[1]; + } else { + xs[0] = SINE_TABLE_A[i/2]; + xs[1] = SINE_TABLE_B[i/2]; + ys[0] = COSINE_TABLE_A[i/2]; + ys[1] = COSINE_TABLE_B[i/2]; + as[0] = SINE_TABLE_A[i/2+1]; + as[1] = SINE_TABLE_B[i/2+1]; + bs[0] = COSINE_TABLE_A[i/2+1]; + bs[1] = COSINE_TABLE_B[i/2+1]; + + /* compute sine */ + splitMult(xs, bs, temps); + splitMult(ys, as, result); + splitAdd(result, temps, result); + SINE_TABLE_A[i] = result[0]; + SINE_TABLE_B[i] = result[1]; + + /* Compute cosine */ + splitMult(ys, bs, result); + splitMult(xs, as, temps); + temps[0] = -temps[0]; + temps[1] = -temps[1]; + splitAdd(result, temps, result); + COSINE_TABLE_A[i] = result[0]; + COSINE_TABLE_B[i] = result[1]; + } + } + + /* Compute tangent = sine/cosine */ + for (int i = 0; i < SINE_TABLE_LEN; i++) { + double xs[] = new double[2]; + double ys[] = new double[2]; + double as[] = new double[2]; + + as[0] = COSINE_TABLE_A[i]; + as[1] = COSINE_TABLE_B[i]; + + splitReciprocal(as, ys); + + xs[0] = SINE_TABLE_A[i]; + xs[1] = SINE_TABLE_B[i]; + + splitMult(xs, ys, as); + + TANGENT_TABLE_A[i] = as[0]; + TANGENT_TABLE_B[i] = as[1]; + } + + } + + /** + * For x between 0 and pi/4 compute cosine using Talor series + * cos(x) = 1 - x^2/2! + x^4/4! ... + * @param x number from which cosine is requested + * @param result placeholder where to put the result in extended precision + * (may be null) + * @return cos(x) + */ + static double slowCos(final double x, final double result[]) { + + final double xs[] = new double[2]; + final double ys[] = new double[2]; + final double facts[] = new double[2]; + final double as[] = new double[2]; + split(x, xs); + ys[0] = ys[1] = 0.0; + + for (int i = FACT.length-1; i >= 0; i--) { + splitMult(xs, ys, as); + ys[0] = as[0]; ys[1] = as[1]; + + if ( (i & 1) != 0) { // skip odd entries + continue; + } + + split(FACT[i], as); + splitReciprocal(as, facts); + + if ( (i & 2) != 0 ) { // alternate terms are negative + facts[0] = -facts[0]; + facts[1] = -facts[1]; + } + + splitAdd(ys, facts, as); + ys[0] = as[0]; ys[1] = as[1]; + } + + if (result != null) { + result[0] = ys[0]; + result[1] = ys[1]; + } + + return ys[0] + ys[1]; + } + + /** + * For x between 0 and pi/4 compute sine using Taylor expansion: + * sin(x) = x - x^3/3! + x^5/5! - x^7/7! ... + * @param x number from which sine is requested + * @param result placeholder where to put the result in extended precision + * (may be null) + * @return sin(x) + */ + static double slowSin(final double x, final double result[]) { + final double xs[] = new double[2]; + final double ys[] = new double[2]; + final double facts[] = new double[2]; + final double as[] = new double[2]; + split(x, xs); + ys[0] = ys[1] = 0.0; + + for (int i = FACT.length-1; i >= 0; i--) { + splitMult(xs, ys, as); + ys[0] = as[0]; ys[1] = as[1]; + + if ( (i & 1) == 0) { // Ignore even numbers + continue; + } + + split(FACT[i], as); + splitReciprocal(as, facts); + + if ( (i & 2) != 0 ) { // alternate terms are negative + facts[0] = -facts[0]; + facts[1] = -facts[1]; + } + + splitAdd(ys, facts, as); + ys[0] = as[0]; ys[1] = as[1]; + } + + if (result != null) { + result[0] = ys[0]; + result[1] = ys[1]; + } + + return ys[0] + ys[1]; + } + + + /** + * For x between 0 and 1, returns exp(x), uses extended precision + * @param x argument of exponential + * @param result placeholder where to place exp(x) split in two terms + * for extra precision (i.e. exp(x) = result[0] + result[1] + * @return exp(x) + */ + static double slowexp(final double x, final double result[]) { + final double xs[] = new double[2]; + final double ys[] = new double[2]; + final double facts[] = new double[2]; + final double as[] = new double[2]; + split(x, xs); + ys[0] = ys[1] = 0.0; + + for (int i = FACT.length-1; i >= 0; i--) { + splitMult(xs, ys, as); + ys[0] = as[0]; + ys[1] = as[1]; + + split(FACT[i], as); + splitReciprocal(as, facts); + + splitAdd(ys, facts, as); + ys[0] = as[0]; + ys[1] = as[1]; + } + + if (result != null) { + result[0] = ys[0]; + result[1] = ys[1]; + } + + return ys[0] + ys[1]; + } + + /** Compute split[0], split[1] such that their sum is equal to d, + * and split[0] has its 30 least significant bits as zero. + * @param d number to split + * @param split placeholder where to place the result + */ + private static void split(final double d, final double split[]) { + if (d < 8e298 && d > -8e298) { + final double a = d * HEX_40000000; + split[0] = (d + a) - a; + split[1] = d - split[0]; + } else { + final double a = d * 9.31322574615478515625E-10; + split[0] = (d + a - d) * HEX_40000000; + split[1] = d - split[0]; + } + } + + /** Recompute a split. + * @param a input/out array containing the split, changed + * on output + */ + private static void resplit(final double a[]) { + final double c = a[0] + a[1]; + final double d = -(c - a[0] - a[1]); + + if (c < 8e298 && c > -8e298) { // MAGIC NUMBER + double z = c * HEX_40000000; + a[0] = (c + z) - z; + a[1] = c - a[0] + d; + } else { + double z = c * 9.31322574615478515625E-10; + a[0] = (c + z - c) * HEX_40000000; + a[1] = c - a[0] + d; + } + } + + /** Multiply two numbers in split form. + * @param a first term of multiplication + * @param b second term of multiplication + * @param ans placeholder where to put the result + */ + private static void splitMult(double a[], double b[], double ans[]) { + ans[0] = a[0] * b[0]; + ans[1] = a[0] * b[1] + a[1] * b[0] + a[1] * b[1]; + + /* Resplit */ + resplit(ans); + } + + /** Add two numbers in split form. + * @param a first term of addition + * @param b second term of addition + * @param ans placeholder where to put the result + */ + private static void splitAdd(final double a[], final double b[], final double ans[]) { + ans[0] = a[0] + b[0]; + ans[1] = a[1] + b[1]; + + resplit(ans); + } + + /** Compute the reciprocal of in. Use the following algorithm. + * in = c + d. + * want to find x + y such that x+y = 1/(c+d) and x is much + * larger than y and x has several zero bits on the right. + * + * 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 + */ + static void splitReciprocal(final double in[], final double result[]) { + final double b = 1.0/4194304.0; + final double a = 1.0 - b; + + if (in[0] == 0.0) { + in[0] = in[1]; + in[1] = 0.0; + } + + result[0] = a / in[0]; + result[1] = (b*in[0]-a*in[1]) / (in[0]*in[0] + in[0]*in[1]); + + if (result[1] != result[1]) { // can happen if result[1] is NAN + result[1] = 0.0; + } + + /* Resplit */ + resplit(result); + + for (int i = 0; i < 2; i++) { + /* this may be overkill, probably once is enough */ + double err = 1.0 - result[0] * in[0] - result[0] * in[1] - + result[1] * in[0] - result[1] * in[1]; + /*err = 1.0 - err; */ + err *= result[0] + result[1]; + /*printf("err = %16e\n", err); */ + result[1] += err; + } + } + + /** Compute (a[0] + a[1]) * (b[0] + b[1]) in extended precision. + * @param a first term of the multiplication + * @param b second term of the multiplication + * @param result placeholder where to put the result + */ + private static void quadMult(final double a[], final double b[], final double result[]) { + final double xs[] = new double[2]; + final double ys[] = new double[2]; + final double zs[] = new double[2]; + + /* a[0] * b[0] */ + split(a[0], xs); + split(b[0], ys); + splitMult(xs, ys, zs); + + result[0] = zs[0]; + result[1] = zs[1]; + + /* a[0] * b[1] */ + split(b[1], ys); + splitMult(xs, ys, zs); + + double tmp = result[0] + zs[0]; + result[1] -= tmp - result[0] - zs[0]; + result[0] = tmp; + tmp = result[0] + zs[1]; + result[1] -= tmp - result[0] - zs[1]; + result[0] = tmp; + + /* a[1] * b[0] */ + split(a[1], xs); + split(b[0], ys); + splitMult(xs, ys, zs); + + tmp = result[0] + zs[0]; + result[1] -= tmp - result[0] - zs[0]; + result[0] = tmp; + tmp = result[0] + zs[1]; + result[1] -= tmp - result[0] - zs[1]; + result[0] = tmp; + + /* a[1] * b[0] */ + split(a[1], xs); + split(b[1], ys); + splitMult(xs, ys, zs); + + tmp = result[0] + zs[0]; + result[1] -= tmp - result[0] - zs[0]; + result[0] = tmp; + tmp = result[0] + zs[1]; + result[1] -= tmp - result[0] - zs[1]; + result[0] = tmp; + } + + /** Compute exp(p) for a integer p in extended precision. + * @param p integer whose exponential is requested + * @param result placeholder where to put the result in extended precision + * @return exp(p) in standard precision (equal to result[0] + result[1]) + */ + static double expint(int p, final double result[]) { + //double x = M_E; + final double xs[] = new double[2]; + final double as[] = new double[2]; + final double ys[] = new double[2]; + //split(x, xs); + //xs[1] = (double)(2.7182818284590452353602874713526625L - xs[0]); + //xs[0] = 2.71827697753906250000; + //xs[1] = 4.85091998273542816811e-06; + //xs[0] = Double.longBitsToDouble(0x4005bf0800000000L); + //xs[1] = Double.longBitsToDouble(0x3ed458a2bb4a9b00L); + + /* E */ + xs[0] = 2.718281828459045; + xs[1] = 1.4456468917292502E-16; + + split(1.0, ys); + + while (p > 0) { + if ((p & 1) != 0) { + quadMult(ys, xs, as); + ys[0] = as[0]; ys[1] = as[1]; + } + + quadMult(xs, xs, as); + xs[0] = as[0]; xs[1] = as[1]; + + p >>= 1; + } + + if (result != null) { + result[0] = ys[0]; + result[1] = ys[1]; + + resplit(result); + } + + return ys[0] + ys[1]; + } + /** xi in the range of [1, 2]. + * 3 5 7 + * x+1 / x x x \ + * ln ----- = 2 * | x + ---- + ---- + ---- + ... | + * 1-x \ 3 5 7 / + * + * 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) + */ + static double[] slowLog(double xi) { + double x[] = new double[2]; + double x2[] = new double[2]; + double y[] = new double[2]; + double a[] = new double[2]; + + split(xi, x); + + /* Set X = (x-1)/(x+1) */ + x[0] += 1.0; + resplit(x); + splitReciprocal(x, a); + x[0] -= 2.0; + resplit(x); + splitMult(x, a, y); + x[0] = y[0]; + x[1] = y[1]; + + /* Square X -> X2*/ + splitMult(x, x, x2); + + + //x[0] -= 1.0; + //resplit(x); + + y[0] = LN_SPLIT_COEF[LN_SPLIT_COEF.length-1][0]; + y[1] = LN_SPLIT_COEF[LN_SPLIT_COEF.length-1][1]; + + for (int i = LN_SPLIT_COEF.length-2; i >= 0; i--) { + splitMult(y, x2, a); + y[0] = a[0]; + y[1] = a[1]; + splitAdd(y, LN_SPLIT_COEF[i], a); + y[0] = a[0]; + y[1] = a[1]; + } + + splitMult(y, x, a); + y[0] = a[0]; + y[1] = a[1]; + + return y; + } + + + /** + * Print an array. + * @param out text output stream where output should be printed + * @param name array name + * @param expectedLen expected length of the array + * @param array2d array data + */ + static void printarray(PrintStream out, String name, int expectedLen, double[][] array2d) { + out.println(name); + checkLen(expectedLen, array2d.length); + out.println(TABLE_START_DECL + " "); + int i = 0; + for(double[] array : array2d) { // "double array[]" causes PMD parsing error + out.print(" {"); + for(double d : array) { // assume inner array has very few entries + out.printf("%-25.25s", format(d)); // multiple entries per line + } + out.println("}, // " + i++); + } + out.println(TABLE_END_DECL); + } + + /** + * Print an array. + * @param out text output stream where output should be printed + * @param name array name + * @param expectedLen expected length of the array + * @param array array data + */ + static void printarray(PrintStream out, String name, int expectedLen, double[] array) { + out.println(name + "="); + checkLen(expectedLen, array.length); + out.println(TABLE_START_DECL); + for(double d : array){ + out.printf(" %s%n", format(d)); // one entry per line + } + out.println(TABLE_END_DECL); + } + + /** Format a double. + * @param d double number to format + * @return formatted number + */ + static String format(double d) { + if (d != d) { + return "Double.NaN,"; + } else { + return ((d >= 0) ? "+" : "") + Double.toString(d) + "d,"; + } + } + + /** + * Check two lengths are equal. + * @param expectedLen expected length + * @param actual actual length + * @exception DimensionMismatchException if the two lengths are not equal + */ + private static void checkLen(int expectedLen, int actual) + throws DimensionMismatchException { + if (expectedLen != actual) { + throw new DimensionMismatchException(actual, expectedLen); + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathLiteralArrays.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathLiteralArrays.java new file mode 100644 index 000000000..74423192b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/FastMathLiteralArrays.java @@ -0,0 +1,6175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.util; + +/** + * Utility class for loading tabulated data used by {@link FastMath}. + * + */ +class FastMathLiteralArrays { + /** Exponential evaluated at integer values, + * exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX]. + */ + private static final double[] EXP_INT_A = new double[] { + +0.0d, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + +1.2167807682331913E-308d, + +3.3075532478807267E-308d, + +8.990862214387203E-308d, + +2.4439696075216986E-307d, + +6.64339758024534E-307d, + +1.8058628951432254E-306d, + +4.908843759498681E-306d, + +1.334362017065677E-305d, + +3.627172425759641E-305d, + +9.85967600992008E-305d, + +2.680137967689915E-304d, + +7.285370725133842E-304d, + +1.9803689272433392E-303d, + +5.3832011494782624E-303d, + +1.463305638201413E-302d, + +3.9776772027043775E-302d, + +1.0812448255518705E-301d, + +2.9391280956327795E-301d, + +7.989378677301346E-301d, + +2.1717383041010577E-300d, + +5.903396499766243E-300d, + +1.604709595901607E-299d, + +4.3620527352131126E-299d, + +1.1857289715706991E-298d, + +3.2231452986239366E-298d, + +8.761416875971053E-298d, + +2.381600167287677E-297d, + +6.473860152384321E-297d, + +1.7597776278732318E-296d, + +4.7835721669653157E-296d, + +1.3003096668152053E-295d, + +3.5346080979652066E-295d, + +9.608060944124859E-295d, + +2.6117415961302846E-294d, + +7.099449830809996E-294d, + +1.9298305829106006E-293d, + +5.245823134132673E-293d, + +1.4259627797225802E-292d, + +3.8761686729764145E-292d, + +1.0536518897078156E-291d, + +2.864122672853628E-291d, + +7.785491934690374E-291d, + +2.116316283183901E-290d, + +5.7527436249968E-290d, + +1.5637579898345352E-289d, + +4.250734424415339E-289d, + +1.1554696041977512E-288d, + +3.1408919441362495E-288d, + +8.537829238438662E-288d, + +2.320822576772103E-287d, + +6.308649765138419E-287d, + +1.7148689119310826E-286d, + +4.66149719271323E-286d, + +1.267126226441217E-285d, + +3.444406231880653E-285d, + +9.362866914115166E-285d, + +2.5450911557068313E-284d, + +6.918275021321188E-284d, + +1.880582039589629E-283d, + +5.111952261540649E-283d, + +1.3895726688907995E-282d, + +3.7772500667438066E-282d, + +1.026763015362553E-281d, + +2.791031173360063E-281d, + +7.586808748646825E-281d, + +2.0623086887184633E-280d, + +5.605936171588964E-280d, + +1.5238514098804918E-279d, + +4.1422578754033235E-279d, + +1.1259823210174452E-278d, + +3.060737220976933E-278d, + +8.319947089683576E-278d, + +2.2615958035357106E-277d, + +6.147655179898435E-277d, + +1.6711060014400145E-276d, + +4.542536646012133E-276d, + +1.2347896500246374E-275d, + +3.3565057475434694E-275d, + +9.123929070778758E-275d, + +2.4801413921885483E-274d, + +6.741722283079056E-274d, + +1.8325902719086093E-273d, + +4.981496462621207E-273d, + +1.3541112064618357E-272d, + +3.68085620656127E-272d, + +1.0005602916630382E-271d, + +2.719805132368625E-271d, + +7.393196131284108E-271d, + +2.0096791226867E-270d, + +5.462874707256208E-270d, + +1.4849631831943512E-269d, + +4.036548930895323E-269d, + +1.0972476870931676E-268d, + +2.9826282194717127E-268d, + +8.107624153838987E-268d, + +2.2038806519542315E-267d, + +5.990769236615968E-267d, + +1.628459873440512E-266d, + +4.4266130556431266E-266d, + +1.203278237867575E-265d, + +3.270849446965521E-265d, + +8.891090288030614E-265d, + +2.4168487931443637E-264d, + +6.569676185250389E-264d, + +1.7858231429575898E-263d, + +4.85437090269903E-263d, + +1.3195548295785448E-262d, + +3.5869215528816054E-262d, + +9.750264097807267E-262d, + +2.650396454019762E-261d, + +7.204525142098426E-261d, + +1.958392846081373E-260d, + +5.32346341339996E-260d, + +1.4470673509275515E-259d, + +3.9335373658569176E-259d, + +1.0692462289051038E-258d, + +2.9065128598079075E-258d, + +7.900720862969045E-258d, + +2.147638465376883E-257d, + +5.8378869339035456E-257d, + +1.5869022483809747E-256d, + +4.3136475849391444E-256d, + +1.1725710340687719E-255d, + +3.1873780814410126E-255d, + +8.66419234315257E-255d, + +2.35517168886351E-254d, + +6.402020300783889E-254d, + +1.740249660600677E-253d, + +4.7304887145310405E-253d, + +1.2858802448614707E-252d, + +3.495384792953975E-252d, + +9.501439740542955E-252d, + +2.582759362004277E-251d, + +7.020668578160457E-251d, + +1.908415302517694E-250d, + +5.1876107490791666E-250d, + +1.4101386971763257E-249d, + +3.8331545111676784E-249d, + +1.0419594359065132E-248d, + +2.8323395451363237E-248d, + +7.699097067385825E-248d, + +2.0928317096428755E-247d, + +5.688906371296133E-247d, + +1.5464049837965422E-246d, + +4.2035646586788297E-246d, + +1.1426473877336358E-245d, + +3.106037603716254E-245d, + +8.443084996839363E-245d, + +2.2950686306677644E-244d, + +6.238642390386363E-244d, + +1.695838923802857E-243d, + +4.6097680405580995E-243d, + +1.2530649392922358E-242d, + +3.4061835424180075E-242d, + +9.25896798127602E-242d, + +2.5168480541429286E-241d, + +6.841502859109196E-241d, + +1.8597132378953187E-240d, + +5.055224959032211E-240d, + +1.374152583940637E-239d, + +3.735333866258403E-239d, + +1.0153690688015855E-238d, + +2.7600590782738726E-238d, + +7.502618487550056E-238d, + +2.0394233446495043E-237d, + +5.543727690168612E-237d, + +1.5069412868172555E-236d, + +4.0962906236847E-236d, + +1.1134873918971586E-235d, + +3.026772467749944E-235d, + +8.227620163729258E-235d, + +2.2364990583200056E-234d, + +6.079434951446575E-234d, + +1.6525617499662284E-233d, + +4.4921289690525345E-233d, + +1.2210872189854344E-232d, + +3.3192593301633E-232d, + +9.02268127425393E-232d, + +2.4526190464373087E-231d, + +6.666909874218774E-231d, + +1.8122539547625083E-230d, + +4.926216840507529E-230d, + +1.3390847149416908E-229d, + +3.6400093808551196E-229d, + +9.894571625944288E-229d, + +2.689623698321582E-228d, + +7.31115423069187E-228d, + +1.9873779569310022E-227d, + +5.402252865260326E-227d, + +1.4684846983789053E-226d, + +3.991755413823315E-226d, + +1.0850715739509136E-225d, + +2.9495302004590423E-225d, + +8.017654713159388E-225d, + +2.179424521221378E-224d, + +5.924290380648597E-224d, + +1.6103890140790331E-223d, + +4.377491272857675E-223d, + +1.1899254154663847E-222d, + +3.2345523990372546E-222d, + +8.792425221770645E-222d, + +2.3900289095512176E-221d, + +6.496772856703278E-221d, + +1.7660059778220905E-220d, + +4.800501435803201E-220d, + +1.3049116216750674E-219d, + +3.5471180281159325E-219d, + +9.642065709892252E-219d, + +2.6209850274990846E-218d, + +7.124574366530717E-218d, + +1.9366601417010147E-217d, + +5.264388476949737E-217d, + +1.431009021985696E-216d, + +3.889885799962507E-216d, + +1.057380684430436E-215d, + +2.8742587656021775E-215d, + +7.813044552050569E-215d, + +2.1238058974550874E-214d, + +5.773102661099307E-214d, + +1.5692921723471877E-213d, + +4.2657777816050375E-213d, + +1.1595585743839232E-212d, + +3.1520070828798975E-212d, + +8.568043768122183E-212d, + +2.329035966595791E-211d, + +6.33097561889469E-211d, + +1.720937714565362E-210d, + +4.677993239821998E-210d, + +1.2716105485691878E-209d, + +3.456595573934475E-209d, + +9.396000024637834E-209d, + +2.55409795397022E-208d, + +6.942757623821567E-208d, + +1.887237361505784E-207d, + +5.13004286606108E-207d, + +1.3944901709366118E-206d, + +3.7906173667738715E-206d, + +1.0303966192973381E-205d, + +2.8009086220877197E-205d, + +7.613657850210907E-205d, + +2.0696069842597556E-204d, + +5.6257755605305175E-204d, + +1.5292444435954893E-203d, + +4.156916476922876E-203d, + +1.12996721591364E-202d, + +3.071569248856111E-202d, + +8.349390727162016E-202d, + +2.2695999828608633E-201d, + +6.1694117899971836E-201d, + +1.677020107827128E-200d, + +4.558612479525779E-200d, + +1.2391595516612638E-199d, + +3.3683846288580648E-199d, + +9.156218120779494E-199d, + +2.4889182184335247E-198d, + +6.765580431441772E-198d, + +1.839075686473352E-197d, + +4.999126524757713E-197d, + +1.3589033107846643E-196d, + +3.6938826366068014E-196d, + +1.0041012794280992E-195d, + +2.7294301888986675E-195d, + +7.419361045185406E-195d, + +2.016791373353671E-194d, + +5.482208065983983E-194d, + +1.490218341008089E-193d, + +4.050833763855709E-193d, + +1.101130773265179E-192d, + +2.993183789477209E-192d, + +8.136316299122392E-192d, + +2.2116799789922265E-191d, + +6.011969568315371E-191d, + +1.6342228966392253E-190d, + +4.4422779589171113E-190d, + +1.2075364784547675E-189d, + +3.282424571107068E-189d, + +8.92255448602772E-189d, + +2.425402115319395E-188d, + +6.592926904915355E-188d, + +1.79214305133496E-187d, + +4.871550528055661E-187d, + +1.3242245776666673E-186d, + +3.599615946028287E-186d, + +9.78476998200719E-186d, + +2.659776075359514E-185d, + +7.230020851688713E-185d, + +1.9653234116333892E-184d, + +5.34230278107224E-184d, + +1.4521887058451231E-183d, + +3.947457923821984E-183d, + +1.0730302255093144E-182d, + +2.9167986204137332E-182d, + +7.928680793406766E-182d, + +2.1552386987482013E-181d, + +5.858546779607288E-181d, + +1.5925182066949723E-180d, + +4.328913614497258E-180d, + +1.1767205227552116E-179d, + +3.198658219194836E-179d, + +8.694853785564504E-179d, + +2.363506255864984E-178d, + +6.42467573615509E-178d, + +1.746408207555959E-177d, + +4.747229597770176E-177d, + +1.2904307529671472E-176d, + +3.507754341050756E-176d, + +9.535066345267336E-176d, + +2.591899541396432E-175d, + +7.045512786902009E-175d, + +1.9151693415969248E-174d, + +5.205969622575851E-174d, + +1.4151292367806538E-173d, + +3.846720258072078E-173d, + +1.045647032279984E-172d, + +2.8423629805010285E-172d, + +7.726344058192276E-172d, + +2.1002377128928765E-171d, + +5.709039546124285E-171d, + +1.5518778128928824E-170d, + +4.218440703602533E-170d, + +1.1466910691560932E-169d, + +3.1170298734336303E-169d, + +8.472965161251656E-169d, + +2.303190374523956E-168d, + +6.260720440258473E-168d, + +1.701840523821621E-167d, + +4.62608152166211E-167d, + +1.2574995962791943E-166d, + +3.418237608335161E-166d, + +9.29173407843235E-166d, + +2.5257552661512635E-165d, + +6.865714679174435E-165d, + +1.866294830116931E-164d, + +5.073114566291778E-164d, + +1.3790154522394582E-163d, + +3.7485528226129495E-163d, + +1.0189624503698769E-162d, + +2.7698267293941856E-162d, + +7.529170882336924E-162d, + +2.0466404088178596E-161d, + +5.56334611651382E-161d, + +1.512274346576166E-160d, + +4.110787043867721E-160d, + +1.1174279267498045E-159d, + +3.0374839443564585E-159d, + +8.25673801176584E-159d, + +2.244414150254963E-158d, + +6.1009492034592176E-158d, + +1.6584100275603453E-157d, + +4.50802633729044E-157d, + +1.2254085656601853E-156d, + +3.3310057014599044E-156d, + +9.054612259832416E-156d, + +2.4612985502035675E-155d, + +6.690503835950083E-155d, + +1.8186679660152888E-154d, + +4.9436516047443576E-154d, + +1.3438240331106108E-153d, + +3.652892398145774E-153d, + +9.92958982547828E-153d, + +2.6991427376823027E-152d, + +7.3370297995122135E-152d, + +1.994411660450821E-151d, + +5.421372463189529E-151d, + +1.4736818914204564E-150d, + +4.005882964287806E-150d, + +1.088911919926534E-149d, + +2.9599693109692324E-149d, + +8.046030012041041E-149d, + +2.18713790898745E-148d, + +5.945256705384597E-148d, + +1.6160884846515524E-147d, + +4.392983574030969E-147d, + +1.1941366764543551E-146d, + +3.2460001983475855E-146d, + +8.8235440586675E-146d, + +2.3984878190403553E-145d, + +6.519765758635405E-145d, + +1.772256261139753E-144d, + +4.817491674217065E-144d, + +1.3095299991573769E-143d, + +3.559671483107555E-143d, + +9.676190774054103E-143d, + +2.630261301303634E-142d, + +7.149792225695347E-142d, + +1.943514969662872E-141d, + +5.283020542151163E-141d, + +1.4360739330834996E-140d, + +3.9036541111764032E-140d, + +1.0611230602364477E-139d, + +2.8844319473099593E-139d, + +7.84069876400596E-139d, + +2.1313228444765414E-138d, + +5.793536445518422E-138d, + +1.5748463788034308E-137d, + +4.2808762411845363E-137d, + +1.1636629220608724E-136d, + +3.163163464591171E-136d, + +8.598369704466743E-136d, + +2.337279322276433E-135d, + +6.353384093665193E-135d, + +1.7270287031459572E-134d, + +4.694550492773212E-134d, + +1.2761111606368036E-133d, + +3.4688299108856403E-133d, + +9.429257929713919E-133d, + +2.5631381141873417E-132d, + +6.967331001069377E-132d, + +1.8939170679975288E-131d, + +5.148199748336684E-131d, + +1.3994258162094293E-130d, + +3.804034213613942E-130d, + +1.0340436948077763E-129d, + +2.8108219632627907E-129d, + +7.640606938467665E-129d, + +2.0769322678328357E-128d, + +5.645687086879944E-128d, + +1.5346568127351796E-127d, + +4.171630237420918E-127d, + +1.1339665711932977E-126d, + +3.0824406750909563E-126d, + +8.37894218404787E-126d, + +2.2776327994966818E-125d, + +6.191247522703296E-125d, + +1.6829556040859853E-124d, + +4.5747479502862494E-124d, + +1.2435453481209945E-123d, + +3.3803067202247166E-123d, + +9.188625696750548E-123d, + +2.4977273040076145E-122d, + +6.789527378582775E-122d, + +1.845584943222965E-121d, + +5.016820182185716E-121d, + +1.3637129731022491E-120d, + +3.706956710275979E-120d, + +1.0076552294433743E-119d, + +2.739090595934893E-119d, + +7.445620503219039E-119d, + +2.023929422267303E-118d, + +5.501611507503037E-118d, + +1.4954928881576769E-117d, + +4.0651709187617596E-117d, + +1.1050280679513555E-116d, + +3.003777734030334E-116d, + +8.165114384910189E-116d, + +2.219508285637377E-115d, + +6.033249389304709E-115d, + +1.6400070480930697E-114d, + +4.458001565878111E-114d, + +1.2118105325725891E-113d, + +3.2940421731384895E-113d, + +8.954135150208654E-113d, + +2.433986351722258E-112d, + +6.616260705434716E-112d, + +1.7984863104885375E-111d, + +4.888792154132158E-111d, + +1.3289115531074511E-110d, + +3.612356038181234E-110d, + +9.819402293160495E-110d, + +2.6691899766673256E-109d, + +7.255611264437603E-109d, + +1.9722796756250217E-108d, + +5.361211684173837E-108d, + +1.4573285967670963E-107d, + +3.961429477016909E-107d, + +1.0768281419102595E-106d, + +2.9271223293841774E-106d, + +7.956744351476403E-106d, + +2.1628672925745152E-105d, + +5.879282834821692E-105d, + +1.5981547034872092E-104d, + +4.344234755347641E-104d, + +1.1808855501885005E-103d, + +3.2099795870407646E-103d, + +8.725629524586503E-103d, + +2.3718718327094683E-102d, + +6.44741641521183E-102d, + +1.7525895549820557E-101d, + +4.7640323331013947E-101d, + +1.2949980563724296E-100d, + +3.5201699899499525E-100d, + +9.56881327374431E-100d, + +2.6010732940533088E-99d, + +7.070450309820548E-99d, + +1.9219478787856753E-98d, + +5.2243955659975294E-98d, + +1.4201378353978042E-97d, + +3.8603349913851996E-97d, + +1.0493479260117497E-96d, + +2.8524232604238555E-96d, + +7.753690709912764E-96d, + +2.1076716069929933E-95d, + +5.72924572981599E-95d, + +1.5573703263204683E-94d, + +4.233371554108682E-94d, + +1.1507496472539512E-93d, + +3.1280620563875923E-93d, + +8.5029538631631E-93d, + +2.3113425190436427E-92d, + +6.28287989314225E-92d, + +1.7078641226055994E-91d, + +4.6424556110307644E-91d, + +1.261950308999819E-90d, + +3.430336362898836E-90d, + +9.324622137237299E-90d, + +2.5346947846365435E-89d, + +6.890014851450124E-89d, + +1.8729003560057785E-88d, + +5.091070300111434E-88d, + +1.3838964592430477E-87d, + +3.761820584522275E-87d, + +1.0225689628581036E-86d, + +2.7796303536272215E-86d, + +7.555818934379333E-86d, + +2.053884626293416E-85d, + +5.583037134407759E-85d, + +1.5176268538776042E-84d, + +4.125337057189083E-84d, + +1.121383042095528E-83d, + +3.0482348236054953E-83d, + +8.285962249116636E-83d, + +2.2523580600947705E-82d, + +6.122543452787843E-82d, + +1.664279766968299E-81d, + +4.523982262003404E-81d, + +1.2297456769063303E-80d, + +3.342795345742034E-80d, + +9.086660081726823E-80d, + +2.4700104681773258E-79d, + +6.714184569587689E-79d, + +1.8251046352720517E-78d, + +4.961148056969105E-78d, + +1.3485799924445315E-77d, + +3.665820371396835E-77d, + +9.964732578705785E-77d, + +2.708695208461993E-76d, + +7.362996533913695E-76d, + +2.0014700145557332E-75d, + +5.440559532453721E-75d, + +1.4788974793889734E-74d, + +4.020060558571273E-74d, + +1.092765612182012E-73d, + +2.970445258959489E-73d, + +8.074507236705857E-73d, + +2.1948784599535102E-72d, + +5.966298125808066E-72d, + +1.6218081151910012E-71d, + +4.408531734441582E-71d, + +1.198363039426718E-70d, + +3.257488853378793E-70d, + +8.854771398921902E-70d, + +2.406976727302894E-69d, + +6.542840888268955E-69d, + +1.778528517418201E-68d, + +4.834541417183388E-68d, + +1.3141647465063647E-67d, + +3.572270133517001E-67d, + +9.710435805122717E-67d, + +2.63957027915428E-66d, + +7.175096392165733E-66d, + +1.9503931430716318E-65d, + +5.3017188565638215E-65d, + +1.4411566290936352E-64d, + +3.9174693825966044E-64d, + +1.0648786018364265E-63d, + +2.8946401383311E-63d, + +7.868447965383903E-63d, + +2.1388659707647114E-62d, + +5.814040618670345E-62d, + +1.5804200403673568E-61d, + +4.296027044486766E-61d, + +1.1677812418806031E-60d, + +3.174358801839755E-60d, + +8.62880163941313E-60d, + +2.345551464945955E-59d, + +6.3758692300917355E-59d, + +1.733140900346534E-58d, + +4.711165925070571E-58d, + +1.2806275683797178E-57d, + +3.481106736845E-57d, + +9.462629520363307E-57d, + +2.5722094667974783E-56d, + +6.9919903587080315E-56d, + +1.9006201022568844E-55d, + +5.166420404109835E-55d, + +1.4043786616805493E-54d, + +3.8174968984748894E-54d, + +1.03770335512154E-53d, + +2.820769858672565E-53d, + +7.667647949477605E-53d, + +2.0842827711783212E-52d, + +5.6656680900216754E-52d, + +1.5400881501571645E-51d, + +4.1863938339341257E-51d, + +1.1379799629071911E-50d, + +3.093350150840571E-50d, + +8.408597060399334E-50d, + +2.2856938448387544E-49d, + +6.2131591878042886E-49d, + +1.688911928929718E-48d, + +4.5909386437919143E-48d, + +1.2479464696643861E-47d, + +3.3922703599272275E-47d, + +9.221146830884422E-47d, + +2.5065676066043174E-46d, + +6.8135571305481364E-46d, + +1.8521166948363666E-45d, + +5.0345752964740226E-45d, + +1.368539456379101E-44d, + +3.720075801577098E-44d, + +1.0112214979786464E-43d, + +2.7487849807248755E-43d, + +7.47197247068667E-43d, + +2.0310928323153876E-42d, + +5.521082422279256E-42d, + +1.5007857288519654E-41d, + +4.0795586181406803E-41d, + +1.108938997126179E-40d, + +3.0144088843073416E-40d, + +8.194012195477669E-40d, + +2.2273635587196807E-39d, + +6.054601485195952E-39d, + +1.6458113136245473E-38d, + +4.473779311490168E-38d, + +1.2160992719555806E-37d, + +3.3057007442449645E-37d, + +8.985825281444118E-37d, + +2.442600707513088E-36d, + +6.639677673630215E-36d, + +1.8048513285848406E-35d, + +4.906094420881007E-35d, + +1.3336148713971936E-34d, + +3.625141007634431E-34d, + +9.854154449263851E-34d, + +2.6786368134431636E-33d, + +7.28128971953363E-33d, + +1.9792597720953414E-32d, + +5.380185921962174E-32d, + +1.4624861244004054E-31d, + +3.975449484028966E-31d, + +1.080639291795678E-30d, + +2.9374821418009058E-30d, + +7.984904044796711E-30d, + +2.1705221445447534E-29d, + +5.900089995748943E-29d, + +1.6038109389511792E-28d, + +4.359610133382778E-28d, + +1.185064946717304E-27d, + +3.221340469489223E-27d, + +8.756510122348782E-27d, + +2.380266370880709E-26d, + +6.47023467943241E-26d, + +1.75879225876483E-25d, + +4.780892502168074E-25d, + +1.2995814853898995E-24d, + +3.5326287852455166E-24d, + +9.602680736954162E-24d, + +2.6102792042257208E-23d, + +7.095474414148981E-23d, + +1.9287497671359936E-22d, + +5.242885191553114E-22d, + +1.4251641388208515E-21d, + +3.873997809109103E-21d, + +1.0530616658562386E-20d, + +2.862518609581133E-20d, + +7.78113163345177E-20d, + +2.1151310700892382E-19d, + +5.74952254077566E-19d, + +1.5628822871880503E-18d, + +4.24835413113866E-18d, + +1.1548223864099742E-17d, + +3.139132557537509E-17d, + +8.533046968331264E-17d, + +2.3195229636950566E-16d, + +6.305116324200775E-16d, + +1.71390848833098E-15d, + +4.6588861918718874E-15d, + +1.2664165777252073E-14d, + +3.442477422913037E-14d, + +9.357622912219837E-14d, + +2.5436656904062604E-13d, + +6.914399608426436E-13d, + +1.879528650772233E-12d, + +5.1090893668503945E-12d, + +1.3887944613766301E-11d, + +3.775134371775124E-11d, + +1.0261880234452292E-10d, + +2.789468100949932E-10d, + +7.582560135332983E-10d, + +2.061153470123145E-9d, + +5.602796449011294E-9d, + +1.5229979055675358E-8d, + +4.139937459513021E-8d, + +1.1253517584464134E-7d, + +3.059023470086686E-7d, + +8.315287232107949E-7d, + +2.260329438286135E-6d, + +6.1442124206223525E-6d, + +1.670170240686275E-5d, + +4.539993096841499E-5d, + +1.2340981629677117E-4d, + +3.35462624207139E-4d, + +9.118819143623114E-4d, + +0.0024787522852420807d, + +0.006737947463989258d, + +0.018315639346837997d, + +0.049787066876888275d, + +0.1353352963924408d, + +0.3678794503211975d, + +1.0d, + +2.7182817459106445d, + +7.389056205749512d, + +20.08553695678711d, + +54.59815216064453d, + +148.41314697265625d, + +403.42877197265625d, + +1096.633056640625d, + +2980.9580078125d, + +8103.083984375d, + +22026.46484375d, + +59874.140625d, + +162754.78125d, + +442413.375d, + +1202604.25d, + +3269017.5d, + +8886110.0d, + +2.4154952E7d, + +6.5659968E7d, + +1.78482304E8d, + +4.85165184E8d, + +1.318815744E9d, + +3.584912896E9d, + +9.74480384E9d, + +2.6489122816E10d, + +7.200489472E10d, + +1.95729620992E11d, + +5.32048248832E11d, + +1.446257098752E12d, + +3.9313342464E12d, + +1.0686474223616E13d, + +2.904884772864E13d, + +7.8962956959744E13d, + +2.14643574308864E14d, + +5.83461777702912E14d, + +1.586013579247616E15d, + +4.31123180027904E15d, + +1.1719142537166848E16d, + +3.1855931348221952E16d, + +8.6593395455164416E16d, + +2.35385270340419584E17d, + +6.3984347447610573E17d, + +1.73927483790327808E18d, + +4.7278395262972723E18d, + +1.285159987981792E19d, + +3.493427277593156E19d, + +9.496119530068797E19d, + +2.581312717296228E20d, + +7.016736290557636E20d, + +1.907346499785443E21d, + +5.1847060206155E21d, + +1.4093490364499379E22d, + +3.831007739580998E22d, + +1.0413759887481643E23d, + +2.8307533984544136E23d, + +7.694785471490595E23d, + +2.0916595931561093E24d, + +5.685720022003016E24d, + +1.545539007875769E25d, + +4.201209991636407E25d, + +1.142007304008196E26d, + +3.104297782658242E26d, + +8.43835682327257E26d, + +2.2937832658080656E27d, + +6.23514943204966E27d, + +1.694889206675675E28d, + +4.607187019879158E28d, + +1.2523630909973607E29d, + +3.4042761729010895E29d, + +9.253781621373885E29d, + +2.5154385492401904E30d, + +6.837671137556327E30d, + +1.8586717056324128E31d, + +5.05239404378821E31d, + +1.3733830589835937E32d, + +3.733241849647479E32d, + +1.014800418749161E33d, + +2.758513549969986E33d, + +7.498416981578345E33d, + +2.0382811492597872E34d, + +5.540622484676759E34d, + +1.5060972626944096E35d, + +4.0939972479624634E35d, + +1.1128638067747114E36d, + +3.0250770246136387E36d, + +8.223012393018281E36d, + +2.2352467822017166E37d, + +6.076029840339376E37d, + +1.6516361647240826E38d, + +4.4896127778163155E38d, + +1.2204032949639917E39d, + +3.3174000012927697E39d, + +9.017628107716908E39d, + +2.451245443147225E40d, + +6.663175904917432E40d, + +1.8112388823726723E41d, + +4.923458004084836E41d, + +1.3383347029375378E42d, + +3.637970747803715E42d, + +9.889030935681123E42d, + +2.6881169167589747E43d, + +7.307059786371152E43d, + +1.986264756071962E44d, + +5.399227989109673E44d, + +1.467662348860426E45d, + +3.989519470441919E45d, + +1.0844638420493122E46d, + +2.9478781225754055E46d, + +8.013164089994031E46d, + +2.1782039447564253E47d, + +5.920972420778763E47d, + +1.609486943324346E48d, + +4.3750396394525074E48d, + +1.1892591576149107E49d, + +3.2327411123173475E49d, + +8.787501601904039E49d, + +2.3886908001521312E50d, + +6.493134033643613E50d, + +1.7650169203544438E51d, + +4.7978130078372714E51d, + +1.3041809768060802E52d, + +3.5451314095271004E52d, + +9.636666808527841E52d, + +2.6195174357581655E53d, + +7.120586694432509E53d, + +1.9355758655647052E54d, + +5.2614409704305464E54d, + +1.4302079642723736E55d, + +3.8877083524279136E55d, + +1.0567886837680406E56d, + +2.872649515690124E56d, + +7.808670894670738E56d, + +2.1226166967029073E57d, + +5.769871153180574E57d, + +1.568413405104933E58d, + +4.263390023436419E58d, + +1.1589095247718807E59d, + +3.150242850860434E59d, + +8.563247933339596E59d, + +2.3277319969498524E60d, + +6.327431953939798E60d, + +1.719974302355042E61d, + +4.675374788964851E61d, + +1.2708985520400816E62d, + +3.454660807101683E62d, + +9.390740355567705E62d, + +2.5526681615684215E63d, + +6.938871462941557E63d, + +1.8861808782043154E64d, + +5.1271712215233855E64d, + +1.3937096689052236E65d, + +3.7884955399150257E65d, + +1.0298199046367501E66d, + +2.799340708992666E66d, + +7.609396391563323E66d, + +2.0684484008569103E67d, + +5.622626080395226E67d, + +1.528388084444653E68d, + +4.1545899609113734E68d, + +1.1293346659459732E69d, + +3.069849599753188E69d, + +8.344717266683004E69d, + +2.268329019570017E70d, + +6.165958325782564E70d, + +1.676081191364984E71d, + +4.556060380835955E71d, + +1.2384658100355657E72d, + +3.3664990715562672E72d, + +9.15109220707761E72d, + +2.4875248571153216E73d, + +6.761793219649385E73d, + +1.8380461271305958E74d, + +4.996327312938759E74d, + +1.3581426848077408E75d, + +3.691814001080034E75d, + +1.0035391101975138E76d, + +2.7279024753382288E76d, + +7.415207287657125E76d, + +2.0156621983963848E77d, + +5.479138512760614E77d, + +1.4893842728520671E78d, + +4.048565732162643E78d, + +1.1005142643914475E79d, + +2.991508131437659E79d, + +8.131762373533769E79d, + +2.210442148596269E80d, + +6.008604166110734E80d, + +1.633308028614055E81d, + +4.439791652732591E81d, + +1.206860599814453E82d, + +3.280586734644871E82d, + +8.917559854082513E82d, + +2.4240442814945802E83d, + +6.589235682116406E83d, + +1.7911398904871E84d, + +4.86882298924053E84d, + +1.3234832005748183E85d, + +3.597600556519039E85d, + +9.77929222446451E85d, + +2.658286976862848E86d, + +7.225974166887662E86d, + +1.9642232209552433E87d, + +5.3393125705958075E87d, + +1.4513757076459615E88d, + +3.945247871835613E88d, + +1.0724295693252266E89d, + +2.915165904253785E89d, + +7.924242330665303E89d, + +2.1540322390343345E90d, + +5.855267177907345E90d, + +1.5916266807316476E91d, + +4.326489915443873E91d, + +1.1760619079592718E92d, + +3.1968677404735245E92d, + +8.689987517871135E92d, + +2.3621834216830225E93d, + +6.421080550439423E93d, + +1.7454306955949023E94d, + +4.744571892885607E94d, + +1.2897084285532175E95d, + +3.505791114318544E95d, + +9.529727908157224E95d, + +2.5904487437231458E96d, + +7.041568925985714E96d, + +1.9140971884979424E97d, + +5.203055142575272E97d, + +1.4143368931719686E98d, + +3.8445667684706366E98d, + +1.0450615121235744E99d, + +2.8407720200442806E99d, + +7.722018663521402E99d, + +2.0990624115923312E100d, + +5.705842978547001E100d, + +1.5510089388648915E101d, + +4.216079296087462E101d, + +1.1460491592124923E102d, + +3.1152847602082673E102d, + +8.468222063292654E102d, + +2.3019011105282883E103d, + +6.257216813084462E103d, + +1.7008878437355237E104d, + +4.62349260394851E104d, + +1.2567956334920216E105d, + +3.416324322370112E105d, + +9.286532888251822E105d, + +2.5243410574836706E106d, + +6.861870970598542E106d, + +1.8652499723625443E107d, + +5.070274654122399E107d, + +1.3782437251846782E108d, + +3.746454626411946E108d, + +1.0183920005400422E109d, + +2.768276122845335E109d, + +7.524954624697075E109d, + +2.0454950851007314E110d, + +5.56023190218245E110d, + +1.511427628805191E111d, + +4.1084862677372065E111d, + +1.1168024085164686E112d, + +3.0357834799588566E112d, + +8.252116273466952E112d, + +2.2431576057283144E113d, + +6.097534318207731E113d, + +1.65748157925005E114d, + +4.5055022172222453E114d, + +1.2247224482958058E115d, + +3.329140840363789E115d, + +9.049543313665034E115d, + +2.4599209935197392E116d, + +6.686758417135634E116d, + +1.817649308779104E117d, + +4.940883275207154E117d, + +1.3430713954289087E118d, + +3.6508464654683645E118d, + +9.924030156169606E118d, + +2.697631034485758E119d, + +7.332921137166064E119d, + +1.9932945470297703E120d, + +5.418336099279846E120d, + +1.472856595860236E121d, + +4.0036393271908754E121d, + +1.0883019300873278E122d, + +2.9583112936666607E122d, + +8.041523923017192E122d, + +2.1859129781586158E123d, + +5.941927186144745E123d, + +1.6151834292371802E124d, + +4.390523815859274E124d, + +1.1934680816813702E125d, + +3.2441826014060764E125d, + +8.81860282490643E125d, + +2.3971445233885962E126d, + +6.516115189736396E126d, + +1.7712635751001657E127d, + +4.814793918384117E127d, + +1.3087966177291396E128d, + +3.557678449715009E128d, + +9.670771210463886E128d, + +2.628788218289742E129d, + +7.145787619369324E129d, + +1.9424264981694277E130d, + +5.280062387569078E130d, + +1.4352697002457768E131d, + +3.901467289560222E131d, + +1.0605288965077546E132d, + +2.882816299252225E132d, + +7.836307815186044E132d, + +2.1301292155181736E133d, + +5.790291758828013E133d, + +1.573964437869041E134d, + +4.278478878300888E134d, + +1.1630112062985817E135d, + +3.1613917467297413E135d, + +8.593554223894477E135d, + +2.335970335559215E136d, + +6.349826172787151E136d, + +1.7260616357651607E137d, + +4.691921416188566E137d, + +1.2753966504932798E138d, + +3.466887271843006E138d, + +9.423976538577447E138d, + +2.561702766944378E139d, + +6.963429563637273E139d, + +1.892856346657855E140d, + +5.1453167686439515E140d, + +1.3986421289359558E141d, + +3.8019036618832785E141d, + +1.033464507572145E142d, + +2.809247950589945E142d, + +7.636326960498012E142d, + +2.075769060297565E143d, + +5.64252553828769E143d, + +1.5337974510118784E144d, + +4.169293918423203E144d, + +1.1333315586787883E145d, + +3.080714152600695E145d, + +8.374250298636991E145d, + +2.276357074042286E146d, + +6.187780443461367E146d, + +1.6820131331794073E147d, + +4.572185635487065E147d, + +1.2428488853188662E148d, + +3.378413594504258E148d, + +9.183480622172801E148d, + +2.4963286658278886E149d, + +6.785725312893433E149d, + +1.8445514681108982E150d, + +5.014010481958507E150d, + +1.3629491735708616E151d, + +3.7048805655699485E151d, + +1.0070909418550386E152d, + +2.7375567044077912E152d, + +7.441451374243517E152d, + +2.022795961737854E153d, + +5.4985298195094216E153d, + +1.494655405262451E154d, + +4.062894701808608E154d, + +1.1044092571980793E155d, + +3.002095574584687E155d, + +8.160542326793782E155d, + +2.218265110516721E156d, + +6.02987028472758E156d, + +1.6390888071605646E157d, + +4.455504920700703E157d, + +1.2111317421229415E158d, + +3.2921976772303727E158d, + +8.94912101169977E158d, + +2.432623425087251E159d, + +6.612555731556604E159d, + +1.7974788874847574E160d, + +4.8860545948985793E160d, + +1.328167263606087E161d, + +3.610333312791256E161d, + +9.813901863427107E161d, + +2.667695552814763E162d, + +7.251548346906463E162d, + +1.9711751621240536E163d, + +5.3582093498119173E163d, + +1.4565123573071036E164d, + +3.959211091077107E164d, + +1.0762251933089556E165d, + +2.9254832789181E165d, + +7.952287052787358E165d, + +2.161656025361765E166d, + +5.8759898326913254E166d, + +1.597259768214821E167d, + +4.3418021646459346E167d, + +1.1802241249113175E168d, + +3.2081817253680657E168d, + +8.720743087611513E168d, + +2.3705435424427623E169d, + +6.443805025317327E169d, + +1.7516078165936552E170d, + +4.7613641572445654E170d, + +1.2942728582966776E171d, + +3.518198614137319E171d, + +9.563454814394247E171d, + +2.5996166206245285E172d, + +7.066491077377918E172d, + +1.920871394985668E173d, + +5.221469250951617E173d, + +1.4193426880442385E174d, + +3.8581732071331E174d, + +1.0487601931965087E175d, + +2.850825930161946E175d, + +7.749348772180658E175d, + +2.1064911705560668E176d, + +5.726036941135634E176d, + +1.5564982816556894E177d, + +4.231000988846797E177d, + +1.1501053030837989E178d, + +3.1263099916916113E178d, + +8.498192212235393E178d, + +2.3100480183046895E179d, + +6.279361500971995E179d, + +1.7069074829463731E180d, + +4.63985600437427E180d, + +1.2612435745231905E181d, + +3.4284156709489884E181d, + +9.319400030019162E181d, + +2.5332752658571312E182d, + +6.88615578404537E182d, + +1.8718514371423056E183d, + +5.088219872370737E183d, + +1.3831214731781958E184d, + +3.759713966511158E184d, + +1.021996184153141E185d, + +2.778073442169904E185d, + +7.55158797540476E185d, + +2.0527342305586606E186d, + +5.579910641313343E186d, + +1.5167767828844167E187d, + +4.123026721295484E187d, + +1.1207549425651513E188d, + +3.0465278560980536E188d, + +8.281321669236493E188d, + +2.251096660331649E189d, + +6.119114404399683E189d, + +1.6633478556884994E190d, + +4.521448560089285E190d, + +1.2290570545894685E191d, + +3.340923580982338E191d, + +9.081571104550255E191d, + +2.468626868232408E192d, + +6.710424255583952E192d, + +1.8240823171621646E193d, + +4.958369974640573E193d, + +1.3478247120462365E194d, + +3.6637673548790206E194d, + +9.959152908532152E194d, + +2.707178052117959E195d, + +7.358873642076596E195d, + +2.0003490682463053E196d, + +5.4375131636754E196d, + +1.4780692924846082E197d, + +4.01780853635105E197d, + +1.0921536132159379E198d, + +2.968781250496917E198d, + +8.069984512111955E198d, + +2.193649279840519E199d, + +5.962956589227457E199d, + +1.620899738203635E200d, + +4.406062052965071E200d, + +1.1976919074588434E201d, + +3.2556641859513496E201d, + +8.849812639395597E201d, + +2.40562867677584E202d, + +6.539175932653188E202d, + +1.7775323307944624E203d, + +4.831833881898182E203d, + +1.3134287685114547E204d, + +3.5702693195009266E204d, + +9.704997606668411E204d, + +2.63809219778715E205d, + +7.171077244202293E205d, + +1.949300880034352E206d, + +5.298749302736127E206d, + +1.4403494631058154E207d, + +3.91527572177694E207d, + +1.0642823992403076E208d, + +2.8930193727937684E208d, + +7.8640411896421955E208d, + +2.1376680994038112E209d, + +5.8107841809216616E209d, + +1.5795351101531684E210d, + +4.293620869258453E210d, + +1.1671272667059652E211d, + +3.172580666390786E211d, + +8.623968972387222E211d, + +2.3442378838418366E212d, + +6.372298757235201E212d, + +1.7321703934464356E213d, + +4.708527306855985E213d, + +1.279910496643312E214d, + +3.479157135998568E214d, + +9.45732984079136E214d, + +2.5707689593428096E215d, + +6.988074107282322E215d, + +1.8995553996578656E216d, + +5.1635269305465607E216d, + +1.4035923083915864E217d, + +3.815359096108819E217d, + +1.0371220592190472E218d, + +2.819190456167585E218d, + +7.663353127378024E218d, + +2.083115484919861E219d, + +5.662495731848751E219d, + +1.5392257142577226E220d, + +4.184049381430498E220d, + +1.1373425785132867E221d, + +3.091617462831603E221d, + +8.403887374207366E221d, + +2.2844135610697528E222d, + +6.209679892802781E222d, + +1.6879660933816274E223d, + +4.588367423411997E223d, + +1.2472476068464461E224d, + +3.3903703993793316E224d, + +9.215982463319503E224d, + +2.5051637206758385E225d, + +6.809741127603255E225d, + +1.8510795864289367E226d, + +5.031755776868959E226d, + +1.3677729802316034E227d, + +3.7179924024793253E227d, + +1.0106552237522032E228d, + +2.7472456017809066E228d, + +7.467788172398272E228d, + +2.029955237703202E229d, + +5.517990469846618E229d, + +1.4999452522236406E230d, + +4.0772734783595525E230d, + +1.1083180046837618E231d, + +3.012720614547867E231d, + +8.18942426109357E231d, + +2.2261161215322043E232d, + +6.051211457626543E232d, + +1.6448897917725177E233d, + +4.471273900208441E233d, + +1.2154183152078517E234d, + +3.3038494682728794E234d, + +8.98079409878202E234d, + +2.4412328161430576E235d, + +6.63595840453991E235d, + +1.8038406914061554E236d, + +4.90334700062756E236d, + +1.3328680266667662E237d, + +3.623110695743118E237d, + +9.848636053777669E237d, + +2.677136737066629E238d, + +7.277212447141125E238d, + +1.978151484427976E239d, + +5.377173488599035E239d, + +1.4616672175682191E240d, + +3.973222981713661E240d, + +1.0800340064859439E241d, + +2.935837009891444E241d, + +7.980432566722885E241d, + +2.169306470354036E242d, + +5.896786161387733E242d, + +1.6029126916635028E243d, + +4.357168123448786E243d, + +1.1844011798406507E244d, + +3.2195361624179725E244d, + +8.751606149833694E244d, + +2.3789334438756013E245d, + +6.466611224443739E245d, + +1.7578073785142153E246d, + +4.7782149589194885E246d, + +1.2988535295611824E247d, + +3.5306502960727705E247d, + +9.597302512507479E247d, + +2.608817438130718E248d, + +7.091500562953208E248d, + +1.9276698418065647E249d, + +5.239949786641934E249d, + +1.42436589329759E250d, + +3.8718282216768776E250d, + +1.0524719896550007E251d, + +2.860915548426704E251d, + +7.77677492833005E251d, + +2.113946677051906E252d, + +5.7463023795153145E252d, + +1.56200679236425E253d, + +4.2459748085663055E253d, + +1.1541756557557508E254d, + +3.137374584307575E254d, + +8.528268445871411E254d, + +2.3182239583484444E255d, + +6.301585387776819E255d, + +1.7129486892266285E256d, + +4.6562769567905925E256d, + +1.26570724146049E257d, + +3.4405490416979487E257d, + +9.352382323649647E257d, + +2.54224113415832E258d, + +6.910528108396216E258d, + +1.8784760208391767E259d, + +5.106228040084293E259d, + +1.3880166914480165E260d, + +3.7730204737910044E260d, + +1.0256131352582533E261d, + +2.787906051540986E261d, + +7.578313650939932E261d, + +2.0599991793068063E262d, + +5.5996586041611455E262d, + +1.522145133131402E263d, + +4.137618951061827E263d, + +1.1247213964487372E264d, + +3.0573102223682595E264d, + +8.310629417537063E264d, + +2.2590636576955473E265d, + +6.1407711078356886E265d, + +1.6692346202615142E266d, + +4.5374504961394207E266d, + +1.2334070098307164E267d, + +3.3527476928456816E267d, + +9.113713162029408E267d, + +2.4773638527240193E268d, + +6.734172833429278E268d, + +1.8305382378470305E269d, + +4.9759187284770303E269d, + +1.352594940263854E270d, + +3.6767339705169146E270d, + +9.994400500679653E270d, + +2.716759624268743E271d, + +7.384918458508588E271d, + +2.007428933605617E272d, + +5.456757565532369E272d, + +1.4833003969415539E273d, + +4.0320284712983994E273d, + +1.096019026243815E274d, + +2.979288529962515E274d, + +8.098545495417704E274d, + +2.201412886580694E275d, + +5.984060832462728E275d, + +1.6266362950862408E276d, + +4.4216561713555547E276d, + +1.2019307065458128E277d, + +3.2671863888979078E277d, + +8.881133159512924E277d, + +2.4141423627760256E278d, + +6.562319473965767E278d, + +1.7838233889223407E279d, + +4.848934634563382E279d, + +1.3180771991576186E280d, + +3.5829049382293792E280d, + +9.739345931419228E280d, + +2.6474285478041252E281d, + +7.196457718729758E281d, + +1.956199868121249E282d, + +5.31750271790054E282d, + +1.4454470027638629E283d, + +3.929132560365955E283d, + +1.0680488848057261E284d, + +2.9032581477488686E284d, + +7.89187408872514E284d, + +2.1452336456259667E285d, + +5.831349876080173E285d, + +1.5851251724785243E286d, + +4.308816643345461E286d, + +1.1712579802975851E287d, + +3.1838092090922606E287d, + +8.654490685278886E287d, + +2.3525345191912968E288d, + +6.39485115791896E288d, + +1.7383009254496851E289d, + +4.725191397657393E289d, + +1.2844402232816276E290d, + +3.491470347090126E290d, + +9.490800658395667E290d, + +2.579867270991543E291d, + +7.012806239173502E291d, + +1.906278351789277E292d, + +5.181801397059486E292d, + +1.408559707497606E293d, + +3.8288623079292884E293d, + +1.0407926842436056E294d, + +2.829168201470791E294d, + +7.690475570840264E294d, + +2.0904882610105383E295d, + +5.68253547942899E295d, + +1.544673396032028E296d, + +4.1988574190754736E296d, + +1.1413677466646359E297d, + +3.102559332875688E297d, + +8.433630296371073E297d, + +2.292498520423419E298d, + +6.23165710486722E298d, + +1.6939399242810123E299d, + +4.604606371472047E299d, + +1.2516618713553432E300d, + +3.402369329874797E300d, + +9.248598815279678E300d, + +2.51402968559859E301d, + +6.833842035076675E301d, + +1.8576309291617257E302d, + +5.049564425991982E302d, + +1.3726137091534984E303d, + +3.7311513682845094E303d, + +1.0142320772726397E304d, + +2.7569686255975333E304d, + +7.494218049456063E304d, + +2.037139607241041E305d, + +5.5375196488302575E305d, + +1.5052539519895093E306d, + +4.091704288360009E306d, + +1.1122405335641184E307d, + +3.023383151402969E307d, + +8.218407798110846E307d, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + }; + + /** Exponential evaluated at integer values, + * exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX] + */ + private static final double[] EXP_INT_B = new double[] { + +0.0d, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + -1.76097684E-316d, + -2.44242319E-315d, + -9.879317845E-315d, + -1.3811462167E-314d, + +2.1775261204E-314d, + -1.4379095864E-313d, + +1.4219324087E-313d, + +1.00605438061E-312d, + -1.287101187097E-312d, + +5.33839690397E-312d, + -9.35130825405E-313d, + -4.15218681073E-311d, + +4.546040329134E-311d, + -1.57333572310673E-310d, + +1.05387548454467E-309d, + +2.095732474644446E-309d, + -2.62524392470767E-310d, + +5.86440876259637E-309d, + -2.401816502004675E-309d, + -2.2711230715729753E-308d, + +2.0670460065057715E-307d, + +3.436860020483706E-308d, + +2.0862243734177337E-306d, + -4.637025318037353E-306d, + +9.222671009756424E-306d, + +6.704597874020559E-305d, + +4.351284159444109E-305d, + +4.232889602759328E-304d, + +1.2840977763293412E-303d, + -2.6993478083348727E-303d, + -1.053265874779237E-303d, + +1.207746682843556E-303d, + +5.21281096513035E-303d, + +1.6515377082609677E-301d, + +3.3951607353932444E-301d, + +5.609418227003629E-301d, + +4.238775357914848E-300d, + -9.441842771290538E-300d, + -2.1745347282493023E-299d, + -6.203839803215248E-299d, + -5.617718879466363E-299d, + +5.2869976233132615E-298d, + -1.4300075619643524E-298d, + +4.3198234936686506E-297d, + -2.6448316331572387E-297d, + +4.315655444002347E-296d, + -7.253671992213344E-296d, + -1.1288398461391523E-295d, + -4.83901764243093E-296d, + +1.7407497662694827E-295d, + +1.1969717029666017E-294d, + -7.752519943329177E-294d, + -4.019569741253664E-293d, + -2.4467928392518484E-293d, + -1.0269233640424235E-292d, + -3.2330960700986594E-292d, + -1.440995270758115E-291d, + -3.726946038150935E-291d, + -1.3424576100819801E-291d, + -3.128894928199484E-290d, + -5.989337506920005E-290d, + -9.438168176533759E-290d, + -1.9220613500411237E-289d, + +2.1186736024949195E-289d, + +6.3015208029537436E-288d, + -8.168129112703755E-288d, + -1.6040513288090055E-287d, + -1.0809972724404233E-287d, + -3.080380385962424E-286d, + +2.6399157174374624E-286d, + +1.3317127674213423E-285d, + -3.5821668044872306E-285d, + +1.978536584535392E-284d, + +1.3399392455370071E-284d, + -2.870168560029448E-284d, + +3.5311184272663063E-283d, + -7.204247881190918E-283d, + +3.2425604548983798E-282d, + +3.913063150326019E-282d, + -2.260957518848075E-281d, + +3.807242187736102E-281d, + -5.095591405025083E-281d, + +2.3400625068490396E-280d, + -1.1564717694090882E-280d, + -3.517594695450786E-279d, + +6.666544384808297E-279d, + -9.204784113858607E-279d, + +4.8677119923665573E-278d, + +7.942176091555472E-278d, + -2.5113270522478854E-277d, + +5.332900939354667E-277d, + -3.491241408725929E-276d, + -2.1141094074221325E-276d, + +1.722049095222509E-275d, + +4.0430160253378594E-275d, + +1.9888195459082551E-274d, + +3.230089643550739E-275d, + +5.077824728028163E-274d, + -3.526547961682877E-274d, + -6.4376298274983765E-273d, + -2.5338279333399964E-272d, + -3.614847626733713E-272d, + +2.510812179067931E-272d, + +3.953806005373127E-272d, + +7.112596406315374E-272d, + -2.850217520533226E-270d, + -8.571477929711754E-270d, + +1.2902019831221148E-269d, + -6.978783784755863E-270d, + +9.89845486618531E-269d, + -3.538563171970534E-268d, + +3.537475449241181E-268d, + +3.6924578046381256E-267d, + +1.3555502536444713E-266d, + -1.1279742372661484E-266d, + +5.475072932318336E-266d, + -1.1679889049814275E-265d, + -8.946297908979776E-266d, + +1.0565816011650582E-264d, + -3.2161237736296753E-265d, + -6.022045553485609E-264d, + -2.0332050860436034E-263d, + -1.0488538406930105E-262d, + +1.6793752843984384E-262d, + +3.2558720916543104E-263d, + -1.9546569053899882E-262d, + +5.082190670014963E-262d, + -1.0188117475357564E-260d, + +3.7920054509691455E-261d, + -8.330969967504819E-260d, + -1.1623181434592597E-259d, + +9.09665088462258E-259d, + -1.56400149127482E-259d, + -7.796557225750673E-258d, + +6.751460509863465E-258d, + +7.243157658226935E-258d, + +1.2574668958946027E-256d, + +2.2678858131411216E-256d, + +5.1079306249351287E-256d, + -5.672261759108003E-257d, + +3.476539491009769E-256d, + -1.3481093992496937E-254d, + -3.314051560952014E-254d, + +7.408112967339146E-255d, + -7.164884605413269E-254d, + -6.456588023278983E-253d, + -1.4881197370811587E-252d, + +1.7534012237555307E-252d, + -1.3070101381473173E-251d, + +6.081420141954215E-251d, + +6.591143677421159E-251d, + +2.6917461073773043E-250d, + +3.683043641790553E-251d, + +1.2195076420741757E-249d, + -8.220283439582378E-249d, + +1.637852737426943E-248d, + -8.332543237340988E-249d, + +2.9581193516975647E-248d, + -1.7790661150204172E-247d, + -1.7809679916043692E-247d, + +8.378574405736031E-247d, + -2.883847036065813E-246d, + +1.3223776943337897E-245d, + +3.098547586845664E-245d, + -1.1036542789147287E-244d, + -5.7187703271582225E-244d, + -1.8058492822440396E-244d, + +4.4373726292703545E-243d, + -3.4631935816990754E-243d, + -1.82770041073856E-243d, + +3.845535085273936E-242d, + +8.446532344375812E-242d, + +2.7751016140238277E-242d, + +1.3158882241538003E-241d, + -3.579433051074272E-240d, + -6.151751570213211E-240d, + -2.990535475079021E-239d, + +2.3396028616528764E-239d, + +7.233790684263346E-239d, + +1.0847913100494912E-238d, + +7.103148400942551E-238d, + +3.463600299750966E-237d, + -4.873121855093712E-237d, + +1.3407295326570417E-236d, + +9.390271617387205E-237d, + -2.4767709454727603E-235d, + +3.205923535388443E-235d, + -1.0074984709952582E-234d, + +2.4747880175747574E-234d, + -5.146939682310558E-234d, + -2.827581009333298E-233d, + -3.0307641004671077E-233d, + +5.92044714050651E-233d, + -2.0582596893119236E-232d, + -6.58066591313112E-232d, + -4.869955151949929E-231d, + -5.763495903609913E-231d, + -2.3580462372762525E-230d, + +1.8559980428862584E-230d, + +2.854978560542175E-229d, + +5.637945686485334E-229d, + +2.1454644909004582E-228d, + -1.1918070206953359E-228d, + -5.021851606912854E-228d, + +3.861525553653117E-227d, + +6.533561982617909E-227d, + -3.015709444206057E-226d, + -5.042005018212734E-227d, + +1.5959614205422845E-225d, + +2.0402105689098835E-224d, + +5.164902728917601E-224d, + +9.981031744879876E-224d, + +4.0281104210095145E-223d, + +1.1158160971176672E-222d, + +2.0736172194624895E-222d, + +4.983162653734032E-222d, + +2.1753390051977871E-221d, + +3.969413618002761E-221d, + +1.3961255018698695E-220d, + +2.1290855095314206E-220d, + +1.1927747883417406E-219d, + +3.7264401117998796E-219d, + +9.318532410862293E-219d, + +2.3414841777613345E-218d, + +4.3791842770430786E-218d, + +1.7173159016511951E-217d, + +3.5037536832675478E-217d, + +1.4300098613455884E-216d, + +2.4189403362149483E-216d, + +9.306541421999056E-216d, + +3.442100456607687E-215d, + +5.94407068841904E-215d, + +2.0483260435783403E-214d, + +3.8410992889527954E-214d, + +1.2038281262953917E-213d, + +3.865007795216205E-213d, + +9.754659138599756E-213d, + +2.7653605770745684E-212d, + +5.359568079675375E-212d, + +2.61726605666378E-211d, + +5.054202073556894E-211d, + +8.707092668016246E-211d, + +1.4080573899148006E-210d, + +1.288124387778789E-209d, + +1.8639901642011898E-209d, + +6.076014540574561E-209d, + +1.798489141298457E-208d, + +2.1525406805994896E-208d, + +1.1864056832305874E-207d, + +2.1077440662171152E-207d, + +1.3784853708457332E-206d, + +1.6965806532093783E-206d, + +7.241626420445137E-206d, + +2.575584299085016E-205d, + +6.151951078101721E-205d, + +2.40652042118887E-204d, + +4.022633486003565E-204d, + +5.8840879519086286E-204d, + +3.2820308007277566E-203d, + +4.31880454864738E-203d, + +2.427240455243201E-202d, + +7.326955749884755E-202d, + +1.4310184489676175E-201d, + +4.464279133463661E-201d, + +4.895131474682867E-201d, + +4.48614966943544E-200d, + +8.924048768324976E-200d, + +2.5035535029701945E-199d, + +6.627829836338812E-199d, + +2.6066826304502746E-198d, + +8.042275310036546E-198d, + +2.115062964308555E-197d, + +4.413745413236018E-197d, + +1.644449394585716E-196d, + +3.138217752973845E-196d, + +7.48533983136081E-196d, + +2.613626422028823E-195d, + +3.6741841454219095E-195d, + +5.906102862953403E-195d, + +4.4940857547850743E-194d, + +5.840064709376958E-194d, + +3.087661273836024E-193d, + +4.995552216100365E-193d, + +1.991444798915497E-192d, + +7.097454751809522E-192d, + +2.0510193986749737E-191d, + +5.759440286608551E-191d, + +1.7013941257113314E-190d, + +2.1383323934483528E-190d, + +8.280292810015406E-190d, + +3.138655772049104E-189d, + +7.961506427685701E-189d, + +2.0579001228504997E-188d, + +7.530840351477639E-188d, + +1.4582863136475673E-187d, + +3.149267215638608E-187d, + +5.443114553057336E-187d, + +3.4672966834277804E-186d, + +7.374944406615125E-186d, + +2.7318417252599104E-185d, + +7.913674211949961E-185d, + +2.5217716516462005E-184d, + +4.0866585874353075E-184d, + +1.2087698972768686E-183d, + +3.7072473866919033E-183d, + +1.1333588840402273E-182d, + +1.61949812578045E-182d, + +6.567779607147072E-182d, + +2.422974840736314E-181d, + +2.551170809294396E-181d, + +1.0905890688083124E-180d, + +3.221279639653057E-180d, + +7.068244813489027E-180d, + +1.3752309224575428E-179d, + +7.20154303462761E-179d, + +1.5391707185581056E-178d, + +7.708777608683431E-178d, + +5.597398155472547E-178d, + +1.8487854656676722E-177d, + +1.0577249492414076E-176d, + +2.8926683313922764E-176d, + +4.090184282164232E-176d, + +1.6142943398013813E-175d, + +7.873864351702525E-175d, + +2.242630017261011E-174d, + +3.4637009373878283E-174d, + +1.5907089565090164E-173d, + +1.6985075903314236E-173d, + +1.1552273904608563E-172d, + +2.237894048535414E-172d, + +5.321990399912051E-172d, + +1.4106062639738257E-171d, + +2.9850404523368767E-171d, + +1.5683802588004895E-170d, + +4.880146806045633E-170d, + +1.1489352403441815E-169d, + +1.6401586605693734E-169d, + +8.29169700697816E-169d, + +1.0380723705441457E-168d, + +7.126414081261746E-168d, + +1.253325949455206E-167d, + +2.595079206183114E-167d, + +1.537490712803659E-166d, + +2.6338455225993276E-166d, + +7.994936425058567E-166d, + +1.5716634677516025E-165d, + +3.669404761339415E-165d, + +1.9941628263579332E-164d, + +4.5012079983352374E-164d, + +7.283163019991001E-164d, + +2.398038505188316E-163d, + +7.868666894503849E-163d, + +2.1478649410390003E-162d, + +8.306111510463843E-162d, + +1.5453160659068463E-161d, + -4.590496588813841E-162d, + +3.5449293983801232E-161d, + -1.0440854056870505E-160d, + -2.321064927632431E-160d, + +5.707867001443433E-160d, + -2.238614484037969E-159d, + +2.482282821883242E-159d, + -1.1508772192025259E-158d, + +1.9903990578876104E-158d, + -1.2116165315442256E-158d, + -2.9084557554502667E-157d, + -1.1211083853006645E-156d, + -1.309893394818129E-156d, + +4.2269712317468864E-156d, + -7.678973146281339E-156d, + +3.2021376921211934E-155d, + -7.08313012515209E-155d, + +1.944398214330544E-154d, + +1.1860061363751161E-153d, + +1.5234465914578058E-153d, + -2.9020908354550263E-153d, + +4.980100072851796E-153d, + +2.3101551448625578E-152d, + -1.1959241322537072E-151d, + -9.27398924154364E-153d, + +5.999390491704392E-152d, + +1.3373196561281372E-150d, + -1.0271780540759147E-150d, + +2.575620466387945E-150d, + -6.56250013356227E-149d, + -1.1961357917482867E-148d, + +5.5807813570926636E-148d, + +9.252840189663807E-148d, + -1.830335419852293E-147d, + +9.350990339947455E-147d, + -1.6072409939877762E-146d, + -2.5309995887229526E-146d, + -1.6014373376410622E-146d, + -3.303297758377758E-145d, + +1.5640419864850202E-145d, + +9.544642884951585E-145d, + -8.64864445321803E-144d, + +7.580392204597681E-144d, + +2.678334184447568E-143d, + -3.7269289985326055E-143d, + -2.851965258161176E-142d, + +7.243267286265823E-142d, + +4.4510805312036926E-141d, + +9.008499734799015E-141d, + +1.130435759928337E-140d, + -3.096539751496479E-140d, + -1.497405487919762E-139d, + +3.51519845948652E-139d, + -4.713790209541894E-139d, + +4.740753295616865E-138d, + +9.517570994930463E-138d, + -1.8842098029339485E-137d, + -3.825558165008403E-137d, + +1.1817638600528107E-136d, + -3.514601201473235E-136d, + -6.344612631552417E-136d, + -1.6754164785291923E-136d, + +4.445372986583078E-135d, + -3.89604237755475E-134d, + -1.0155552195374609E-134d, + +2.1858142063550155E-134d, + +3.497714990137842E-133d, + -7.635830383612894E-133d, + +1.2050744860079718E-132d, + -7.683019590615251E-133d, + -3.344806129021162E-131d, + -1.6737914131474577E-131d, + -4.30610076666344E-131d, + +5.184023388254773E-130d, + +2.6290763595212492E-129d, + +7.90041744728452E-130d, + -3.204473056113754E-129d, + -2.552517201762272E-128d, + +7.130134251490065E-128d, + -3.2244113258340395E-127d, + -1.064920993515727E-127d, + +2.7466520735457463E-126d, + +4.368312797746065E-126d, + +1.8802599072446818E-125d, + -4.257625799463564E-125d, + +5.491672256552995E-125d, + +3.7298611779671127E-124d, + +5.724180836308973E-124d, + +1.3861841053630075E-123d, + +4.2303826056297614E-123d, + +3.5335436928899096E-123d, + -2.522906629540626E-122d, + +1.0147808005267102E-121d, + +6.734406065735473E-122d, + -4.948973160958133E-121d, + +2.4256181927024344E-120d, + +4.9056283164780554E-120d, + +6.846440394397547E-120d, + +3.512747689569002E-119d, + -9.020907406701404E-119d, + +2.5718749916003624E-118d, + +4.3724191002977524E-119d, + +1.001455050575191E-117d, + -2.4442443105031435E-117d, + +2.38873950760028E-116d, + -4.831068747037129E-118d, + -5.148989321866988E-116d, + +1.7875271881514469E-115d, + -1.1821586412088555E-114d, + +4.43247726423679E-115d, + +4.634817120492781E-114d, + +1.671311907037975E-113d, + -4.595250028278979E-113d, + -5.905511605694905E-113d, + -1.3657642265608213E-112d, + +2.881416869529271E-112d, + +2.1253302469985373E-111d, + -5.301386276260592E-111d, + +1.4198782892306878E-112d, + -3.395494928605007E-110d, + +9.284633292147283E-110d, + -6.054133004896379E-110d, + -8.324100783330331E-109d, + -2.193190669794277E-108d, + +1.3613655394659198E-107d, + +6.463452607647978E-108d, + +1.0187183636134904E-106d, + +1.0705673935006142E-106d, + +2.509050608571019E-106d, + -1.5096182622106617E-105d, + +1.7794190449526737E-106d, + +1.2261246749706581E-104d, + +2.1377905661197194E-104d, + -2.2015877944429946E-104d, + +7.873970951802825E-104d, + -1.7999197335480384E-103d, + +1.0487383011058756E-105d, + -2.9988278531841556E-102d, + +4.7976477743232285E-102d, + +3.452316818502442E-102d, + +5.89953246760617E-101d, + -4.0785601577267006E-101d, + +2.7214076662438963E-100d, + +5.237807655758373E-100d, + +6.180972117932364E-99d, + -1.3019742873005683E-98d, + +4.501188264957416E-99d, + -2.4075054705261798E-98d, + +1.6503086546628772E-97d, + -6.878666975101243E-97d, + +1.196718116616528E-96d, + +2.476190162339265E-96d, + -7.1844969234484515E-96d, + +5.088386759261555E-95d, + +6.749368983223726E-95d, + +1.965737856765605E-94d, + -5.574080023496771E-94d, + +1.2493696959436675E-93d, + +8.533262777516794E-94d, + -7.225259028588793E-93d, + -7.340587186324432E-93d, + -3.482412195764625E-92d, + +3.4742610108480497E-91d, + -7.177274244758699E-91d, + +1.2736636153072213E-90d, + -5.730160886217076E-90d, + -1.545495535488274E-89d, + +1.1304179460367007E-89d, + +1.249260560756154E-88d, + -4.7439719382414206E-88d, + +7.164663249266942E-88d, + +1.7617425105337704E-87d, + +2.4175248449172035E-87d, + -1.043079666926483E-86d, + -2.8137609614326677E-86d, + -1.2091497144395591E-85d, + +3.7944631664558904E-85d, + -2.8144926807308225E-85d, + +3.9782728352520784E-85d, + +4.313978872469646E-84d, + +5.82190887044604E-84d, + +5.883385169571802E-83d, + +1.134857098306787E-82d, + +3.468049324128309E-82d, + +2.625423995658143E-82d, + -3.42827917465521E-81d, + +5.119461911618321E-81d, + -2.134387988350615E-80d, + -4.4703076268400615E-80d, + +4.806078883451016E-80d, + +2.3820250362443495E-79d, + -7.258551497833573E-79d, + -4.0297835558876335E-78d, + +2.1424166787650852E-78d, + -3.2117127164185917E-77d, + +4.8459153070935316E-77d, + -1.766924303914492E-76d, + -2.6921749814579492E-76d, + -4.1291070428848755E-76d, + +2.2086994756104319E-75d, + -7.814146377574201E-75d, + -1.9589778310104216E-74d, + +6.52658129486538E-74d, + +1.7804909509998055E-74d, + -4.1900132227036916E-73d, + +1.5705861683841123E-72d, + -1.904714978998808E-72d, + -7.81295459930537E-72d, + +2.818537910881676E-71d, + +5.840507984320445E-71d, + +1.7331720051707087E-70d, + +1.936946987935961E-70d, + -5.86517231340979E-71d, + -1.3277440528416646E-69d, + +1.9906256185827793E-69d, + +8.668714514280051E-69d, + +6.643105496829061E-69d, + -2.5436254170647032E-67d, + -4.8279217213630774E-67d, + -1.2640304072937576E-66d, + +3.51187258511716E-66d, + +1.4199501303738373E-65d, + -1.2351697477129173E-65d, + +7.0542365522414836E-65d, + +1.030593104122615E-64d, + -5.452692909894593E-65d, + -9.415506349675128E-64d, + -3.6206211446779087E-63d, + -1.6699188275658641E-62d, + +2.287280262665656E-62d, + +7.076135457313529E-62d, + +2.9019628518165404E-61d, + -3.1305705497720186E-61d, + +2.2978757040142953E-60d, + +1.2424439441817321E-60d, + +7.140343013236265E-60d, + +8.633726388939636E-60d, + +1.3483035574114863E-58d, + +1.653701058949654E-58d, + -8.939932297357388E-58d, + -1.395320103272191E-57d, + +6.440430933947252E-58d, + -1.681200826841738E-56d, + +3.9904382022898837E-56d, + -4.870518577546228E-56d, + -1.6990896855901115E-55d, + -6.751434891261518E-56d, + -1.669012123121194E-54d, + -4.079585475491198E-54d, + -1.3070436427679952E-53d, + -3.090028378908628E-53d, + +7.468160889798606E-53d, + +6.229095980733463E-53d, + +1.4794751934479566E-52d, + +1.7444373785853918E-51d, + -5.3681978363391484E-52d, + +2.71853394036182E-51d, + -1.3334367969274016E-50d, + -1.6958057665854177E-49d, + -1.452507231312146E-49d, + +3.3855429446520427E-49d, + +4.903687986212687E-49d, + +2.2185957416622524E-48d, + -9.924196700842429E-48d, + +4.285128462851149E-47d, + +3.076063086193525E-48d, + +4.102052341676543E-46d, + +1.1745772638457318E-45d, + -5.309047216809048E-47d, + +2.72972449891179E-45d, + -1.1748423022293739E-44d, + +6.626052626622228E-44d, + +3.0227439688367925E-44d, + -4.740494808228372E-43d, + +5.926057457356852E-43d, + +3.09768273342776E-42d, + -5.589493227475577E-42d, + -8.84908716783327E-42d, + +2.3684740712822874E-41d, + +1.4836491430755657E-40d, + +4.5878801324451396E-40d, + +1.0585156316103144E-39d, + +2.3805896467049493E-39d, + +1.0285082556185196E-38d, + +2.5187968110874885E-38d, + -1.4088399542613178E-38d, + -3.00901028043488E-38d, + +2.0089026801414973E-37d, + -1.3324111396289096E-36d, + +5.458481186294964E-36d, + -4.8402541351522003E-36d, + -1.3331969720555312E-35d, + -8.248332290732976E-35d, + -1.8349670703969982E-34d, + +6.403477383195494E-34d, + +3.7813691654412385E-34d, + +2.4621305031382827E-33d, + -5.634051826192439E-33d, + +3.817173955083142E-32d, + -6.038239639506472E-32d, + -2.130447095555397E-31d, + -6.824454861992054E-31d, + -1.3455801602048414E-30d, + -2.518642767561659E-30d, + +8.082792416221215E-30d, + +4.718103502869148E-29d, + -5.607991635038776E-29d, + -1.8042191582018579E-28d, + +6.989914264479507E-28d, + -2.9031739430339586E-28d, + +6.076820259849921E-27d, + -3.24981577480893E-27d, + -2.7648210023059463E-26d, + -9.785306155980342E-26d, + +1.241529292737115E-25d, + +3.0891604448087654E-25d, + +2.3451052074796954E-24d, + +6.574128018028633E-24d, + -1.3345148716925826E-23d, + +4.3594621428644293E-23d, + -5.678896695157704E-23d, + -4.676849004137386E-23d, + -2.281578975407609E-22d, + -3.144430608076357E-21d, + +5.662033727488754E-22d, + -4.30293375386492E-21d, + +4.985137671479376E-20d, + +1.657668502165438E-19d, + -3.3878706977811337E-19d, + -7.488022803661722E-19d, + +1.725039737424264E-18d, + -6.0275040161173166E-18d, + -8.081007442213538E-19d, + +2.9257892371894816E-17d, + +1.5231541295722552E-16d, + -1.1474026049124666E-17d, + +6.890372706231206E-16d, + +2.592721454922832E-15d, + -1.1253822296423454E-15d, + -2.650684279637763E-14d, + -4.107226967119929E-15d, + -3.130508064738312E-14d, + -6.729414275200856E-14d, + -1.6166170913368169E-12d, + -1.2059301405584488E-12d, + -1.2210091619211167E-11d, + +3.695372823623631E-12d, + +5.119220484478292E-11d, + -1.0857572226543142E-10d, + -4.6490379071586397E-10d, + -4.5810381714280557E-10d, + +1.4909756678328582E-9d, + -1.3155828104004438E-8d, + -9.149755188170102E-9d, + +0.0d, + +8.254840070411029E-8d, + -1.0681886149151956E-7d, + -3.359944163407147E-8d, + -2.1275002921718894E-6d, + +1.2129920353421116E-5d, + +2.1520078872608393E-5d, + +1.0178783359926372E-4d, + -2.077077172525637E-5d, + -5.67996159922899E-5d, + +9.510567165169581E-4d, + +0.0010901978184553272d, + +0.010169003920808009d, + +0.017008920503326107d, + +0.03416477677774927d, + -0.1275278893606981d, + +0.5205078726367633d, + +0.7535752982147762d, + +1.1373305111387886d, + -3.036812739155085d, + +11.409790277969124d, + -9.516785302789955d, + -49.86840843831867d, + -393.7510973999651d, + -686.1565277058598d, + +4617.385872524165d, + -11563.161235730215d, + -8230.201383316231d, + -34460.52482632287d, + +50744.04207438878d, + +357908.46214699093d, + +1936607.425231087d, + +3222936.695160983d, + +5477052.0646243105d, + -3.517545711859706E7d, + -1.2693418527187027E8d, + -2.5316384477288628E8d, + -1.6436423669122624E8d, + +4.0889180422033095E8d, + +4.968829330953611E9d, + -3.503399598592085E9d, + +1.905394922122271E10d, + +1.0361722296739479E11d, + -5.806792575852521E10d, + +2.3454138776381036E11d, + -1.718446464587963E12d, + -1.0946634815588584E12d, + +1.6889383928999305E13d, + -3.784600043778247E13d, + +7.270965670658928E13d, + -4.9202842786896806E14d, + +4.597700093952774E14d, + +2.6113557852262235E15d, + -4.544525556171388E15d, + -9.517971970450354E15d, + -2.0634857819227416E16d, + -9.7143113104549808E16d, + -2.2667083759873216E16d, + -7.2285665164439578E17d, + +4.1215410760803866E18d, + +8.5807488300972206E18d, + +1.530436781375042E19d, + -1.5453111533064765E19d, + -1.0633845571643594E20d, + -3.512380426745336E20d, + +3.7734658676841284E20d, + -3.855478664503271E21d, + +7.984485303520287E21d, + -1.2296934902142301E22d, + +1.042139023692827E22d, + +1.2167897656061312E23d, + +9.22064170155394E22d, + +3.965171513035854E23d, + -4.135121057126514E24d, + -7.944341754299148E24d, + +1.4715152230577016E25d, + -3.0635272288480756E25d, + -9.54468158713835E25d, + +1.5411775738825048E25d, + -8.274711842374368E26d, + -1.0028324930788433E27d, + +5.189062091114782E27d, + -2.8583500869462184E28d, + -5.198295198128238E28d, + +2.9758750368256437E29d, + +3.216046320616945E29d, + -1.7846700158234043E30d, + +3.847174961282827E30d, + +9.026991921214922E30d, + +4.1358029739592175E30d, + -6.461509354879894E29d, + +9.704297297526684E31d, + +2.9731739067444943E32d, + +9.97728609663656E32d, + +3.1149346370027763E33d, + +2.0051635097366476E34d, + +2.819272221032373E34d, + +1.6266731695798413E34d, + +1.998050894021586E35d, + -6.1633417615076335E35d, + +2.2505716077585116E36d, + +1.9299691540987203E36d, + +8.006569251375383E36d, + -3.785295042408568E37d, + -1.1870498357197593E38d, + +1.0010529668998112E38d, + +1.3240710866573994E38d, + +2.6888010385137123E39d, + +1.7400655988987023E39d, + -6.402740469853475E39d, + -3.93114092562274E40d, + +1.2363717201084252E41d, + -1.9219116633978794E41d, + -1.347867098583136E42d, + +7.87675118338788E41d, + +3.3932984011177642E41d, + -1.9872713979884691E43d, + +2.220208491349658E43d, + -3.466267817480825E43d, + +3.19462030745197E44d, + -9.841244788104406E44d, + -2.2676593395522725E45d, + -1.1349246400274207E46d, + -1.1700910284427406E46d, + -3.6754317105801715E46d, + +1.7647101734915075E47d, + +2.122358392979746E47d, + +3.156243682143956E47d, + +5.356668151937413E47d, + +2.7668218233914262E48d, + +3.5127708120698784E48d, + +1.7884841356632925E49d, + +1.716531820904728E50d, + -2.9114757102866277E50d, + +1.0657703081219677E51d, + -7.512169809356372E50d, + +1.764200470879736E51d, + -1.0088898215431471E52d, + -3.1085734725176E52d, + +4.3529009584292495E52d, + -2.467842129213774E53d, + -3.9317379627195146E53d, + -4.332335454045836E52d, + +7.979013724931926E54d, + -1.5038413653121357E55d, + +9.310799925566843E55d, + -2.2042966348036592E55d, + -4.518315366841937E55d, + -6.971366338144781E56d, + -2.0461505570781806E57d, + -8.823884392655312E57d, + -1.1264032993918548E58d, + -7.692065092509875E58d, + -1.8472516879728875E59d, + +8.72220314694275E58d, + +1.6525336989036362E59d, + -3.343201925128334E60d, + +5.493352163155986E60d, + -2.548073509300398E61d, + -9.566541624209933E61d, + +4.0891054447206644E61d, + -7.724182294653349E62d, + +1.0143022354947225E63d, + -4.952031310451961E63d, + -7.877410133454722E63d, + +4.505432606253564E64d, + -7.330635250808021E64d, + -1.642361029990822E65d, + +5.982180242124184E65d, + +7.120242132370469E65d, + +5.908356249789671E66d, + -2.8477710945673134E65d, + +6.65688196961235E66d, + -9.233295580238604E67d, + +3.2850043261803593E68d, + +7.041681569694413E68d, + -1.5652761725518397E69d, + +1.5377053215489084E68d, + +1.282130763903269E70d, + -2.380286345847567E70d, + -7.207022875977515E70d, + +2.7641662602473095E71d, + +7.685235201534525E71d, + +4.3239378585884645E70d, + -1.6840562544109314E72d, + -5.04128025464686E71d, + +5.4557485189210095E73d, + +7.160277784358221E73d, + +7.636179075087608E73d, + -8.18804507680012E74d, + +2.807397988979441E75d, + +2.165163304600171E75d, + -1.3208450062862734E76d, + -5.1939252391404724E76d, + -6.985952908805853E76d, + -1.6259920998287064E77d, + +6.098975200926637E77d, + -5.63383579957466E77d, + -1.5876819186852907E78d, + +2.1487475413123092E79d, + -3.987619123706934E79d, + +9.772655251656639E79d, + -1.638756156057952E79d, + -7.83892088580041E80d, + +1.274413296252691E81d, + +2.51946651720982E81d, + -2.516866097506943E81d, + +1.053956282234684E82d, + +1.8279051206232177E83d, + +1.2250764591564252E82d, + -4.0353723442917463E83d, + -1.4121324224340735E84d, + -5.45287716696021E84d, + -1.7514953095665195E85d, + -5.0706081370522526E85d, + -4.35799392139009E85d, + -3.982538093450217E86d, + -1.4591838284752642E87d, + +2.5313735821872488E87d, + -3.718501227185903E86d, + -1.3907979640327008E88d, + -5.79002114093961E86d, + -1.2500675565781447E89d, + +4.8182788286170926E89d, + -1.7198866036687559E90d, + -4.690417668647599E88d, + +1.3020631859056421E91d, + -1.3850458263351744E91d, + +4.87301010703588E91d, + -1.695546877943826E92d, + -1.6353756659909833E92d, + -1.5483926773679628E93d, + -1.8921091400297595E93d, + -6.183525570536406E93d, + -4.987913342551977E93d, + +1.0186485886120274E93d, + -1.5343120819745468E95d, + -5.262123923229857E95d, + +1.618327917706804E96d, + -4.135185828158998E96d, + -8.016793741945299E96d, + -3.0399439534134115E97d, + -1.2319346292749103E98d, + +7.536337311795176E97d, + -3.577715974851322E98d, + +2.0521614818695524E99d, + +1.2627736197958951E98d, + -5.206910481915062E99d, + +3.0974593993948837E100d, + -9.522726334561169E100d, + -1.1909272509710985E100d, + -5.056512677995137E101d, + +2.0902045062932175E102d, + +6.243669516810509E102d, + -1.7375090618655787E103d, + -2.5445477450140954E103d, + +3.619891246849381E103d, + +8.90737333900943E103d, + -2.7897360297480367E104d, + +1.3725786770437066E105d, + -8.316530604593264E105d, + -6.054541568735673E105d, + +7.523374196797555E105d, + +1.1475955030427985E107d, + +1.5260756679495707E107d, + +7.370294848920685E107d, + +1.3608995799112174E108d, + +1.0700758858011432E108d, + -4.989318918773146E108d, + -1.6629755787634093E108d, + +7.635999584053557E109d, + +1.892621828736983E109d, + -6.793094743406533E110d, + -8.160628910742724E110d, + -7.724219106106896E111d, + -1.6059226011778748E112d, + -1.5277127454062126E112d, + +3.911086668967361E112d, + +3.529920406834134E113d, + -4.3991443996021166E113d, + -1.2631909085915044E114d, + +3.8656278695544835E114d, + +1.71845288713123E115d, + +3.7660598745907915E115d, + -4.048086182363988E115d, + +2.3093822298965837E116d, + -9.684925795536813E116d, + -3.137992585221854E117d, + -5.637415935329794E117d, + -1.5536658521931418E118d, + -6.336314643222911E118d, + +8.550658957115427E118d, + -5.591880480212007E119d, + +2.4137404318673354E119d, + -2.631656656397244E120d, + -7.653117429165879E119d, + -4.073965591445897E121d, + +3.634781057940233E121d, + +4.537273754534966E121d, + -2.5138919966097735E122d, + -1.0292817180691822E123d, + -1.4265564976097062E122d, + +6.000235114895513E123d, + +4.186590347846346E124d, + -1.8950538406321535E124d, + +7.716762345695022E124d, + -4.443798187035849E125d, + -2.268994961992292E125d, + -2.8169291774231604E126d, + -2.749127978087685E126d, + -2.2929764629585683E126d, + -7.369842361872221E127d, + +2.81312841469177E128d, + +2.7856896414497757E128d, + -3.096733638475319E128d, + -5.4199510725063615E129d, + -7.315860999413894E129d, + +3.6424644535156437E130d, + -7.886250961456327E130d, + +5.289988151341401E130d, + +2.7758613753516344E131d, + -2.738246981762776E132d, + -2.2667181460478093E132d, + -3.614672661225457E131d, + +2.325337720526947E133d, + +4.16603235883392E133d, + -6.50348962894948E133d, + +3.851445905038431E134d, + -5.46060534001412E134d, + +5.4679180659102885E135d, + -3.037477806841494E135d, + -3.0417051809209134E136d, + -6.995964550587914E136d, + -3.6897084415718804E137d, + -6.938000231893302E137d, + +2.403806217004454E138d, + -3.4552363953199905E138d, + +7.3409917428393E138d, + -1.7445917446236717E139d, + -6.680679913078676E139d, + -8.193572619487537E139d, + +5.337290292186291E139d, + -3.951314467739045E140d, + -4.4662073456574476E141d, + +6.249381778908997E141d, + -2.928362616578011E142d, + -1.6661676835672304E143d, + -1.974465323891493E143d, + +1.3083870531380308E144d, + -2.382825271750576E144d, + -5.4826958838142734E144d, + +1.5340733916570804E145d, + -3.1327120557842516E145d, + +1.5790297768522832E146d, + +1.1518771984292262E146d, + -4.789917000227385E145d, + -8.689594184775204E146d, + +3.0680417869552433E146d, + +4.877860620031438E147d, + -3.4650891244084597E148d, + +1.8702183451052442E149d, + -3.5727227900139915E148d, + -1.3457821696677932E150d, + +3.3212950284273017E149d, + +7.316033240396569E150d, + -7.187723217018267E150d, + -8.537194547485455E150d, + -1.4561530066010593E152d, + -7.548155147049997E151d, + +1.0047353208353007E153d, + -1.2489460589853119E153d, + +4.426120229279107E153d, + -2.5466223330961086E154d, + +8.831699889789037E154d, + -2.0258084311749475E155d, + -5.525009099476396E155d, + -1.0235056525096769E156d, + -4.117971654572494E154d, + -4.7559175309753334E156d, + -1.4656240137098836E157d, + -7.675790582869644E157d, + -1.0126616322947826E158d, + +7.084865265284368E158d, + -9.374695893307895E158d, + +2.05597910889115E159d, + -7.368602086210704E159d, + -1.6167825196198978E160d, + +2.3832096207000712E160d, + +1.3166970112139726E161d, + -6.432337568761393E161d, + +2.9279594746502846E161d, + +4.8926595743317624E162d, + +1.2704793774453618E163d, + -1.1345910784680524E163d, + +7.75933511025868E163d, + -1.1441115218462356E163d, + +5.162248481759758E164d, + +6.362563919556132E164d, + -2.8362173224732088E165d, + -4.342161053332263E165d, + +4.388125271425036E166d, + -7.049068240916723E166d, + +3.8520227881415595E166d, + +2.9274120974020826E167d, + -7.500936767542933E167d, + -6.540181860667302E168d, + +4.664436780622191E168d, + -1.436111169285268E169d, + -1.0407581736224179E170d, + -2.7670181051374297E170d, + -6.788169932297778E170d, + +1.6997387217850427E171d, + -1.0965324942770584E171d, + +9.841563119484623E171d, + +3.175748919314254E172d, + +2.9621181706527444E172d, + -3.30101656090905E173d, + -3.791840683760427E173d, + -2.841347842666459E174d, + -7.836327226971707E174d, + +9.650358667643114E174d, + +5.9994277301267294E175d, + -6.0490084078440215E175d, + -2.8964095485948707E176d, + +9.916187343252014E175d, + +2.7535627955313556E176d, + +3.886891475472745E177d, + +3.1962472803616787E178d, + -5.50599549115449E178d, + +5.672812341879918E178d, + -3.295268490032475E179d, + +9.761163062156018E179d, + +3.107837179570674E180d, + +3.3894811576571423E179d, + -5.235397688850367E180d, + -5.004237248003625E181d, + -1.7544995191195304E182d, + +2.645622651144787E182d, + -3.459885432869825E182d, + -4.0361435606199565E183d, + -1.8382923511801317E183d, + -1.7332235571505177E184d, + +2.847653455671381E184d, + +1.7991060813894693E185d, + -2.0937429891059164E185d, + +5.744446753652847E185d, + -2.1349396267483754E184d, + -1.2542332720182776E186d, + +3.3730714236579374E186d, + -5.923734606208998E187d, + +2.24669039465627E188d, + -1.2588742703536392E188d, + +1.474522484905093E189d, + -2.4006971787803736E189d, + -3.52597540499141E189d, + +2.6676722922838097E190d, + +5.27579825970359E190d, + +2.1360492104281465E191d, + +1.9442210982008953E191d, + -1.4691239161932232E190d, + +3.8218180377739526E192d, + +1.9722862688653467E192d, + +3.047601928063002E193d, + +1.6747356805175311E193d, + +7.710512446969693E192d, + +1.7780021277684035E194d, + -1.4015110811648513E195d, + +4.0447634595724164E195d, + +9.023639664212642E195d, + +1.976868146639626E196d, + -9.084495133765657E196d, + -1.2023077889892748E196d, + +5.7455368446308694E197d, + -1.7766273910482863E198d, + +3.5590470673352285E198d, + +1.1304970373249033E199d, + +1.6496143246803731E199d, + -2.394588390685223E199d, + -1.4677321100833294E199d, + -1.1843870433971731E201d, + -1.8853982316037226E201d, + +2.8829871423339434E201d, + +5.369687677705385E200d, + +1.8356062677502141E202d, + -1.5544655377217875E203d, + +2.955364187248884E203d, + -2.7651059253677425E203d, + +9.903174064539538E203d, + -3.284204788892967E204d, + -1.5843229740595697E205d, + +5.333371443528904E204d, + +1.2781631468016048E205d, + +3.2188292385399854E205d, + -6.619064395428225E206d, + +1.291561142865928E207d, + +1.3142988156905172E207d, + -1.3841980097978606E208d, + +6.380177790989479E207d, + +1.0386032577072378E209d, + +2.7681631086098026E209d, + -9.053874899534375E209d, + +1.2424707839848734E210d, + +1.045546633850141E211d, + -1.2448938139338362E211d, + +7.221902646057552E211d, + +6.651345415954053E211d, + -5.8180712702152444E212d, + +5.275183961165903E212d, + +5.092753117288608E212d, + -2.437796532151255E213d, + +1.3480763914637323E214d, + +5.619995933180841E214d, + +2.547000388735681E214d, + +4.817319356453926E214d, + -7.897146442236022E215d, + -7.93844120619577E215d, + -4.9489938500591624E216d, + -2.862720607805682E216d, + -2.9275804461593914E217d, + -3.411186219855533E217d, + -2.0110092718356274E218d, + -8.472642266772353E218d, + -4.357990742470246E217d, + +4.793444363780116E219d, + +1.6544084224626834E220d, + -6.017988576347111E220d, + -3.580397221598409E220d, + -4.7208848667217906E221d, + -7.724899660259369E221d, + -2.4459728627968634E222d, + +3.667348665023154E221d, + +4.544122762558404E223d, + -4.0573420909530794E223d, + -3.2552002992257195E223d, + -6.488296536838142E224d, + +1.7544839352461719E224d, + -4.0873400635183405E225d, + -8.833499967268279E225d, + -1.0953484767704112E226d, + -8.56825295972308E226d, + -1.8097633115378247E227d, + -6.171564449018882E227d, + -4.351843341274115E227d, + +2.8032429752543687E228d, + -1.0065901934522996E229d, + +9.863720960170636E228d, + -9.481088691357648E229d, + -1.6843492713373762E229d, + -1.3282890219894906E230d, + +6.883577595238845E230d, + -1.153577281189635E231d, + -8.009548754642203E231d, + -4.722612904888278E232d, + -4.768909872963015E232d, + +3.2542391242036633E233d, + +6.513425781583774E233d, + -1.8889614379831606E233d, + -2.227647301474917E234d, + -4.7971208532986115E234d, + +6.693500938105557E234d, + -6.587776621471115E234d, + +3.0099905634916516E236d, + -4.6694407626686244E235d, + +2.965546585110978E236d, + +5.771457643937893E237d, + -9.029878114318277E237d, + +8.169926810324408E237d, + -1.779945804977441E239d, + +4.1218749988429474E239d, + +7.201319954099161E239d, + -1.530845432304069E240d, + -3.861762510530086E240d, + -2.4090696463777446E241d, + -1.8196842273916379E241d, + -1.7959243076374794E242d, + -3.7257346819782323E242d, + +3.413310324247329E242d, + -2.0406580894051073E243d, + -1.5335923091350053E243d, + -1.056727406551016E244d, + -4.6753408714233723E244d, + -2.0697130057384643E245d, + -1.0356006160554071E245d, + +1.1339195187304043E246d, + +1.792783182582235E246d, + +9.599214853681978E245d, + +1.5367645598839362E247d, + +2.934570385464815E247d, + -1.6411525886171892E248d, + +2.2638862982382794E248d, + -1.2268014119628852E249d, + +4.737693450915584E247d, + +6.3818993714899675E249d, + +1.2639113706171572E250d, + -4.011320021817099E249d, + -5.2744376732859406E250d, + -3.732266217624991E251d, + +1.7591819833844019E252d, + -3.292458622014749E252d, + -9.161340309319204E252d, + -1.728610646009749E253d, + +1.1698424008604891E254d, + -1.8494343291160577E254d, + +2.0568656302182574E254d, + +1.0537591246531136E255d, + +1.803052068234866E254d, + -1.053036399720808E256d, + +2.1836166619192508E256d, + +1.0368403169781264E257d, + -2.0648015610276362E257d, + +8.426174035728768E257d, + -1.3577357192972777E258d, + +2.1313950901331177E258d, + +8.919141843592823E258d, + -1.1800039972549816E259d, + -1.1878772398311421E260d, + -1.538273497873993E260d, + -4.51305093266001E260d, + +1.1241179396053055E261d, + +6.154786508667658E261d, + -1.0626125049032536E262d, + -1.8908603201210102E262d, + -4.571195152299358E262d, + +1.526100002923062E263d, + -9.457084582570225E263d, + -1.5460500618825853E264d, + -5.598276199126451E264d, + -1.2074097381167957E265d, + -3.015972957475025E265d, + +1.4345106852061226E265d, + +8.28479585346867E265d, + -3.118741081244705E266d, + -1.2054747399765794E266d, + +3.4454766202661184E267d, + +1.1279135096919439E268d, + +1.2066382528772518E268d, + +1.1984128162292276E269d, + +3.685169705587367E268d, + +6.570047690198998E269d, + +1.8836492887460383E270d, + +7.4364594917181125E270d, + +1.2773080633674971E271d, + +1.8928981707279692E271d, + +4.039437286589528E271d, + +1.785277385538302E272d, + -6.017681359527226E272d, + +1.9716943051755635E273d, + -8.772048092842086E271d, + +1.5645672698520312E274d, + -3.7979660725865874E274d, + +5.324902289537048E274d, + -1.8806716685063293E274d, + +9.320900373401115E275d, + +1.4615985810260016E275d, + +8.321226457219046E276d, + -4.608112855795952E276d, + -3.476352191116455E277d, + +5.266381689434054E277d, + -9.622106063561645E277d, + +4.1719443712336026E278d, + +4.222411269063919E279d, + -6.714376022102489E279d, + -1.0732735585199074E280d, + -2.5866883048437488E280d, + -1.1306860837934988E281d, + +3.690690354793168E281d, + -5.5299180508885456E281d, + +2.7006726968568243E282d, + +4.135457669031131E282d, + +2.8401159516008676E283d, + +5.127265762024798E283d, + -3.4893601256685762E283d, + -1.145160459652136E283d, + +2.1742808735341656E284d, + +4.656972469326391E285d, + +7.672307991205681E285d, + +1.5781599575584034E286d, + +4.218682431618625E286d, + -2.4602260687026867E287d, + +2.7211316452521414E287d, + -1.8740018211089393E288d, + +2.6367639658206183E288d, + -3.102678910525039E288d, + +1.1992295328636466E289d, + +6.8190133180135345E289d, + +5.783203879030497E289d, + +5.171047077293295E290d, + +1.8396930096213817E290d, + +1.4977047507315718E290d, + +1.0672499803427623E292d, + +3.3310942289102464E291d, + -7.962256961838823E292d, + +1.7396889119023863E293d, + +3.8072183820435085E293d, + +2.2772059538865722E294d, + -2.0549866377878678E294d, + -1.2277120342804144E295d, + -3.609949022969024E295d, + +1.1479863663699871E296d, + -1.5314373779304356E296d, + -2.2537635160762597E296d, + -6.1370690793508674E296d, + -4.996854125490041E297d, + -6.883499809714189E297d, + -2.595456638706416E298d, + -1.1892631528580186E299d, + -1.4672600326020399E299d, + -3.200068509818696E299d, + -7.126913872617518E298d, + -3.3655587417265094E300d, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + Double.NaN, + }; + + + /** Exponential over the range of 0 - 1 in increments of 2^-10 + * exp(x/1024) = expFracTableA[x] + expFracTableB[x]. + * 1024 = 2^10 + */ + private static final double[] EXP_FRAC_A = new double[] { + +1.0d, + +1.0009770393371582d, + +1.0019550323486328d, + +1.0029339790344238d, + +1.0039138793945312d, + +1.004894733428955d, + +1.0058765411376953d, + +1.006859302520752d, + +1.007843017578125d, + +1.0088276863098145d, + +1.0098135471343994d, + +1.0108001232147217d, + +1.0117876529693604d, + +1.0127761363983154d, + +1.013765811920166d, + +1.014756202697754d, + +1.0157477855682373d, + +1.016740083694458d, + +1.0177335739135742d, + +1.0187277793884277d, + +1.0197231769561768d, + +1.0207195281982422d, + +1.021716833114624d, + +1.0227150917053223d, + +1.023714303970337d, + +1.024714469909668d, + +1.0257158279418945d, + +1.0267179012298584d, + +1.0277209281921387d, + +1.0287251472473145d, + +1.0297303199768066d, + +1.0307364463806152d, + +1.0317435264587402d, + +1.0327515602111816d, + +1.0337605476379395d, + +1.0347704887390137d, + +1.0357816219329834d, + +1.0367934703826904d, + +1.037806510925293d, + +1.038820505142212d, + +1.0398354530334473d, + +1.040851354598999d, + +1.0418684482574463d, + +1.0428862571716309d, + +1.043905258178711d, + +1.0449252128601074d, + +1.0459461212158203d, + +1.0469679832458496d, + +1.0479910373687744d, + +1.0490150451660156d, + +1.0500397682189941d, + +1.0510656833648682d, + +1.0520927906036377d, + +1.0531206130981445d, + +1.0541496276855469d, + +1.0551795959472656d, + +1.0562105178833008d, + +1.0572423934936523d, + +1.0582754611968994d, + +1.059309482574463d, + +1.0603444576263428d, + +1.061380386352539d, + +1.0624175071716309d, + +1.06345534324646d, + +1.0644943714141846d, + +1.0655345916748047d, + +1.066575527191162d, + +1.067617654800415d, + +1.0686607360839844d, + +1.0697050094604492d, + +1.0707499980926514d, + +1.071796178817749d, + +1.072843313217163d, + +1.0738916397094727d, + +1.0749409198760986d, + +1.075991153717041d, + +1.0770423412322998d, + +1.078094720840454d, + +1.0791480541229248d, + +1.080202341079712d, + +1.0812578201293945d, + +1.0823142528533936d, + +1.083371639251709d, + +1.08443021774292d, + +1.0854897499084473d, + +1.086550235748291d, + +1.0876119136810303d, + +1.088674545288086d, + +1.089738130569458d, + +1.0908029079437256d, + +1.0918686389923096d, + +1.092935562133789d, + +1.094003438949585d, + +1.0950722694396973d, + +1.096142053604126d, + +1.0972130298614502d, + +1.09828519821167d, + +1.099358320236206d, + +1.1004323959350586d, + +1.1015074253082275d, + +1.102583646774292d, + +1.103661060333252d, + +1.1047391891479492d, + +1.105818748474121d, + +1.1068990230560303d, + +1.107980489730835d, + +1.1090631484985352d, + +1.1101467609405518d, + +1.1112313270568848d, + +1.1123170852661133d, + +1.1134037971496582d, + +1.1144917011260986d, + +1.1155805587768555d, + +1.1166706085205078d, + +1.1177616119384766d, + +1.1188538074493408d, + +1.1199469566345215d, + +1.1210410594940186d, + +1.1221363544464111d, + +1.1232328414916992d, + +1.1243302822113037d, + +1.1254286766052246d, + +1.126528263092041d, + +1.127629041671753d, + +1.1287307739257812d, + +1.129833459854126d, + +1.1309373378753662d, + +1.132042407989502d, + +1.133148431777954d, + +1.1342556476593018d, + +1.1353638172149658d, + +1.1364731788635254d, + +1.1375834941864014d, + +1.1386950016021729d, + +1.1398074626922607d, + +1.1409211158752441d, + +1.142035961151123d, + +1.1431517601013184d, + +1.14426851272583d, + +1.1453864574432373d, + +1.14650559425354d, + +1.1476259231567383d, + +1.148747205734253d, + +1.149869441986084d, + +1.1509928703308105d, + +1.1521174907684326d, + +1.153243064880371d, + +1.154369831085205d, + +1.1554977893829346d, + +1.1566267013549805d, + +1.1577568054199219d, + +1.1588881015777588d, + +1.160020351409912d, + +1.161153793334961d, + +1.1622881889343262d, + +1.163423776626587d, + +1.1645605564117432d, + +1.1656982898712158d, + +1.166837215423584d, + +1.1679773330688477d, + +1.1691184043884277d, + +1.1702606678009033d, + +1.1714041233062744d, + +1.172548532485962d, + +1.173694133758545d, + +1.1748409271240234d, + +1.1759889125823975d, + +1.177137851715088d, + +1.1782879829406738d, + +1.1794393062591553d, + +1.1805915832519531d, + +1.1817450523376465d, + +1.1828997135162354d, + +1.1840553283691406d, + +1.1852121353149414d, + +1.1863701343536377d, + +1.1875293254852295d, + +1.1886897087097168d, + +1.1898510456085205d, + +1.1910135746002197d, + +1.1921772956848145d, + +1.1933419704437256d, + +1.1945080757141113d, + +1.1956751346588135d, + +1.1968433856964111d, + +1.1980125904083252d, + +1.1991832256317139d, + +1.200354814529419d, + +1.2015275955200195d, + +1.2027015686035156d, + +1.2038767337799072d, + +1.2050528526306152d, + +1.2062301635742188d, + +1.2074086666107178d, + +1.2085883617401123d, + +1.2097692489624023d, + +1.210951328277588d, + +1.2121343612670898d, + +1.2133188247680664d, + +1.2145042419433594d, + +1.2156908512115479d, + +1.2168786525726318d, + +1.2180676460266113d, + +1.2192575931549072d, + +1.2204489707946777d, + +1.2216413021087646d, + +1.222834825515747d, + +1.224029779434204d, + +1.2252256870269775d, + +1.2264227867126465d, + +1.227621078491211d, + +1.2288203239440918d, + +1.2300209999084473d, + +1.2312228679656982d, + +1.2324256896972656d, + +1.2336299419403076d, + +1.234835147857666d, + +1.23604154586792d, + +1.2372493743896484d, + +1.2384581565856934d, + +1.2396681308746338d, + +1.2408792972564697d, + +1.2420918941497803d, + +1.2433054447174072d, + +1.2445201873779297d, + +1.2457361221313477d, + +1.2469532489776611d, + +1.2481715679168701d, + +1.2493910789489746d, + +1.2506117820739746d, + +1.2518336772918701d, + +1.2530567646026611d, + +1.2542810440063477d, + +1.2555065155029297d, + +1.2567331790924072d, + +1.2579610347747803d, + +1.2591900825500488d, + +1.260420322418213d, + +1.2616519927978516d, + +1.2628846168518066d, + +1.2641184329986572d, + +1.2653534412384033d, + +1.266589879989624d, + +1.2678272724151611d, + +1.2690660953521729d, + +1.27030611038208d, + +1.2715470790863037d, + +1.272789478302002d, + +1.2740330696105957d, + +1.275277853012085d, + +1.2765238285064697d, + +1.27777099609375d, + +1.2790195941925049d, + +1.2802691459655762d, + +1.281519889831543d, + +1.2827720642089844d, + +1.2840254306793213d, + +1.2852799892425537d, + +1.2865357398986816d, + +1.287792682647705d, + +1.2890510559082031d, + +1.2903103828430176d, + +1.2915711402893066d, + +1.2928330898284912d, + +1.2940962314605713d, + +1.2953605651855469d, + +1.296626091003418d, + +1.2978930473327637d, + +1.2991611957550049d, + +1.3004305362701416d, + +1.3017010688781738d, + +1.3029727935791016d, + +1.304245948791504d, + +1.3055200576782227d, + +1.3067958354949951d, + +1.308072566986084d, + +1.3093504905700684d, + +1.3106298446655273d, + +1.3119103908538818d, + +1.3131921291351318d, + +1.3144752979278564d, + +1.3157594203948975d, + +1.317044973373413d, + +1.3183319568634033d, + +1.31961989402771d, + +1.3209092617034912d, + +1.322199821472168d, + +1.3234915733337402d, + +1.324784755706787d, + +1.3260791301727295d, + +1.3273746967315674d, + +1.3286716938018799d, + +1.329969882965088d, + +1.3312692642211914d, + +1.3325698375701904d, + +1.333871841430664d, + +1.3351752758026123d, + +1.336479663848877d, + +1.3377854824066162d, + +1.339092493057251d, + +1.3404009342193604d, + +1.3417105674743652d, + +1.3430213928222656d, + +1.3443336486816406d, + +1.3456470966339111d, + +1.3469617366790771d, + +1.3482778072357178d, + +1.349595069885254d, + +1.3509137630462646d, + +1.352233648300171d, + +1.3535549640655518d, + +1.3548774719238281d, + +1.356201171875d, + +1.3575263023376465d, + +1.3588526248931885d, + +1.360180139541626d, + +1.361509084701538d, + +1.3628394603729248d, + +1.364171028137207d, + +1.3655037879943848d, + +1.366837978363037d, + +1.368173360824585d, + +1.3695101737976074d, + +1.3708481788635254d, + +1.372187614440918d, + +1.373528242111206d, + +1.3748703002929688d, + +1.376213550567627d, + +1.3775582313537598d, + +1.378904104232788d, + +1.380251407623291d, + +1.3815999031066895d, + +1.3829498291015625d, + +1.384300947189331d, + +1.3856534957885742d, + +1.387007236480713d, + +1.3883624076843262d, + +1.389719009399414d, + +1.3910768032073975d, + +1.3924360275268555d, + +1.393796443939209d, + +1.395158290863037d, + +1.3965213298797607d, + +1.397885799407959d, + +1.3992514610290527d, + +1.4006187915802002d, + +1.401987075805664d, + +1.4033570289611816d, + +1.4047281742095947d, + +1.4061005115509033d, + +1.4074742794036865d, + +1.4088494777679443d, + +1.4102261066436768d, + +1.4116039276123047d, + +1.4129831790924072d, + +1.4143636226654053d, + +1.415745496749878d, + +1.4171288013458252d, + +1.418513298034668d, + +1.4198992252349854d, + +1.4212865829467773d, + +1.4226751327514648d, + +1.424065351486206d, + +1.4254565238952637d, + +1.426849365234375d, + +1.4282433986663818d, + +1.4296388626098633d, + +1.4310357570648193d, + +1.432433843612671d, + +1.433833360671997d, + +1.4352343082427979d, + +1.4366366863250732d, + +1.4380402565002441d, + +1.4394452571868896d, + +1.4408516883850098d, + +1.4422595500946045d, + +1.4436686038970947d, + +1.4450790882110596d, + +1.446491003036499d, + +1.447904348373413d, + +1.4493188858032227d, + +1.450735092163086d, + +1.4521524906158447d, + +1.4535713195800781d, + +1.454991340637207d, + +1.4564130306243896d, + +1.4578359127044678d, + +1.4592602252960205d, + +1.460686206817627d, + +1.4621131420135498d, + +1.4635417461395264d, + +1.4649717807769775d, + +1.4664030075073242d, + +1.4678359031677246d, + +1.4692699909210205d, + +1.470705509185791d, + +1.4721424579620361d, + +1.4735808372497559d, + +1.475020408630371d, + +1.47646164894104d, + +1.4779040813446045d, + +1.4793481826782227d, + +1.4807934761047363d, + +1.4822404384613037d, + +1.4836885929107666d, + +1.485138177871704d, + +1.4865891933441162d, + +1.488041639328003d, + +1.4894955158233643d, + +1.4909508228302002d, + +1.4924075603485107d, + +1.493865728378296d, + +1.4953253269195557d, + +1.49678635597229d, + +1.49824857711792d, + +1.4997124671936035d, + +1.5011777877807617d, + +1.5026445388793945d, + +1.504112720489502d, + +1.505582332611084d, + +1.5070531368255615d, + +1.5085256099700928d, + +1.5099995136260986d, + +1.511474847793579d, + +1.5129516124725342d, + +1.5144298076629639d, + +1.5159096717834473d, + +1.5173907279968262d, + +1.5188732147216797d, + +1.5203571319580078d, + +1.5218427181243896d, + +1.523329496383667d, + +1.524817943572998d, + +1.5263078212738037d, + +1.5277988910675049d, + +1.5292916297912598d, + +1.5307857990264893d, + +1.5322813987731934d, + +1.5337786674499512d, + +1.5352771282196045d, + +1.5367772579193115d, + +1.538278579711914d, + +1.5397815704345703d, + +1.5412859916687012d, + +1.5427920818328857d, + +1.5442993640899658d, + +1.5458080768585205d, + +1.547318458557129d, + +1.548830270767212d, + +1.5503435134887695d, + +1.5518584251403809d, + +1.5533745288848877d, + +1.5548923015594482d, + +1.5564115047454834d, + +1.5579321384429932d, + +1.5594542026519775d, + +1.5609779357910156d, + +1.5625030994415283d, + +1.5640296936035156d, + +1.5655577182769775d, + +1.5670874118804932d, + +1.5686185359954834d, + +1.5701510906219482d, + +1.5716853141784668d, + +1.5732207298278809d, + +1.5747578144073486d, + +1.5762965679168701d, + +1.577836513519287d, + +1.5793781280517578d, + +1.5809214115142822d, + +1.5824658870697021d, + +1.5840120315551758d, + +1.5855598449707031d, + +1.587108850479126d, + +1.5886595249176025d, + +1.5902118682861328d, + +1.5917654037475586d, + +1.593320608139038d, + +1.5948774814605713d, + +1.596435785293579d, + +1.5979955196380615d, + +1.5995566844940186d, + +1.6011195182800293d, + +1.6026840209960938d, + +1.6042497158050537d, + +1.6058173179626465d, + +1.6073861122131348d, + +1.6089565753936768d, + +1.6105287075042725d, + +1.6121022701263428d, + +1.6136772632598877d, + +1.6152539253234863d, + +1.6168320178985596d, + +1.6184117794036865d, + +1.619992971420288d, + +1.6215758323669434d, + +1.6231601238250732d, + +1.6247460842132568d, + +1.626333475112915d, + +1.627922534942627d, + +1.6295130252838135d, + +1.6311051845550537d, + +1.6326987743377686d, + +1.634294033050537d, + +1.6358907222747803d, + +1.6374890804290771d, + +1.6390891075134277d, + +1.640690565109253d, + +1.6422934532165527d, + +1.6438980102539062d, + +1.6455042362213135d, + +1.6471118927001953d, + +1.6487212181091309d, + +1.6503322124481201d, + +1.651944637298584d, + +1.6535584926605225d, + +1.6551742553710938d, + +1.6567914485931396d, + +1.6584100723266602d, + +1.6600303649902344d, + +1.6616523265838623d, + +1.663275957107544d, + +1.6649010181427002d, + +1.666527509689331d, + +1.6681559085845947d, + +1.669785737991333d, + +1.671417236328125d, + +1.6730501651763916d, + +1.674684762954712d, + +1.676321029663086d, + +1.6779589653015137d, + +1.679598331451416d, + +1.681239366531372d, + +1.6828820705413818d, + +1.6845262050628662d, + +1.6861720085144043d, + +1.687819480895996d, + +1.6894686222076416d, + +1.6911191940307617d, + +1.6927716732025146d, + +1.6944255828857422d, + +1.6960809230804443d, + +1.6977381706237793d, + +1.6993968486785889d, + +1.7010571956634521d, + +1.7027192115783691d, + +1.7043828964233398d, + +1.7060482501983643d, + +1.7077150344848633d, + +1.709383487701416d, + +1.7110536098480225d, + +1.7127254009246826d, + +1.7143988609313965d, + +1.716073989868164d, + +1.7177505493164062d, + +1.7194287776947021d, + +1.7211089134216309d, + +1.7227904796600342d, + +1.7244737148284912d, + +1.726158618927002d, + +1.7278449535369873d, + +1.7295331954956055d, + +1.7312231063842773d, + +1.7329144477844238d, + +1.7346076965332031d, + +1.736302375793457d, + +1.7379989624023438d, + +1.739696979522705d, + +1.7413966655731201d, + +1.7430980205535889d, + +1.7448012828826904d, + +1.7465059757232666d, + +1.7482123374938965d, + +1.74992036819458d, + +1.7516300678253174d, + +1.7533416748046875d, + +1.7550547122955322d, + +1.7567694187164307d, + +1.7584857940673828d, + +1.7602040767669678d, + +1.7619237899780273d, + +1.7636451721191406d, + +1.7653684616088867d, + +1.7670931816101074d, + +1.768819808959961d, + +1.770547866821289d, + +1.77227783203125d, + +1.7740094661712646d, + +1.775742769241333d, + +1.777477741241455d, + +1.7792143821716309d, + +1.7809526920318604d, + +1.7826926708221436d, + +1.7844345569610596d, + +1.7861778736114502d, + +1.7879230976104736d, + +1.7896699905395508d, + +1.7914185523986816d, + +1.7931687831878662d, + +1.7949209213256836d, + +1.7966744899749756d, + +1.7984299659729004d, + +1.800187110900879d, + +1.8019459247589111d, + +1.8037066459655762d, + +1.8054687976837158d, + +1.8072328567504883d, + +1.8089985847473145d, + +1.8107659816741943d, + +1.812535285949707d, + +1.8143062591552734d, + +1.8160789012908936d, + +1.8178532123565674d, + +1.819629430770874d, + +1.8214070796966553d, + +1.8231868743896484d, + +1.8249680995941162d, + +1.8267512321472168d, + +1.828536033630371d, + +1.830322504043579d, + +1.83211088180542d, + +1.8339009284973145d, + +1.8356926441192627d, + +1.8374862670898438d, + +1.8392815589904785d, + +1.841078519821167d, + +1.8428773880004883d, + +1.8446779251098633d, + +1.846480131149292d, + +1.8482842445373535d, + +1.8500902652740479d, + +1.8518977165222168d, + +1.8537070751190186d, + +1.8555183410644531d, + +1.8573312759399414d, + +1.8591458797454834d, + +1.8609623908996582d, + +1.8627805709838867d, + +1.864600658416748d, + +1.866422414779663d, + +1.8682458400726318d, + +1.8700714111328125d, + +1.8718984127044678d, + +1.8737273216247559d, + +1.8755581378936768d, + +1.8773906230926514d, + +1.8792247772216797d, + +1.8810608386993408d, + +1.8828988075256348d, + +1.8847384452819824d, + +1.886579990386963d, + +1.888423204421997d, + +1.890268325805664d, + +1.8921151161193848d, + +1.8939638137817383d, + +1.8958141803741455d, + +1.8976664543151855d, + +1.8995206356048584d, + +1.901376485824585d, + +1.9032342433929443d, + +1.9050939083099365d, + +1.9069552421569824d, + +1.908818244934082d, + +1.9106833934783936d, + +1.9125502109527588d, + +1.9144186973571777d, + +1.9162893295288086d, + +1.9181616306304932d, + +1.9200356006622314d, + +1.9219114780426025d, + +1.9237892627716064d, + +1.9256689548492432d, + +1.9275505542755127d, + +1.929433822631836d, + +1.931318759918213d, + +1.9332058429718018d, + +1.9350945949554443d, + +1.9369852542877197d, + +1.938877820968628d, + +1.940772294998169d, + +1.9426684379577637d, + +1.9445664882659912d, + +1.9464664459228516d, + +1.9483680725097656d, + +1.9502718448638916d, + +1.9521772861480713d, + +1.9540846347808838d, + +1.955993890762329d, + +1.9579050540924072d, + +1.959817886352539d, + +1.9617326259613037d, + +1.9636495113372803d, + +1.9655680656433105d, + +1.9674885272979736d, + +1.9694106578826904d, + +1.9713349342346191d, + +1.9732608795166016d, + +1.975188970565796d, + +1.977118730545044d, + +1.9790503978729248d, + +1.9809842109680176d, + +1.982919692993164d, + +1.9848570823669434d, + +1.9867963790893555d, + +1.9887375831604004d, + +1.990680456161499d, + +1.9926254749298096d, + +1.994572401046753d, + +1.996521234512329d, + +1.998471736907959d, + +2.000424385070801d, + +2.0023789405822754d, + +2.004335403442383d, + +2.006293773651123d, + +2.008254051208496d, + +2.010216236114502d, + +2.0121798515319824d, + +2.014145851135254d, + +2.016113758087158d, + +2.0180835723876953d, + +2.0200552940368652d, + +2.022029399871826d, + +2.0240049362182617d, + +2.02598237991333d, + +2.0279617309570312d, + +2.0299429893493652d, + +2.0319266319274902d, + +2.03391170501709d, + +2.0358991622924805d, + +2.0378880500793457d, + +2.039879322052002d, + +2.041872501373291d, + +2.0438671112060547d, + +2.0458641052246094d, + +2.047863006591797d, + +2.049863815307617d, + +2.0518670082092285d, + +2.0538716316223145d, + +2.055878162384033d, + +2.057887077331543d, + +2.0598974227905273d, + +2.0619101524353027d, + +2.063924789428711d, + +2.065941333770752d, + +2.067959785461426d, + +2.0699801445007324d, + +2.07200288772583d, + +2.0740270614624023d, + +2.0760536193847656d, + +2.0780820846557617d, + +2.0801124572753906d, + +2.0821447372436523d, + +2.084178924560547d, + +2.0862154960632324d, + +2.0882534980773926d, + +2.0902938842773438d, + +2.0923361778259277d, + +2.0943803787231445d, + +2.0964269638061523d, + +2.0984749794006348d, + +2.100525379180908d, + +2.1025776863098145d, + +2.1046319007873535d, + +2.1066884994506836d, + +2.1087465286254883d, + +2.110806941986084d, + +2.1128692626953125d, + +2.114933490753174d, + +2.117000102996826d, + +2.1190686225891113d, + +2.1211390495300293d, + +2.12321138381958d, + +2.1252856254577637d, + +2.1273622512817383d, + +2.1294407844543457d, + +2.131521224975586d, + +2.133604049682617d, + +2.135688304901123d, + +2.13777494430542d, + +2.139863967895508d, + +2.1419544219970703d, + +2.144047260284424d, + +2.14614200592041d, + +2.1482391357421875d, + +2.1503376960754395d, + +2.1524391174316406d, + +2.1545419692993164d, + +2.156647205352783d, + +2.1587538719177246d, + +2.1608633995056152d, + +2.1629743576049805d, + +2.1650876998901367d, + +2.167203426361084d, + +2.169320583343506d, + +2.1714401245117188d, + +2.1735615730285645d, + +2.175685405731201d, + +2.1778111457824707d, + +2.179938793182373d, + +2.1820688247680664d, + +2.1842007637023926d, + +2.1863350868225098d, + +2.1884708404541016d, + +2.1906094551086426d, + +2.192749500274658d, + +2.194891929626465d, + +2.1970362663269043d, + +2.1991829872131348d, + +2.201331615447998d, + +2.2034826278686523d, + +2.2056355476379395d, + +2.2077903747558594d, + +2.2099475860595703d, + +2.212106704711914d, + +2.214268207550049d, + +2.2164316177368164d, + +2.218596935272217d, + +2.220764636993408d, + +2.2229342460632324d, + +2.2251062393188477d, + +2.2272801399230957d, + +2.2294564247131348d, + +2.2316346168518066d, + +2.2338151931762695d, + +2.2359976768493652d, + +2.2381820678710938d, + +2.2403693199157715d, + +2.242558002471924d, + +2.244749069213867d, + +2.2469425201416016d, + +2.2491378784179688d, + +2.2513351440429688d, + +2.2535347938537598d, + +2.2557363510131836d, + +2.2579402923583984d, + +2.2601466178894043d, + +2.262354850769043d, + +2.2645654678344727d, + +2.266777992248535d, + +2.2689924240112305d, + +2.271209716796875d, + +2.273428440093994d, + +2.2756495475769043d, + +2.2778730392456055d, + +2.2800989151000977d, + +2.2823266983032227d, + +2.2845563888549805d, + +2.2867884635925293d, + +2.289022922515869d, + +2.291259288787842d, + +2.2934980392456055d, + +2.295738697052002d, + +2.2979817390441895d, + +2.300227165222168d, + +2.3024744987487793d, + +2.3047242164611816d, + +2.306975841522217d, + +2.309229850769043d, + +2.31148624420166d, + +2.31374454498291d, + +2.316005229949951d, + +2.318267822265625d, + +2.32053279876709d, + +2.3228001594543457d, + +2.3250694274902344d, + +2.3273415565490723d, + +2.3296151161193848d, + +2.3318915367126465d, + +2.334169864654541d, + +2.3364500999450684d, + +2.338733196258545d, + +2.3410181999206543d, + +2.3433055877685547d, + +2.345594882965088d, + +2.347886562347412d, + +2.3501806259155273d, + +2.3524770736694336d, + +2.3547754287719727d, + +2.3570761680603027d, + +2.3593788146972656d, + +2.3616843223571777d, + +2.3639917373657227d, + +2.3663015365600586d, + +2.3686132431030273d, + +2.370927333831787d, + +2.373243808746338d, + +2.3755626678466797d, + +2.3778839111328125d, + +2.380207061767578d, + +2.3825325965881348d, + +2.3848605155944824d, + +2.387190818786621d, + +2.3895230293273926d, + +2.391857624053955d, + +2.3941946029663086d, + +2.396533966064453d, + +2.3988752365112305d, + +2.401218891143799d, + +2.4035654067993164d, + +2.4059133529663086d, + +2.40826416015625d, + +2.4106173515319824d, + +2.4129724502563477d, + +2.415329933166504d, + +2.417689800262451d, + +2.4200520515441895d, + +2.4224166870117188d, + +2.424783229827881d, + +2.427152633666992d, + +2.4295239448547363d, + +2.4318976402282715d, + +2.4342737197875977d, + +2.436652183532715d, + +2.439032554626465d, + +2.441415786743164d, + +2.4438014030456543d, + +2.4461889266967773d, + +2.4485788345336914d, + +2.4509711265563965d, + +2.4533658027648926d, + +2.4557628631591797d, + +2.458162307739258d, + +2.460564136505127d, + +2.462968349456787d, + +2.46537446975708d, + +2.4677834510803223d, + +2.4701943397521973d, + +2.4726080894470215d, + +2.4750237464904785d, + +2.4774417877197266d, + +2.479862689971924d, + +2.482285499572754d, + +2.484710693359375d, + +2.487138271331787d, + +2.4895682334899902d, + +2.4920010566711426d, + +2.4944357872009277d, + +2.496872901916504d, + +2.499312400817871d, + +2.5017542839050293d, + +2.5041985511779785d, + +2.5066452026367188d, + +2.50909423828125d, + +2.5115456581115723d, + +2.5139999389648438d, + +2.516456127166748d, + +2.5189146995544434d, + +2.5213756561279297d, + +2.5238394737243652d, + +2.5263051986694336d, + +2.528773307800293d, + +2.5312442779541016d, + +2.533717155456543d, + +2.5361928939819336d, + +2.538670539855957d, + +2.5411510467529297d, + +2.5436339378356934d, + +2.546119213104248d, + +2.5486068725585938d, + +2.5510969161987305d, + +2.553589344024658d, + +2.556084632873535d, + +2.558581829071045d, + +2.5610814094543457d, + +2.5635838508605957d, + +2.5660886764526367d, + +2.5685958862304688d, + +2.571105480194092d, + +2.573617458343506d, + +2.576131820678711d, + +2.5786490440368652d, + +2.5811686515808105d, + +2.5836901664733887d, + +2.586214542388916d, + +2.5887417793273926d, + +2.591270923614502d, + +2.5938024520874023d, + +2.596336841583252d, + +2.5988736152648926d, + +2.601412773132324d, + +2.603954315185547d, + +2.6064987182617188d, + +2.6090455055236816d, + +2.6115946769714355d, + +2.6141462326049805d, + +2.6167001724243164d, + +2.6192569732666016d, + +2.6218161582946777d, + +2.624377727508545d, + +2.626941680908203d, + +2.6295084953308105d, + +2.632077217102051d, + +2.6346492767333984d, + +2.637223243713379d, + +2.6398000717163086d, + +2.6423792839050293d, + +2.644960880279541d, + +2.6475448608398438d, + +2.6501317024230957d, + +2.6527209281921387d, + +2.655313014984131d, + +2.657907009124756d, + +2.6605043411254883d, + +2.6631035804748535d, + +2.665705680847168d, + +2.6683101654052734d, + +2.67091703414917d, + +2.6735267639160156d, + +2.6761388778686523d, + +2.67875337600708d, + +2.681370735168457d, + +2.683990478515625d, + +2.686613082885742d, + +2.689237594604492d, + +2.6918654441833496d, + +2.69449520111084d, + +2.6971278190612793d, + +2.699763298034668d, + +2.7024011611938477d, + +2.7050414085388184d, + +2.70768404006958d, + +2.710329532623291d, + +2.712977886199951d, + +2.7156286239624023d, + +2.7182817459106445d, + }; + + /** Exponential over the range of 0 - 1 in increments of 2^-10 + * exp(x/1024) = expFracTableA[x] + expFracTableB[x]. + */ + private static final double[] EXP_FRAC_B = new double[] { + +0.0d, + +1.552583321178453E-10d, + +1.2423699995465188E-9d, + +4.194022929828008E-9d, + +9.94381632344361E-9d, + +1.9426261544163577E-8d, + +3.3576783010266685E-8d, + +5.3331719086630523E-8d, + +7.962832297769345E-8d, + +1.1340476362128895E-7d, + -8.281845251820919E-8d, + -3.126416414805498E-8d, + +3.058997113995161E-8d, + +1.0368579417304741E-7d, + -4.9452513107409435E-8d, + +4.8955889659397494E-8d, + -7.698155155722897E-8d, + +5.051784853384516E-8d, + -4.443661736519001E-8d, + +1.1593958457401774E-7d, + +5.575759739697068E-8d, + +1.4385227981629147E-8d, + -7.227368462584163E-9d, + -8.129108387083023E-9d, + +1.263202100290635E-8d, + +5.600896265625552E-8d, + -1.154629885168314E-7d, + -2.399186832888246E-8d, + +9.295948298604103E-8d, + -2.070841011504222E-9d, + -6.97066538508643E-8d, + -1.0898941254272996E-7d, + -1.1895963756343625E-7d, + -9.865691193993138E-8d, + -4.711988033385175E-8d, + +3.6613751875298095E-8d, + -8.491135959370133E-8d, + +6.610611940107793E-8d, + +1.3794148633283659E-8d, + -2.462631860370667E-9d, + +1.830278273495162E-8d, + +7.705834203598065E-8d, + -6.364563771711373E-8d, + +7.39978436695387E-8d, + +1.4122417557484554E-8d, + -3.881598887298574E-9d, + +2.0958481826069642E-8d, + +8.96162975425619E-8d, + -3.535214171178576E-8d, + -1.1455271549574576E-7d, + +9.140964977432485E-8d, + +1.0667524445105459E-7d, + -6.777752790396222E-8d, + +4.586785041291296E-8d, + -2.8245462428022094E-8d, + -5.071761314397018E-8d, + -2.0566368810068663E-8d, + +6.319146317890346E-8d, + -3.687854305539139E-8d, + -8.137269363160008E-8d, + -6.930491127388755E-8d, + +3.1184473002226595E-10d, + -1.0995299963140049E-7d, + +7.772668425499348E-8d, + +8.750367485925089E-8d, + -7.963112393823186E-8d, + +5.415131809829094E-8d, + +1.3006683896462346E-8d, + +3.634736373360733E-8d, + -1.132504393233074E-7d, + +4.2046187038837375E-8d, + +2.6396811618001066E-8d, + +7.92177143584738E-8d, + -3.691100820545433E-8d, + -8.257112559083188E-8d, + -5.676200971739166E-8d, + +4.151794514828518E-8d, + -2.5147255753587636E-8d, + -1.7335469415174996E-8d, + +6.595784859136531E-8d, + -1.2680354928109105E-8d, + -1.3824992526093461E-8d, + +6.353142754175797E-8d, + -1.8021197722549054E-8d, + -1.9054827792903468E-8d, + +6.144098503892116E-8d, + -1.3940903373095247E-8d, + -5.7694907599522404E-9d, + +8.696863522320578E-8d, + +2.6869297963554945E-8d, + +5.3366470162689076E-8d, + -7.094204160127543E-8d, + -1.0662027949814858E-7d, + -5.26498707801063E-8d, + +9.198855229106814E-8d, + +8.989677431456647E-8d, + -5.790384407322479E-8d, + -1.1197236522467887E-7d, + -7.12854317090566E-8d, + +6.51813137650059E-8d, + +6.003465022483798E-8d, + -8.569906238528267E-8d, + +1.0584469687624562E-7d, + -7.956144278281947E-8d, + +7.43676272093501E-8d, + +9.182512565315022E-8d, + -2.6157563728873715E-8d, + -4.012947040998503E-8d, + +5.094280572218447E-8d, + +9.675095351161728E-9d, + +7.552139802281006E-8d, + +1.1099566726533146E-8d, + +5.58656252899437E-8d, + -2.756054703800197E-8d, + +2.791018095971047E-10d, + -9.799351869734466E-8d, + -8.291832428736212E-8d, + +4.654720780112994E-8d, + +5.302803981406403E-8d, + -6.243126731995636E-8d, + -6.036655299348577E-8d, + +6.026878587378257E-8d, + +6.210379583313526E-8d, + -5.381287389094251E-8d, + -4.8012970400697E-8d, + +8.055420567281602E-8d, + +9.452180117175641E-8d, + -5.057430382371206E-9d, + +2.1288872215266507E-8d, + -6.380305844689076E-8d, + -2.0858800984600168E-8d, + -8.724006061713588E-8d, + -2.3470351753125604E-8d, + -6.690931338790221E-8d, + +2.192160831263035E-8d, + +5.6648446166177225E-9d, + -1.1461755745719884E-7d, + -9.944393412663547E-8d, + +5.2249837964645906E-8d, + +1.0311034276196487E-7d, + +5.4203784018566126E-8d, + -9.340259278913173E-8d, + -1.0022192034216903E-7d, + +3.481513333662908E-8d, + +7.436036590244714E-8d, + +1.9485199912395296E-8d, + +1.0968068384729757E-7d, + +1.0760175582979094E-7d, + +1.4322981952798675E-8d, + +6.933855730431659E-8d, + +3.530656968851287E-8d, + -8.669526204279467E-8d, + -5.7169586962345785E-8d, + -1.1345515834332824E-7d, + -1.605251622332555E-8d, + -2.298302779758532E-9d, + -7.110952399338234E-8d, + +1.70164513845372E-8d, + +2.4746155561368937E-8d, + -4.6834239957353325E-8d, + +4.1781076667923185E-8d, + +5.326182134294869E-8d, + -1.1302647617762544E-8d, + +8.759667154796094E-8d, + +1.126326877851684E-7d, + +6.48979555673987E-8d, + -5.451390316294111E-8d, + -6.0896188500539086E-9d, + -2.7152010585461855E-8d, + -1.1660424775832058E-7d, + -3.492984900939992E-8d, + -1.944841848873016E-8d, + -6.905990750285027E-8d, + +5.575538653428039E-8d, + +1.1768108384670781E-7d, + +1.178204606523101E-7d, + +5.727787111340131E-8d, + -6.284125161007433E-8d, + -3.0118152047565877E-9d, + -5.448044533034374E-10d, + -5.433154287341921E-8d, + +7.515630833946181E-8d, + -8.780756503572527E-8d, + -6.527407547535494E-8d, + -9.45487863616303E-8d, + +6.390098458668406E-8d, + -6.564672913105876E-8d, + -5.238488022920792E-9d, + +7.824500749252316E-9d, + -2.5339299158309795E-8d, + -1.036103313062145E-7d, + +1.2550633697348567E-8d, + +8.584676196065558E-8d, + +1.1740089468291563E-7d, + +1.0833697012353316E-7d, + +5.978002467397905E-8d, + -2.7143806069290897E-8d, + +8.711129287069315E-8d, + -7.316349947981893E-8d, + -3.00015852582934E-8d, + -2.0691000399732483E-8d, + -4.4100097152254264E-8d, + -9.909612209943178E-8d, + +5.38733640215475E-8d, + -6.0893829005035E-8d, + +3.457553391989844E-8d, + +1.0300006058273187E-7d, + -9.290053015365092E-8d, + -7.514966995961323E-8d, + -8.10254145615142E-8d, + -1.0938612624777085E-7d, + +7.932952721989251E-8d, + +9.428257290008738E-9d, + -7.952636967837795E-8d, + +5.203033137154554E-8d, + -7.159157201731446E-8d, + +2.7593424989059015E-8d, + +1.1231621190000476E-7d, + -5.469119869891027E-8d, + +4.560067256086347E-9d, + +5.280427179595944E-8d, + +9.119538242455128E-8d, + -1.1753008498403413E-7d, + -9.537874867759656E-8d, + -7.96118345325538E-8d, + -6.907085854395348E-8d, + -6.259620482221904E-8d, + -5.902712448725381E-8d, + -5.720173456146447E-8d, + -5.5957016861703E-8d, + -5.412881689012608E-8d, + -5.0551842723970724E-8d, + -4.405966390424518E-8d, + -3.348471032333413E-8d, + -1.7658271111516935E-8d, + +4.589506477601956E-9d, + +3.4429618182751655E-8d, + +7.303420385174346E-8d, + -1.168420305422519E-7d, + -5.718749537552229E-8d, + +1.4754809136835937E-8d, + +1.001616104682875E-7d, + -3.8207793300052055E-8d, + +7.766278405014509E-8d, + -2.7883635712109803E-8d, + -1.1524714043067699E-7d, + +5.517333625963128E-8d, + +7.724278756071081E-9d, + -1.7990934773848504E-8d, + -2.0786347668702902E-8d, + +5.251554594269693E-10d, + +4.7131849857076246E-8d, + -1.1819540733893871E-7d, + -1.742885956093543E-8d, + +1.1220467571570283E-7d, + +3.347954541376715E-8d, + -1.399157980498908E-8d, + -2.9013441705763093E-8d, + -1.0389614239253089E-8d, + +4.307749759934266E-8d, + -1.0583192018912101E-7d, + +2.0919226941745448E-8d, + -5.2305110482722706E-8d, + -8.588407110184028E-8d, + -7.861419797923639E-8d, + -2.929085835358592E-8d, + +6.329175751021792E-8d, + -3.807794163054899E-8d, + -9.377320954068088E-8d, + -1.0258469865953145E-7d, + -6.330187984612758E-8d, + +2.5286958775281306E-8d, + -7.40238661307607E-8d, + +1.1681688445204168E-7d, + -1.1623125976292733E-7d, + -5.6696107089038004E-8d, + +5.822140627806124E-8d, + -8.678466172071259E-9d, + -1.7757121899175995E-8d, + +3.220665454652531E-8d, + -9.598330731102836E-8d, + +7.573375369829243E-8d, + +7.174547784678893E-8d, + -1.0672213971363184E-7d, + +1.8395252217743006E-8d, + -2.8511112548600118E-8d, + -7.79306270997787E-9d, + +8.178019529487065E-8d, + +3.0220784595602374E-9d, + -4.4156343103298585E-9d, + +6.07014616741277E-8d, + -3.8809601937571554E-8d, + -6.329342805230603E-8d, + -1.1511990258493999E-8d, + +1.177739474561431E-7d, + +8.738625278484571E-8d, + -1.0143341551207646E-7d, + +2.9394972678456236E-8d, + +4.278345398213486E-9d, + +6.28805835150457E-8d, + -3.197037359731606E-8d, + -4.060821046423735E-8d, + +3.82160283750664E-8d, + -3.2666060441373307E-8d, + -1.3584500601329896E-8d, + +9.671332777035621E-8d, + +6.10626893063691E-8d, + +1.1913723189736356E-7d, + +3.3774671482641995E-8d, + +4.4651109654500895E-8d, + -8.539328154875224E-8d, + -1.166799420361101E-7d, + -4.794765976694151E-8d, + -1.1635256954820579E-7d, + -8.221241452580445E-8d, + +5.5737717715868425E-8d, + +6.034539636024073E-8d, + -6.712199323081945E-8d, + -8.697724830833087E-8d, + +2.0494942705297694E-9d, + -3.718924074653624E-8d, + +3.499747150995707E-8d, + -1.8535359161566028E-8d, + +4.1905679587096103E-8d, + -2.0821912536551675E-8d, + +3.297776915751238E-8d, + -3.3835280846270374E-8d, + +1.8437339356553904E-8d, + -4.734187609526424E-8d, + +8.527976799299225E-9d, + -5.1088103279787804E-8d, + +1.3513294656751725E-8d, + -3.480032127343472E-8d, + +4.367697180842916E-8d, + +1.1815196363705356E-8d, + +1.0932279207149782E-7d, + +9.907230065250944E-8d, + -1.764389559496152E-8d, + -1.1135725625095859E-9d, + -8.846040040259342E-8d, + -3.996962588736431E-8d, + -9.276238757878814E-8d, + -7.12139818505956E-9d, + -2.016525972830718E-8d, + +1.0782585410141121E-7d, + -9.868269632073771E-8d, + +7.686861750031585E-8d, + -7.947087669425045E-8d, + -8.955768055535647E-8d, + +4.791582240886607E-8d, + +9.583994718167641E-8d, + +5.5524866689108584E-8d, + -7.171796605211277E-8d, + -4.6157237582310713E-8d, + -1.0489751005162237E-7d, + -8.204903560604627E-9d, + +6.818588687884566E-9d, + -5.850916105103205E-8d, + +3.5549586192569994E-8d, + +5.1896700056778354E-8d, + -8.146080588190463E-9d, + +9.516285362051742E-8d, + -1.1368933260611668E-7d, + +8.187871486648885E-8d, + -3.206182925646474E-8d, + +2.265440168347286E-8d, + +8.938334752179552E-9d, + -7.187922490287331E-8d, + +1.9952407216533937E-8d, + +4.734805892507655E-8d, + +1.1642439930208906E-8d, + -8.582843599651953E-8d, + -5.3086706437795354E-9d, + +1.6121782610217253E-8d, + -2.0197142620980974E-8d, + -1.129242035557684E-7d, + -2.2298267863810133E-8d, + +1.4605950309628873E-8d, + -8.663710700190489E-10d, + -6.736873974532501E-8d, + +5.486523121881414E-8d, + -1.0965249168570443E-7d, + -8.27343074126263E-8d, + -1.0144703278439455E-7d, + +7.39809943048038E-8d, + -3.193297932837415E-8d, + +5.900393284617182E-8d, + +1.0973020465397083E-7d, + -1.1681436418514489E-7d, + +9.5985669644661E-8d, + +3.423560333632085E-8d, + -6.22836197265283E-8d, + +4.621027492345726E-8d, + -1.1575484316683829E-7d, + -6.997545435826076E-8d, + -5.3502441327259514E-8d, + -6.49667713553005E-8d, + -1.029980741248172E-7d, + +7.219393868923887E-8d, + -1.4854841678687828E-8d, + +1.1406713393562271E-7d, + -1.650155887561251E-8d, + +7.165331603232264E-8d, + -9.692697614257269E-8d, + -4.402550702194912E-8d, + -6.679737442193143E-9d, + +1.6492800268960003E-8d, + +2.68759245092879E-8d, + +2.5854805721793077E-8d, + +1.4815967715704613E-8d, + -4.852711011229633E-9d, + -3.176199594915881E-8d, + -6.452129525125173E-8d, + -1.01738658407525E-7d, + +9.639780418418697E-8d, + +5.4445606140746644E-8d, + +1.2219361033150988E-8d, + -2.8883532688356087E-8d, + -6.746431126005811E-8d, + -1.0212284427080097E-7d, + +1.0696094577483825E-7d, + +8.43527683868743E-8d, + +6.987544103716777E-8d, + +6.493457409236137E-8d, + +7.093715125593688E-8d, + +8.929153091001965E-8d, + -1.1701113164306871E-7d, + -6.972256643013266E-8d, + -5.848862070736576E-9d, + +7.602385197610123E-8d, + -6.110775144284437E-8d, + +6.101012058093429E-8d, + -3.304167134225169E-8d, + -1.0342514383702196E-7d, + +8.969907328603505E-8d, + +7.091600108064668E-8d, + +8.006778743052707E-8d, + +1.1857939200074815E-7d, + -5.0541412403312774E-8d, + +5.0970277930552287E-8d, + -5.229355472795119E-8d, + +1.1793478462381443E-7d, + +8.625007227318527E-8d, + +9.250422086873268E-8d, + -1.0028661472061573E-7d, + -1.384914052949463E-8d, + +1.1483560326413004E-7d, + +4.878798101459259E-8d, + +2.7866921183936055E-8d, + +5.3514180410849046E-8d, + -1.1124565511436785E-7d, + +1.186914813275767E-8d, + -5.253258132241335E-8d, + -6.458486486369316E-8d, + -2.2838888809969377E-8d, + +7.415557606805398E-8d, + -1.0568403170659571E-8d, + -3.7139182948393606E-8d, + -4.1022790876160215E-9d, + +8.999821367768787E-8d, + +8.201043988912348E-9d, + -9.616457442665051E-9d, + +3.8005886250603055E-8d, + -8.588890051473289E-8d, + +9.699937202692456E-8d, + +1.11298006674538E-7d, + -4.1527104733570825E-8d, + +1.1682852007826251E-7d, + +1.1099648061301941E-7d, + -5.755303038890997E-8d, + +8.948877445235827E-8d, + +7.675780395028194E-8d, + -9.427143563390596E-8d, + +5.471416081500162E-8d, + +4.8354824064383506E-8d, + -1.118706134478866E-7d, + +5.235528379688445E-8d, + +6.567708120053687E-8d, + -7.042204992948526E-8d, + -1.1603891006723397E-7d, + -6.968742825553785E-8d, + +7.01199184127881E-8d, + +6.645352711199266E-8d, + -7.919617109348822E-8d, + +1.1149986927391714E-7d, + -7.522074418324674E-8d, + +7.739252980388984E-8d, + +9.39987974788905E-8d, + -2.390421480210064E-8d, + -3.639873824357815E-8d, + +5.8015881615938497E-8d, + +2.2423186335040668E-8d, + +9.674534330665206E-8d, + +4.4068830785712375E-8d, + +1.0431875573076199E-7d, + +4.0584538834428926E-8d, + +9.279423236781974E-8d, + +2.404020521381534E-8d, + +7.425346071427343E-8d, + +6.529321706138789E-9d, + +6.080174837146273E-8d, + +1.6902327633329284E-10d, + +6.456806922371733E-8d, + +1.7100134295216033E-8d, + +9.770510970673519E-8d, + +6.94872148530716E-8d, + -6.602926393514549E-8d, + -6.889997193778161E-8d, + +6.240235720677117E-8d, + +9.098790295810902E-8d, + +1.8386917534879182E-8d, + +8.454972737414241E-8d, + +5.259099728747365E-8d, + -7.595453077213505E-8d, + -6.113203624663034E-8d, + +9.859622328905143E-8d, + -7.206766550807255E-8d, + -9.474579567171831E-8d, + +3.210408693366267E-8d, + +7.160716418525417E-8d, + +2.530870537724554E-8d, + -1.0524451040704701E-7d, + -8.008561371849434E-8d, + +1.0233519853128553E-7d, + -3.326791455362767E-8d, + -8.504961764629757E-9d, + -6.024017201863256E-8d, + +5.1500902632092514E-8d, + +8.98570720774568E-8d, + +5.638724693948384E-8d, + -4.734813904255994E-8d, + +1.8631451577542948E-8d, + +1.7470924137873214E-8d, + -4.926470933588261E-8d, + +5.84096713620797E-8d, + +1.0364355880696472E-7d, + +8.800655674349468E-8d, + +1.3069802481237792E-8d, + +1.1882454749452428E-7d, + -6.999215748398631E-8d, + -7.49674072510849E-8d, + +1.054760847603618E-7d, + -3.920012014371067E-9d, + +7.526183084319617E-8d, + +1.0618494853096868E-7d, + +9.043280094115832E-8d, + +2.9590395068826316E-8d, + -7.475571347653619E-8d, + +1.7401160143611842E-8d, + +6.923209420670962E-8d, + +8.232829924979753E-8d, + +5.82825404854514E-8d, + -1.3108606792380822E-9d, + -9.485602512220194E-8d, + +1.7663064617118723E-8d, + +9.942682855652123E-8d, + -8.638275100090915E-8d, + -6.132639063569726E-8d, + -6.221897889344726E-8d, + -8.745525834919404E-8d, + +1.029901759234897E-7d, + +3.3888561478632076E-8d, + -5.47315553588771E-8d, + +7.715994473741065E-8d, + -4.566098167230033E-8d, + +5.5257514455273825E-8d, + -9.530545662611411E-8d, + -1.889488909834863E-8d, + +4.769006625301079E-8d, + +1.0607041998938709E-7d, + -8.054981263802322E-8d, + -3.370929373457322E-8d, + +9.799164177397836E-9d, + +5.160291611526656E-8d, + +9.333090708652975E-8d, + -1.0180490545927503E-7d, + -5.533523366931846E-8d, + -4.044932340334176E-9d, + +5.370131904567218E-8d, + -1.1887814032213867E-7d, + -4.3307634616102625E-8d, + +4.363437558318513E-8d, + -9.482896784430338E-8d, + +1.9782818312325887E-8d, + -8.77224935488516E-8d, + +6.113879253864931E-8d, + -8.822335132515693E-9d, + -5.753754066078771E-8d, + -8.335545536862392E-8d, + -8.462309712606694E-8d, + -5.968586877433824E-8d, + -6.887556547891059E-9d, + +7.542967150507818E-8d, + -4.949331199790077E-8d, + +9.684172421525468E-8d, + +3.9260317944365246E-8d, + +1.784536881359796E-8d, + +3.426282345243592E-8d, + +9.018025618601154E-8d, + -5.1151708476133135E-8d, + +8.877492215808044E-8d, + +3.479545684576179E-8d, + +2.7002575714977818E-8d, + +6.707201545505014E-8d, + -8.173742908533777E-8d, + +5.909041310777802E-8d, + +1.439903710393587E-8d, + +2.4289317341982113E-8d, + +9.044519282818302E-8d, + -2.3866331257845713E-8d, + -7.853944465095286E-8d, + -7.188526769607005E-8d, + -2.2132706360079843E-9d, + -1.0624985110080394E-7d, + +9.453598391231829E-8d, + -1.134160131581847E-7d, + -1.315295870404327E-8d, + -7.981320644583728E-8d, + -7.327771300038971E-8d, + +8.155647334672472E-9d, + -7.222791579580787E-8d, + -7.430436987497092E-8d, + +3.633404807819848E-9d, + -7.512438321498593E-8d, + -7.044869765481105E-8d, + +1.9372589859580955E-8d, + -4.2365298585101096E-8d, + -1.552830824758035E-8d, + +1.0160071259930585E-7d, + +7.232201430620959E-8d, + -1.0164389431039905E-7d, + +5.826233477413577E-8d, + +7.6927415825689E-8d, + -4.392309439525734E-8d, + -6.414337408955734E-8d, + +1.799550702470095E-8d, + -3.4194410638967946E-8d, + +1.9437762419688045E-8d, + -5.7792549966531335E-8d, + -2.5731071572354522E-8d, + +1.173595905705643E-7d, + -1.0361863127101014E-7d, + +2.8330789837569332E-8d, + +3.81131861433539E-8d, + -7.252724942149532E-8d, + -6.342604067787756E-8d, + +6.716441526213986E-8d, + +8.257484966196574E-8d, + -1.5443717968117592E-8d, + +1.3280021798948244E-8d, + -6.79180673261558E-8d, + -1.8863249269709046E-8d, + -7.62162303263991E-8d, + +2.011589233663723E-10d, + -2.62683511147141E-8d, + +8.455684903712996E-8d, + +9.602293320384794E-8d, + +9.896378545255258E-9d, + +6.636396724067746E-8d, + +2.8777050870552646E-8d, + -1.0109271059094341E-7d, + -8.305334708631055E-8d, + +8.467026501338835E-8d, + -7.29821745001452E-8d, + -7.739491336852633E-8d, + +7.321238022013781E-8d, + -9.621538067089515E-8d, + -1.0705722541811197E-7d, + +4.247240125405735E-8d, + +1.1574222007764044E-7d, + +1.145412771487496E-7d, + +4.066036653218687E-8d, + -1.0410796803072171E-7d, + -7.955085231106037E-8d, + +1.1612776191572459E-7d, + +7.888519481107568E-9d, + +7.436813814737735E-8d, + +7.894935661289349E-8d, + +2.343525263620692E-8d, + -9.036933434595339E-8d, + -2.2239222395888823E-8d, + -8.784622656707742E-9d, + -4.819540032304379E-8d, + +9.975892708522332E-8d, + -3.9945124955316294E-8d, + +1.1345047468988893E-8d, + +1.702808472925844E-8d, + -2.10770182066344E-8d, + -1.0114948914089626E-7d, + +1.70518021921727E-8d, + +9.693260855961159E-8d, + -9.809953482725758E-8d, + -8.937957126662392E-8d, + -1.134963954323427E-7d, + +6.980004387880031E-8d, + -1.4494150014095534E-8d, + +1.122932337832262E-7d, + -2.483811732227808E-8d, + +5.278759515330048E-8d, + +1.0859222881334994E-7d, + -9.400056055939758E-8d, + -7.630957994128623E-8d, + -7.490757191850264E-8d, + -8.794689652049879E-8d, + -1.1357810855950775E-7d, + +8.846862323478745E-8d, + +4.32092015744956E-8d, + -9.082923009890997E-9d, + -6.655106680680314E-8d, + +1.1108184705020206E-7d, + +4.8838973948592766E-8d, + -1.2998975819628988E-8d, + -7.25680516883106E-8d, + -1.280024819379844E-7d, + -1.7743467191652895E-7d, + -2.1899520225809197E-7d, + +2.2602433110285232E-7d, + +2.0582268590356215E-7d, + +1.9911192455808124E-7d, + +2.0776878313278689E-7d, + +2.3367183133931002E-7d, + -1.9813568387704588E-7d, + -1.320972037315105E-7d, + -4.316580502355056E-8d, + +7.054443447243064E-8d, + +2.109212796025238E-7d, + -9.698281856949837E-8d, + +1.0239791185239086E-7d, + -1.4271754202157014E-7d, + +1.232402895636637E-7d, + -5.150590480969644E-8d, + -1.882201085012735E-7d, + +1.918355503889933E-7d, + +1.368893262241355E-7d, + +1.256828068633383E-7d, + +1.601222826656464E-7d, + -2.3472125169205568E-7d, + -1.032634625827871E-7d, + +7.957037517331382E-8d, + -1.6114314525832115E-7d, + +1.3018591370778052E-7d, + +1.8007284821359149E-9d, + -6.75421764491544E-8d, + -7.592155950645605E-8d, + -2.1414301981236817E-8d, + +9.79045937979623E-8d, + -1.9287515190177685E-7d, + +6.184953843236509E-8d, + -8.966500602352001E-8d, + -1.686490951669855E-7d, + -1.7316830893872364E-7d, + -1.0128633727463388E-7d, + +4.8935021740786486E-8d, + -1.9740129448026905E-7d, + +1.1532102163380318E-7d, + +3.5371542244169364E-8d, + +4.153321337726989E-8d, + +1.3575372396796738E-7d, + -1.5685449228299222E-7d, + +1.1933437776279623E-7d, + +1.2599421120614435E-8d, + +1.7331079674066365E-9d, + +8.869266069401045E-8d, + -2.013999442282902E-7d, + +8.709065843311144E-8d, + +2.453117120472083E-9d, + +2.3489472779602617E-8d, + +1.5216652792122652E-7d, + -8.638415150333099E-8d, + -2.1335475961524608E-7d, + -2.2677272333821516E-7d, + -1.246635423141374E-7d, + +9.494921297991565E-8d, + -4.27932550865546E-8d, + -5.907349480138712E-8d, + +4.809072216941908E-8d, + -1.9615359732789476E-7d, + +1.6385396676990034E-7d, + +1.7642714221524228E-7d, + -1.564440844355254E-7d, + +1.2090653407564583E-7d, + +5.679855838941285E-8d, + +1.3006497185242537E-7d, + -1.341336085949317E-7d, + +2.1987686050231372E-7d, + -2.3641341460419062E-7d, + -7.048932272279454E-8d, + -2.3401958604540354E-7d, + +2.2867766559333004E-7d, + -1.1089952719756529E-7d, + +1.7977178878541792E-7d, + +1.4903074102418675E-7d, + -2.011072593789072E-7d, + +8.504948422097802E-8d, + +5.5846006716348844E-8d, + +1.9014079059505456E-7d, + +1.3119976852347583E-8d, + +3.645999732952202E-9d, + +1.6374611405314333E-7d, + +1.8612397134087598E-8d, + +4.7113225346448296E-8d, + -2.2555535676499395E-7d, + +1.5631615647329739E-7d, + -2.3574653182047758E-7d, + +3.08072210937242E-8d, + +4.344259288116142E-9d, + +1.6374489573868447E-7d, + +3.42171232580676E-8d, + +9.46452492584643E-8d, + -1.297587351085525E-7d, + -1.601065201853145E-7d, + +5.6550495386976275E-9d, + -1.0725602261510391E-7d, + -1.9945408945084193E-8d, + -2.071910882200156E-7d, + -1.900947109027913E-7d, + +3.34069282059055E-8d, + -1.145810806477298E-8d, + +1.5421457732308477E-7d, + +5.5657084775121975E-8d, + +1.7177785285061278E-7d, + +2.7813027425289027E-8d, + +1.0267509648109748E-7d, + -7.839574072711142E-8d, + -3.648293887796095E-8d, + +2.3049492079013518E-7d, + -2.290530257391564E-7d, + +1.747018414872141E-8d, + +1.8477759656842807E-8d, + -2.2394073401050633E-7d, + -2.3085653185818848E-7d, + -1.7598351175286083E-10d, + -6.640551220774385E-9d, + +2.2868466674913266E-7d, + +2.3106230530437902E-7d, + +2.594209135294356E-9d, + +2.2221434720602702E-8d, + -1.847872222755186E-7d, + -1.3948659218254467E-7d, + +1.6023339607737848E-7d, + -2.3718944120137026E-7d, + +1.0087056692827474E-7d, + +2.228553660510707E-7d, + +1.3088328582956644E-7d, + -1.7292527438195104E-7d, + -2.0961068531216087E-7d, + +2.2951597845188004E-8d, + +5.005103745740068E-8d, + -1.2618366811281002E-7d, + -2.6784582477238417E-8d, + -1.2645600379949252E-7d, + +5.3774170051560117E-8d, + +3.9205810725333715E-8d, + -1.6802196396307013E-7d, + -8.893078799284047E-8d, + -1.9821451970481713E-7d, + -1.689060694498032E-8d, + -1.9648717830943396E-8d, + -2.0433926409457167E-7d, + -9.1973399031975E-8d, + -1.5723449006087263E-7d, + +7.887051614592191E-8d, + +1.4166246290402286E-7d, + +3.330146018487787E-8d, + +2.3278688667580978E-7d, + -2.1139124097042925E-7d, + +1.334449995534113E-7d, + -1.6104730195920897E-7d, + -1.3902314592614197E-7d, + +2.0169027167169864E-7d, + -9.040643863751471E-8d, + -5.946190852360168E-8d, + -1.8013411720005014E-7d, + +2.6595401669835947E-8d, + +8.607292924069425E-8d, + +4.84038176769263E-10d, + -2.2798356346688802E-7d, + -1.203028719549339E-7d, + -1.5111906039270745E-7d, + +1.5859915617670956E-7d, + -1.426262681506497E-7d, + -9.892260062323546E-8d, + -1.8492643515928268E-7d, + +7.840210076743552E-8d, + +2.1643071541578027E-7d, + +2.313664294893465E-7d, + +1.2541842003811723E-7d, + -9.920197743470107E-8d, + +3.655589133934081E-8d, + +5.807052689551411E-8d, + -3.244024724169575E-8d, + -2.327564406466327E-7d, + -6.38187356721971E-8d, + -2.3995994000400915E-10d, + -3.9793609609721186E-8d, + -1.802510054588344E-7d, + +5.745586744591196E-8d, + +1.987228872666507E-7d, + -2.3105188606976847E-7d, + +2.0088042407239129E-7d, + +6.624793114025702E-8d, + -1.5587043044056635E-7d, + +1.3606464059428694E-8d, + +1.0008761540741556E-7d, + +1.058213771597129E-7d, + +3.3058299602856804E-8d, + -1.1594886810010702E-7d, + +1.378919824418909E-7d, + -1.5683631181406778E-7d, + -4.4200075770425176E-8d, + +1.2250985436706623E-9d, + -1.8297013058336644E-8d, + -1.005004229646318E-7d, + +2.337202285991116E-7d, + +3.296104292035678E-8d, + -2.23668185816307E-7d, + -5.7055442971184756E-8d, + +5.82391923137467E-8d, + +1.244950238958056E-7d, + +1.4399358260219398E-7d, + +1.1901862840583523E-7d, + +5.1856152603337505E-8d, + -5.520562000491495E-8d, + -1.9987622893254038E-7d, + +9.697418238031897E-8d, + -1.1603376405901542E-7d, + +1.170714288147407E-7d, + -1.550851303094034E-7d, + +2.3472546699189522E-8d, + +1.78211222185955E-7d, + -1.6540009048230807E-7d, + -5.137865010872577E-8d, + +4.57490653163866E-8d, + +1.2829599363166098E-7d, + +1.985773325073412E-7d, + -2.1792661654989742E-7d, + -1.652218131743459E-7d, + -1.178234251477505E-7d, + -7.34071933723896E-8d, + -2.9646587857612632E-8d, + +1.5787194498912167E-8d, + +6.52252321321176E-8d, + +1.2100088103262734E-7d, + +1.8544977697201776E-7d, + -2.159273204728711E-7d, + -1.2711589287782304E-7d, + -2.2610609958205195E-8d, + +9.993330547750349E-8d, + -2.33974236642384E-7d, + -6.830955860192377E-8d, + +1.2244183812423448E-7d, + -1.3620325027706252E-7d, + +1.1178574689680927E-7d, + -8.490693031052439E-8d, + +2.2975389535985893E-7d, + +1.0445707500867073E-7d, + +1.8405243253979117E-8d, + -2.6033812325397097E-8d, + -2.6489990728664908E-8d, + +1.9409124727247465E-8d, + +1.1403826867020365E-7d, + -2.1706266226554237E-7d, + -1.7839974359909697E-8d, + +2.3725087624341041E-7d, + +7.37567604176979E-8d, + -2.9098805266958403E-8d, + -6.892713087722722E-8d, + -4.333719263537725E-8d, + +5.006436936098099E-8d, + +2.1367325342138113E-7d, + -2.6949659655907758E-8d, + -1.9256682968755803E-7d, + +1.960616287777496E-7d, + +1.876664741413704E-7d, + -2.1534486893602122E-7d, + -5.688830723853217E-8d, + +1.8861113228746644E-7d, + +4.6730779443102234E-8d, + -3.275360514112964E-9d, + +4.1011920825226876E-8d, + +1.820141955326842E-7d, + -5.468175655175594E-8d, + -1.8981247089866317E-7d, + -2.209492705846306E-7d, + -1.4566110577298295E-7d, + +3.848544860465368E-8d, + -1.429109630340783E-7d, + -2.105749999899302E-7d, + -1.6206609756618993E-7d, + +5.058693461947143E-9d, + -1.8359244902596882E-7d, + +2.2810251664891242E-7d, + -1.8791776732592608E-7d, + +1.3106843166204263E-9d, + -1.5543153797220025E-7d, + -1.7884997059081524E-7d, + -6.648490725635754E-8d, + +1.8412576154421806E-7d, + +9.860939269906055E-8d, + +1.5627006743114285E-7d, + -1.17260039161597E-7d, + +2.3416513526430908E-7d, + -2.1749172296989992E-7d, + -3.9242560971295217E-8d, + -1.822826971477839E-7d, + -1.6729355321895212E-7d, + +8.208715337901827E-9d, + -1.301267783434537E-7d, + -1.029741755377153E-7d, + +9.215765583599035E-8d, + -1.907487641016455E-8d, + +4.2661388254716074E-8d, + -1.9697226735187428E-7d, + +2.1819935527247946E-7d, + -1.398318929248588E-7d, + +1.6195123407015624E-7d, + +1.723826394935661E-7d, + -1.0602700638269148E-7d, + -1.9392742205954563E-7d, + -8.880302882034106E-8d, + +2.1186420987133E-7d, + +2.3375763256988976E-7d, + -2.0599801342241997E-8d, + -7.184550924856607E-8d, + +8.254840070367875E-8d, + }; + + /** Extended precision logarithm table over the range 1 - 2 in increments of 2^-10. */ + private static final double[][] LN_MANT = new double[][] { + {+0.0d, +0.0d, }, // 0 + {+9.760860120877624E-4d, -3.903230345984362E-11d, }, // 1 + {+0.0019512202125042677d, -8.124251825289188E-11d, }, // 2 + {+0.0029254043474793434d, -1.8374207360194882E-11d,}, // 3 + {+0.0038986406289041042d, -2.1324678121885073E-10d,}, // 4 + {+0.004870930686593056d, -4.5199654318611534E-10d,}, // 5 + {+0.00584227591753006d, -2.933016992001806E-10d, }, // 6 + {+0.006812678650021553d, -2.325147219074669E-10d, }, // 7 + {+0.007782140746712685d, -3.046577356838847E-10d, }, // 8 + {+0.008750664070248604d, -5.500631513861575E-10d, }, // 9 + {+0.00971824862062931d, +8.48292035519895E-10d, }, // 10 + {+0.010684899985790253d, +1.1422610134013436E-10d,}, // 11 + {+0.01165061630308628d, +9.168889933128375E-10d, }, // 12 + {+0.012615403160452843d, -5.303786078838E-10d, }, // 13 + {+0.013579258695244789d, -5.688639355498786E-10d, }, // 14 + {+0.01454218477010727d, +7.296670293275653E-10d, }, // 15 + {+0.015504186972975731d, -4.370104767451421E-10d, }, // 16 + {+0.016465261578559875d, +1.43695591408832E-9d, }, // 17 + {+0.01742541790008545d, -1.1862263158849434E-9d, }, // 18 + {+0.018384650349617004d, -9.482976524690715E-10d, }, // 19 + {+0.01934296265244484d, +1.9068609515836638E-10d,}, // 20 + {+0.020300358533859253d, +2.655990315697216E-10d, }, // 21 + {+0.021256837993860245d, +1.0315548713040775E-9d, }, // 22 + {+0.022212404757738113d, +5.13345647019085E-10d, }, // 23 + {+0.02316705882549286d, +4.5604151934208014E-10d,}, // 24 + {+0.02412080392241478d, -1.1255706987475148E-9d, }, // 25 + {+0.025073636323213577d, +1.2289023836765196E-9d, }, // 26 + {+0.02602556347846985d, +1.7990281828096504E-9d, }, // 27 + {+0.026976589113473892d, -1.4152718164638451E-9d, }, // 28 + {+0.02792670577764511d, +7.568772963781632E-10d, }, // 29 + {+0.0288759246468544d, -1.1449998592111558E-9d, }, // 30 + {+0.029824241995811462d, -1.6850976862319495E-9d, }, // 31 + {+0.030771657824516296d, +8.422373919843096E-10d, }, // 32 + {+0.0317181795835495d, +6.872350402175489E-10d, }, // 33 + {+0.03266380727291107d, -4.541194749189272E-10d, }, // 34 + {+0.03360854089260101d, -8.9064764856495E-10d, }, // 35 + {+0.034552380442619324d, +1.0640404096769032E-9d, }, // 36 + {+0.0354953333735466d, -3.5901655945224663E-10d,}, // 37 + {+0.03643739968538284d, -3.4829517943661266E-9d, }, // 38 + {+0.037378571927547455d, +8.149473794244232E-10d, }, // 39 + {+0.03831886500120163d, -6.990650304449166E-10d, }, // 40 + {+0.03925827145576477d, +1.0883076226453258E-9d, }, // 41 + {+0.040196798741817474d, +3.845192807999274E-10d, }, // 42 + {+0.04113444685935974d, -1.1570594692045927E-9d, }, // 43 + {+0.04207121580839157d, -1.8877045166697178E-9d, }, // 44 + {+0.043007105588912964d, -1.6332083257987747E-10d,}, // 45 + {+0.04394212365150452d, -1.7950057534514933E-9d, }, // 46 + {+0.04487626254558563d, +2.302710041648838E-9d, }, // 47 + {+0.045809537172317505d, -1.1410233017161343E-9d, }, // 48 + {+0.04674194008111954d, -3.0498741599744685E-9d, }, // 49 + {+0.04767347127199173d, -1.8026348269183678E-9d, }, // 50 + {+0.04860413819551468d, -3.233204600453039E-9d, }, // 51 + {+0.04953393340110779d, +1.7211688427961583E-9d, }, // 52 + {+0.05046287178993225d, -2.329967807055457E-10d, }, // 53 + {+0.05139094591140747d, -4.191810118556531E-11d, }, // 54 + {+0.052318163216114044d, -3.5574324788328143E-9d, }, // 55 + {+0.053244516253471375d, -1.7346590916458485E-9d, }, // 56 + {+0.05417001247406006d, -4.343048751383674E-10d, }, // 57 + {+0.055094651877880096d, +1.92909364037955E-9d, }, // 58 + {+0.056018441915512085d, -5.139745677199588E-10d, }, // 59 + {+0.05694137513637543d, +1.2637629975129189E-9d, }, // 60 + {+0.05786345899105072d, +1.3840561112481119E-9d, }, // 61 + {+0.058784693479537964d, +1.414889689612056E-9d, }, // 62 + {+0.05970507860183716d, +2.9199191907666474E-9d, }, // 63 + {+0.0606246218085289d, +7.90594243412116E-12d, }, // 64 + {+0.06154331564903259d, +1.6844747839686189E-9d, }, // 65 + {+0.06246116757392883d, +2.0498074572151747E-9d, }, // 66 + {+0.06337818503379822d, -4.800180493433863E-9d, }, // 67 + {+0.06429435312747955d, -2.4220822960064277E-9d, }, // 68 + {+0.06520968675613403d, -4.179048566709334E-9d, }, // 69 + {+0.06612417101860046d, +6.363872957010456E-9d, }, // 70 + {+0.06703783571720123d, +9.339468680056365E-10d, }, // 71 + {+0.06795066595077515d, -4.04226739708981E-9d, }, // 72 + {+0.0688626617193222d, -7.043545052852817E-9d, }, // 73 + {+0.06977382302284241d, -6.552819560439773E-9d, }, // 74 + {+0.07068414986133575d, -1.0571674860370546E-9d, }, // 75 + {+0.07159365713596344d, -3.948954622015801E-9d, }, // 76 + {+0.07250232994556427d, +1.1776625988228244E-9d, }, // 77 + {+0.07341018319129944d, +9.221072639606492E-10d, }, // 78 + {+0.07431721687316895d, -3.219119568928366E-9d, }, // 79 + {+0.0752234160900116d, +5.147575929018918E-9d, }, // 80 + {+0.07612881064414978d, -2.291749683541979E-9d, }, // 81 + {+0.07703337073326111d, +5.749565906124772E-9d, }, // 82 + {+0.07793712615966797d, +9.495158151301779E-10d, }, // 83 + {+0.07884006202220917d, -3.144331429489291E-10d, }, // 84 + {+0.0797421783208847d, +3.430029236134205E-9d, }, // 85 + {+0.08064348995685577d, -1.2499290483167703E-9d, }, // 86 + {+0.08154398202896118d, +2.011215719133196E-9d, }, // 87 + {+0.08244366943836212d, -2.2728753031387152E-10d,}, // 88 + {+0.0833425521850586d, -6.508966857277253E-9d, }, // 89 + {+0.0842406153678894d, -4.801131671405377E-10d, }, // 90 + {+0.08513787388801575d, +4.406750291994231E-9d, }, // 91 + {+0.08603434264659882d, -5.304795662536171E-9d, }, // 92 + {+0.08692999184131622d, +1.6284313912612293E-9d, }, // 93 + {+0.08782485127449036d, -3.158898981674071E-9d, }, // 94 + {+0.08871890604496002d, -3.3324878834139977E-9d, }, // 95 + {+0.08961215615272522d, +2.536961912893389E-9d, }, // 96 + {+0.09050461649894714d, +9.737596728980696E-10d, }, // 97 + {+0.0913962870836258d, -6.600437262505396E-9d, }, // 98 + {+0.09228715300559998d, -3.866609889222889E-9d, }, // 99 + {+0.09317722916603088d, -4.311847594020281E-9d, }, // 100 + {+0.09406651556491852d, -6.525851105645959E-9d, }, // 101 + {+0.09495499730110168d, +5.799080912675435E-9d, }, // 102 + {+0.09584270417690277d, +4.2634204358490415E-9d, }, // 103 + {+0.09672962129116058d, +5.167390528799477E-9d, }, // 104 + {+0.09761576354503632d, -4.994827392841906E-9d, }, // 105 + {+0.09850110113620758d, +4.970725577861395E-9d, }, // 106 + {+0.09938566386699677d, +6.6496705953229645E-9d, }, // 107 + {+0.10026945173740387d, +1.4262712796792241E-9d, }, // 108 + {+0.1011524498462677d, +5.5822855204629114E-9d, }, // 109 + {+0.10203467309474945d, +5.593494835247651E-9d, }, // 110 + {+0.10291612148284912d, +2.8332008343480686E-9d, }, // 111 + {+0.10379679501056671d, -1.3289231465997192E-9d, }, // 112 + {+0.10467669367790222d, -5.526819276639527E-9d, }, // 113 + {+0.10555580258369446d, +6.503128678219282E-9d, }, // 114 + {+0.10643415153026581d, +6.317463237641817E-9d, }, // 115 + {+0.10731174051761627d, -4.728528221305482E-9d, }, // 116 + {+0.10818853974342346d, +4.519199083083901E-9d, }, // 117 + {+0.10906457901000977d, +5.606492666349878E-9d, }, // 118 + {+0.10993985831737518d, -1.220176214398581E-10d, }, // 119 + {+0.11081436276435852d, +3.5759315936869937E-9d, }, // 120 + {+0.11168810725212097d, +3.1367659571899855E-9d, }, // 121 + {+0.11256109178066254d, -1.0543075713098835E-10d,}, // 122 + {+0.11343331634998322d, -4.820065619207094E-9d, }, // 123 + {+0.11430476605892181d, +5.221136819669415E-9d, }, // 124 + {+0.11517547070980072d, +1.5395018670011342E-9d, }, // 125 + {+0.11604541540145874d, +3.5638391501880846E-10d,}, // 126 + {+0.11691460013389587d, +2.9885336757136527E-9d, }, // 127 + {+0.11778303980827332d, -4.151889860890893E-9d, }, // 128 + {+0.11865071952342987d, -4.853823938804204E-9d, }, // 129 + {+0.11951763927936554d, +2.189226237170704E-9d, }, // 130 + {+0.12038381397724152d, +3.3791993048776982E-9d, }, // 131 + {+0.1212492436170578d, +1.5811884868243975E-11d,}, // 132 + {+0.12211392819881439d, -6.6045909118908625E-9d, }, // 133 + {+0.1229778528213501d, -2.8786263916116364E-10d,}, // 134 + {+0.12384103238582611d, +5.354472503748251E-9d, }, // 135 + {+0.12470348179340363d, -3.2924463896248744E-9d, }, // 136 + {+0.12556517124176025d, +4.856678149580005E-9d, }, // 137 + {+0.12642613053321838d, +1.2791850600366742E-9d, }, // 138 + {+0.12728634476661682d, +2.1525945093362843E-9d, }, // 139 + {+0.12814581394195557d, +8.749974471767862E-9d, }, // 140 + {+0.129004567861557d, -7.461209161105275E-9d, }, // 141 + {+0.12986254692077637d, +1.4390208226263824E-8d, }, // 142 + {+0.1307198405265808d, -1.3839477920475328E-8d, }, // 143 + {+0.13157635927200317d, -1.483283901239408E-9d, }, // 144 + {+0.13243216276168823d, -6.889072914229094E-9d, }, // 145 + {+0.1332872211933136d, +9.990351100568362E-10d, }, // 146 + {+0.13414156436920166d, -6.370937412495338E-9d, }, // 147 + {+0.13499516248703003d, +2.05047480130511E-9d, }, // 148 + {+0.1358480453491211d, -2.29509872547079E-9d, }, // 149 + {+0.13670018315315247d, +1.16354361977249E-8d, }, // 150 + {+0.13755163550376892d, -1.452496267904829E-8d, }, // 151 + {+0.1384023129940033d, +9.865115839786888E-9d, }, // 152 + {+0.13925230503082275d, -3.369999130712228E-9d, }, // 153 + {+0.14010155200958252d, +6.602496401651853E-9d, }, // 154 + {+0.14095008373260498d, +1.1205312852298845E-8d, }, // 155 + {+0.14179790019989014d, +1.1660367213160203E-8d, }, // 156 + {+0.142645001411438d, +9.186471222585239E-9d, }, // 157 + {+0.14349138736724854d, +4.999341878263704E-9d, }, // 158 + {+0.14433705806732178d, +3.11611905696257E-10d, }, // 159 + {+0.14518201351165771d, -3.6671598175618173E-9d, }, // 160 + {+0.14602625370025635d, -5.730477881659618E-9d, }, // 161 + {+0.14686977863311768d, -4.674900007989718E-9d, }, // 162 + {+0.1477125883102417d, +6.999732437141968E-10d, }, // 163 + {+0.14855468273162842d, +1.159150872494107E-8d, }, // 164 + {+0.14939609169960022d, -6.082714828488485E-10d, }, // 165 + {+0.15023678541183472d, -4.905712741596318E-9d, }, // 166 + {+0.1510767638683319d, -1.124848988733307E-10d, }, // 167 + {+0.15191605687141418d, -1.484557220949851E-8d, }, // 168 + {+0.15275460481643677d, +1.1682026251371384E-8d, }, // 169 + {+0.15359249711036682d, -8.757272519238786E-9d, }, // 170 + {+0.15442964434623718d, +1.4419920764774415E-8d, }, // 171 + {+0.15526613593101501d, -7.019891063126053E-9d, }, // 172 + {+0.15610191226005554d, -1.230153548825964E-8d, }, // 173 + {+0.15693697333335876d, -2.574172005933276E-10d, }, // 174 + {+0.15777134895324707d, +4.748140799544371E-10d, }, // 175 + {+0.15860503911972046d, -8.943081874891003E-9d, }, // 176 + {+0.15943801403045654d, +2.4500739038517657E-9d, }, // 177 + {+0.1602703034877777d, +6.007922084557054E-9d, }, // 178 + {+0.16110190749168396d, +2.8835418231126645E-9d, }, // 179 + {+0.1619328260421753d, -5.772862039728412E-9d, }, // 180 + {+0.16276302933692932d, +1.0988372954605789E-8d, }, // 181 + {+0.16359257698059082d, -5.292913162607026E-9d, }, // 182 + {+0.16442140936851501d, +6.12956339275823E-9d, }, // 183 + {+0.16524958610534668d, -1.3210039516811888E-8d, }, // 184 + {+0.16607704758644104d, -2.5711014608334873E-9d, }, // 185 + {+0.16690382361412048d, +9.37721319457112E-9d, }, // 186 + {+0.1677299439907074d, -6.0370682395944045E-9d, }, // 187 + {+0.168555349111557d, +1.1918249660105651E-8d, }, // 188 + {+0.1693800985813141d, +4.763282949656017E-9d, }, // 189 + {+0.17020416259765625d, +3.4223342273948817E-9d, }, // 190 + {+0.1710275411605835d, +9.014612241310916E-9d, }, // 191 + {+0.1718502640724182d, -7.145758990550526E-9d, }, // 192 + {+0.172672301530838d, -1.4142763934081504E-8d, }, // 193 + {+0.1734936535358429d, -1.0865453656579032E-8d, }, // 194 + {+0.17431432008743286d, +3.794385569450774E-9d, }, // 195 + {+0.1751343309879303d, +1.1399188501627291E-9d, }, // 196 + {+0.17595365643501282d, +1.2076238768270153E-8d, }, // 197 + {+0.1767723262310028d, +7.901084730502162E-9d, }, // 198 + {+0.17759034037590027d, -1.0288181007465474E-8d, }, // 199 + {+0.1784076690673828d, -1.15945645153806E-8d, }, // 200 + {+0.17922431230545044d, +5.073923825786778E-9d, }, // 201 + {+0.18004029989242554d, +1.1004278077575267E-8d, }, // 202 + {+0.1808556318283081d, +7.2831502374676964E-9d, }, // 203 + {+0.18167030811309814d, -5.0054634662706464E-9d, }, // 204 + {+0.18248429894447327d, +5.022108460298934E-9d, }, // 205 + {+0.18329763412475586d, +8.642254225732676E-9d, }, // 206 + {+0.18411031365394592d, +6.931054493326395E-9d, }, // 207 + {+0.18492233753204346d, +9.619685356326533E-10d, }, // 208 + {+0.18573370575904846d, -8.194157257980706E-9d, }, // 209 + {+0.18654438853263855d, +1.0333241479437797E-8d, }, // 210 + {+0.1873544454574585d, -1.9948340196027965E-9d, }, // 211 + {+0.1881638467311859d, -1.4313002926259948E-8d, }, // 212 + {+0.1889725625514984d, +4.241536392174967E-9d, }, // 213 + {+0.18978065252304077d, -4.877952454011428E-9d, }, // 214 + {+0.1905880868434906d, -1.0813801247641613E-8d, }, // 215 + {+0.1913948655128479d, -1.2513218445781325E-8d, }, // 216 + {+0.19220098853111267d, -8.925958555729115E-9d, }, // 217 + {+0.1930064558982849d, +9.956860681280245E-10d, }, // 218 + {+0.193811297416687d, -1.1505428993246996E-8d, }, // 219 + {+0.1946154534816742d, +1.4217997464522202E-8d, }, // 220 + {+0.19541901350021362d, -1.0200858727747717E-8d, }, // 221 + {+0.19622188806533813d, +5.682607223902455E-9d, }, // 222 + {+0.1970241367816925d, +3.2988908516009827E-9d, }, // 223 + {+0.19782572984695435d, +1.3482965534659446E-8d, }, // 224 + {+0.19862669706344604d, +7.462678536479685E-9d, }, // 225 + {+0.1994270384311676d, -1.3734273888891115E-8d, }, // 226 + {+0.20022669434547424d, +1.0521983802642893E-8d, }, // 227 + {+0.20102575421333313d, -8.152742388541905E-9d, }, // 228 + {+0.2018241584300995d, -9.133484280193855E-9d, }, // 229 + {+0.20262190699577332d, +8.59763959528144E-9d, }, // 230 + {+0.2034190595149994d, -1.3548568223001414E-8d, }, // 231 + {+0.20421552658081055d, +1.4847880344628818E-8d, }, // 232 + {+0.20501139760017395d, +5.390620378060543E-9d, }, // 233 + {+0.2058066427707672d, -1.1109834472051523E-8d, }, // 234 + {+0.20660123229026794d, -3.845373872038116E-9d, }, // 235 + {+0.20739519596099854d, -1.6149279479975042E-9d, }, // 236 + {+0.20818853378295898d, -3.4174925203771133E-9d, }, // 237 + {+0.2089812457561493d, -8.254443919468538E-9d, }, // 238 + {+0.20977330207824707d, +1.4672790944499144E-8d, }, // 239 + {+0.2105647623538971d, +6.753452542942992E-9d, }, // 240 + {+0.21135559678077698d, -1.218609462241927E-9d, }, // 241 + {+0.21214580535888672d, -8.254218316367887E-9d, }, // 242 + {+0.21293538808822632d, -1.3366540360587255E-8d, }, // 243 + {+0.2137243151664734d, +1.4231244750190031E-8d, }, // 244 + {+0.2145126760005951d, -1.3885660525939072E-8d, }, // 245 + {+0.21530038118362427d, -7.3304404046850136E-9d, }, // 246 + {+0.2160874605178833d, +5.072117654842356E-9d, }, // 247 + {+0.21687394380569458d, -5.505080220459036E-9d, }, // 248 + {+0.21765980124473572d, -8.286782292266659E-9d, }, // 249 + {+0.2184450328350067d, -2.302351152358085E-9d, }, // 250 + {+0.21922963857650757d, +1.3416565858314603E-8d, }, // 251 + {+0.22001364827156067d, +1.0033721426962048E-8d, }, // 252 + {+0.22079706192016602d, -1.1487079818684332E-8d, }, // 253 + {+0.22157981991767883d, +9.420348186357043E-9d, }, // 254 + {+0.2223619818687439d, +1.4110645699377834E-8d, }, // 255 + {+0.2231435477733612d, +3.5408485497116107E-9d, }, // 256 + {+0.22392448782920837d, +8.468072777056227E-9d, }, // 257 + {+0.2247048318386078d, +4.255446699237779E-11d, }, // 258 + {+0.22548454999923706d, +9.016946273084244E-9d, }, // 259 + {+0.22626367211341858d, +6.537034810260226E-9d, }, // 260 + {+0.22704219818115234d, -6.451285264969768E-9d, }, // 261 + {+0.22782009840011597d, +7.979956357126066E-10d, }, // 262 + {+0.22859740257263184d, -5.759582672039005E-10d, }, // 263 + {+0.22937411069869995d, -9.633854121180397E-9d, }, // 264 + {+0.23015019297599792d, +4.363736368635843E-9d, }, // 265 + {+0.23092567920684814d, +1.2549416560182509E-8d, }, // 266 + {+0.231700599193573d, -1.3946383592553814E-8d, }, // 267 + {+0.2324748933315277d, -1.458843364504023E-8d, }, // 268 + {+0.23324856162071228d, +1.1551692104697154E-8d, }, // 269 + {+0.23402166366577148d, +5.795621295524984E-9d, }, // 270 + {+0.23479416966438293d, -1.1301979046684263E-9d, }, // 271 + {+0.23556607961654663d, -8.303779721781787E-9d, }, // 272 + {+0.23633739352226257d, -1.4805271785394075E-8d, }, // 273 + {+0.23710808157920837d, +1.0085373835899469E-8d, }, // 274 + {+0.2378782033920288d, +7.679117635349454E-9d, }, // 275 + {+0.2386477291584015d, +8.69177352065934E-9d, }, // 276 + {+0.23941665887832642d, +1.4034725764547136E-8d, }, // 277 + {+0.24018502235412598d, -5.185064518887831E-9d, }, // 278 + {+0.2409527599811554d, +1.1544236628121676E-8d, }, // 279 + {+0.24171993136405945d, +5.523085719902123E-9d, }, // 280 + {+0.24248650670051575d, +7.456824943331887E-9d, }, // 281 + {+0.24325251579284668d, -1.1555923403029638E-8d, }, // 282 + {+0.24401789903640747d, +8.988361382732908E-9d, }, // 283 + {+0.2447827160358429d, +1.0381848020926893E-8d, }, // 284 + {+0.24554696679115295d, -6.480706118857055E-9d, }, // 285 + {+0.24631062150001526d, -1.0904271124793968E-8d, }, // 286 + {+0.2470736801624298d, -1.998183061531611E-9d, }, // 287 + {+0.247836172580719d, -8.676137737360023E-9d, }, // 288 + {+0.24859806895256042d, -2.4921733203932487E-10d,}, // 289 + {+0.2493593990802765d, -5.635173762130303E-9d, }, // 290 + {+0.2501201629638672d, -2.3951455355985637E-8d, }, // 291 + {+0.25088030099868774d, +5.287121672447825E-9d, }, // 292 + {+0.2516399025917053d, -6.447877375049486E-9d, }, // 293 + {+0.25239890813827515d, +1.32472428796441E-9d, }, // 294 + {+0.2531573176383972d, +2.9479464287605006E-8d, }, // 295 + {+0.2539151906967163d, +1.9284247135543574E-8d, }, // 296 + {+0.2546725273132324d, -2.8390360197221716E-8d, }, // 297 + {+0.255429208278656d, +6.533522495226226E-9d, }, // 298 + {+0.2561853528022766d, +5.713225978895991E-9d, }, // 299 + {+0.25694090127944946d, +2.9618050962556135E-8d, }, // 300 + {+0.25769591331481934d, +1.950605015323617E-8d, }, // 301 + {+0.25845038890838623d, -2.3762031507525576E-8d, }, // 302 + {+0.2592042088508606d, +1.98818938195077E-8d, }, // 303 + {+0.25995755195617676d, -2.751925069084042E-8d, }, // 304 + {+0.2607102394104004d, +1.3703391844683932E-8d, }, // 305 + {+0.26146239042282104d, +2.5193525310038174E-8d, }, // 306 + {+0.2622140049934387d, +7.802219817310385E-9d, }, // 307 + {+0.26296502351760864d, +2.1983272709242607E-8d, }, // 308 + {+0.2637155055999756d, +8.979279989292184E-9d, }, // 309 + {+0.2644653916358948d, +2.9240221157844312E-8d, }, // 310 + {+0.265214741230011d, +2.4004885823813374E-8d, }, // 311 + {+0.2659635543823242d, -5.885186277410878E-9d, }, // 312 + {+0.2667117714881897d, +1.4300386517357162E-11d,}, // 313 + {+0.2674594521522522d, -1.7063531531989365E-8d, }, // 314 + {+0.26820653676986694d, +3.3218524692903896E-9d, }, // 315 + {+0.2689530849456787d, +2.3998252479954764E-9d, }, // 316 + {+0.2696990966796875d, -1.8997462070389404E-8d, }, // 317 + {+0.27044451236724854d, -4.350745270980051E-10d, }, // 318 + {+0.2711893916130066d, -6.892221115467135E-10d, }, // 319 + {+0.27193373441696167d, -1.89333199110902E-8d, }, // 320 + {+0.272677481174469d, +5.262017392507765E-9d, }, // 321 + {+0.27342069149017334d, +1.3115046679980076E-8d, }, // 322 + {+0.2741633653640747d, +5.4468361834451975E-9d, }, // 323 + {+0.2749055027961731d, -1.692337384653611E-8d, }, // 324 + {+0.27564704418182373d, +6.426479056697412E-9d, }, // 325 + {+0.2763880491256714d, +1.670735065191342E-8d, }, // 326 + {+0.27712851762771606d, +1.4733029698334834E-8d, }, // 327 + {+0.27786844968795776d, +1.315498542514467E-9d, }, // 328 + {+0.2786078453063965d, -2.2735061539223372E-8d, }, // 329 + {+0.27934664487838745d, +2.994379757313727E-9d, }, // 330 + {+0.28008490800857544d, +1.970577274107218E-8d, }, // 331 + {+0.28082263469696045d, +2.820392733542077E-8d, }, // 332 + {+0.2815598249435425d, +2.929187356678173E-8d, }, // 333 + {+0.28229647874832153d, +2.377086680926386E-8d, }, // 334 + {+0.2830325961112976d, +1.2440393009992529E-8d, }, // 335 + {+0.2837681770324707d, -3.901826104778096E-9d, }, // 336 + {+0.2845032215118408d, -2.4459827842685974E-8d, }, // 337 + {+0.2852376699447632d, +1.1165241398059789E-8d, }, // 338 + {+0.28597164154052734d, -1.54434478239181E-8d, }, // 339 + {+0.28670501708984375d, +1.5714110564653245E-8d, }, // 340 + {+0.28743791580200195d, -1.3782394940142479E-8d, }, // 341 + {+0.2881702184677124d, +1.6063569876284005E-8d, }, // 342 + {+0.28890204429626465d, -1.317176818216125E-8d, }, // 343 + {+0.28963327407836914d, +1.8504673536253893E-8d, }, // 344 + {+0.29036402702331543d, -7.334319635123628E-9d, }, // 345 + {+0.29109418392181396d, +2.9300903540317107E-8d, }, // 346 + {+0.2918238639831543d, +9.979706999541057E-9d, }, // 347 + {+0.29255300760269165d, -4.916314210412424E-9d, }, // 348 + {+0.293281614780426d, -1.4611908070155308E-8d, }, // 349 + {+0.2940096855163574d, -1.833351586679361E-8d, }, // 350 + {+0.29473721981048584d, -1.530926726615185E-8d, }, // 351 + {+0.2954642176628113d, -4.7689754029101934E-9d, }, // 352 + {+0.29619067907333374d, +1.4055868011423819E-8d, }, // 353 + {+0.296916663646698d, -1.7672547212604003E-8d, }, // 354 + {+0.2976420521736145d, +2.0020234215759705E-8d, }, // 355 + {+0.2983669638633728d, +8.688424478730524E-9d, }, // 356 + {+0.2990913391113281d, +8.69851089918337E-9d, }, // 357 + {+0.29981517791748047d, +2.0810681643102672E-8d, }, // 358 + {+0.3005385398864746d, -1.3821169493779352E-8d, }, // 359 + {+0.301261305809021d, +2.4769140784919128E-8d, }, // 360 + {+0.3019835948944092d, +1.8127576600610336E-8d, }, // 361 + {+0.3027053475379944d, +2.6612401062437074E-8d, }, // 362 + {+0.3034266233444214d, -8.629042891789934E-9d, }, // 363 + {+0.3041473627090454d, -2.724174869314043E-8d, }, // 364 + {+0.30486756563186646d, -2.8476975783775358E-8d, }, // 365 + {+0.3055872321128845d, -1.1587600174449919E-8d, }, // 366 + {+0.3063063621520996d, +2.417189020581056E-8d, }, // 367 + {+0.3070250153541565d, +1.99407553679345E-8d, }, // 368 + {+0.3077431917190552d, -2.35387025694381E-8d, }, // 369 + {+0.3084607720375061d, +1.3683509995845583E-8d, }, // 370 + {+0.30917787551879883d, +1.3137214081023085E-8d, }, // 371 + {+0.30989450216293335d, -2.444006866174775E-8d, }, // 372 + {+0.3106105327606201d, +2.0896888605749563E-8d, }, // 373 + {+0.31132614612579346d, -2.893149098508887E-8d, }, // 374 + {+0.31204116344451904d, +5.621509038251498E-9d, }, // 375 + {+0.3127557039260864d, +6.0778104626050015E-9d, }, // 376 + {+0.3134697675704956d, -2.6832941696716294E-8d, }, // 377 + {+0.31418323516845703d, +2.6826625274495256E-8d, }, // 378 + {+0.31489628553390503d, -1.1030897183911054E-8d, }, // 379 + {+0.31560879945755005d, -2.047124671392676E-8d, }, // 380 + {+0.3163207769393921d, -7.709990443086711E-10d, }, // 381 + {+0.3170322775840759d, -1.0812918808112342E-8d, }, // 382 + {+0.3177432417869568d, +9.727979174888975E-9d, }, // 383 + {+0.31845372915267944d, +1.9658551724508715E-9d, }, // 384 + {+0.3191636800765991d, +2.6222628001695826E-8d, }, // 385 + {+0.3198731541633606d, +2.3609400272358744E-8d, }, // 386 + {+0.32058215141296387d, -5.159602957634814E-9d, }, // 387 + {+0.32129061222076416d, +2.329701319016099E-10d, }, // 388 + {+0.32199859619140625d, -1.910633190395738E-8d, }, // 389 + {+0.32270604372024536d, -2.863180390093667E-9d, }, // 390 + {+0.32341301441192627d, -9.934041364456825E-9d, }, // 391 + {+0.3241194486618042d, +1.999240777687192E-8d, }, // 392 + {+0.3248254060745239d, +2.801670341647724E-8d, }, // 393 + {+0.32553088665008545d, +1.4842534265191358E-8d, }, // 394 + {+0.32623589038848877d, -1.882789920477354E-8d, }, // 395 + {+0.3269403576850891d, -1.268923579073577E-8d, }, // 396 + {+0.32764434814453125d, -2.564688370677835E-8d, }, // 397 + {+0.3283478021621704d, +2.6015626820520968E-9d, }, // 398 + {+0.32905077934265137d, +1.3147747907784344E-8d, }, // 399 + {+0.3297532796859741d, +6.686493860720675E-9d, }, // 400 + {+0.33045530319213867d, -1.608884086544153E-8d, }, // 401 + {+0.33115679025650024d, +5.118287907840204E-9d, }, // 402 + {+0.3318578004837036d, +1.139367970944884E-8d, }, // 403 + {+0.3325583338737488d, +3.426327822115399E-9d, }, // 404 + {+0.33325839042663574d, -1.809622142990733E-8d, }, // 405 + {+0.3339579105377197d, +7.116780143398601E-9d, }, // 406 + {+0.3346569538116455d, +2.0145352306345386E-8d, }, // 407 + {+0.3353555202484131d, +2.167272474431968E-8d, }, // 408 + {+0.33605360984802246d, +1.2380696294966822E-8d, }, // 409 + {+0.33675122261047363d, -7.050361059209181E-9d, }, // 410 + {+0.3374482989311218d, +2.366314656322868E-8d, }, // 411 + {+0.3381449580192566d, -1.4010540194086646E-8d, }, // 412 + {+0.3388410806655884d, -1.860165465666482E-10d, }, // 413 + {+0.33953672647476196d, +6.206776940880773E-9d, }, // 414 + {+0.34023189544677734d, +5.841137379010982E-9d, }, // 415 + {+0.3409265875816345d, -6.11041311179286E-10d, }, // 416 + {+0.3416208028793335d, -1.2479264502054702E-8d, }, // 417 + {+0.34231454133987427d, -2.909443297645926E-8d, }, // 418 + {+0.34300774335861206d, +9.815805717097634E-9d, }, // 419 + {+0.3437005281448364d, -1.4291517981101049E-8d, }, // 420 + {+0.3443927764892578d, +1.8457821628427503E-8d, }, // 421 + {+0.34508460760116577d, -1.0481908869377813E-8d, }, // 422 + {+0.34577590227127075d, +1.876076001514746E-8d, }, // 423 + {+0.3464667797088623d, -1.2362653723769037E-8d, }, // 424 + {+0.3471571207046509d, +1.6016578405624026E-8d, }, // 425 + {+0.347847044467926d, -1.4652759033760925E-8d, }, // 426 + {+0.3485364317893982d, +1.549533655901835E-8d, }, // 427 + {+0.34922540187835693d, -1.2093068629412478E-8d, }, // 428 + {+0.3499138355255127d, +2.244531711424792E-8d, }, // 429 + {+0.35060185194015503d, +5.538565518604807E-10d, }, // 430 + {+0.35128939151763916d, -1.7511499366215853E-8d, }, // 431 + {+0.3519763946533203d, +2.850385787215544E-8d, }, // 432 + {+0.35266298055648804d, +2.003926370146842E-8d, }, // 433 + {+0.35334908962249756d, +1.734665280502264E-8d, }, // 434 + {+0.3540347218513489d, +2.1071983674869414E-8d, }, // 435 + {+0.35471993684768677d, -2.774475773922311E-8d, }, // 436 + {+0.3554046154022217d, -9.250975291734664E-9d, }, // 437 + {+0.3560888171195984d, +1.7590672330295415E-8d, }, // 438 + {+0.35677260160446167d, -6.1837904549178745E-9d, }, // 439 + {+0.35745590925216675d, -2.0330362973820856E-8d, }, // 440 + {+0.3581387400627136d, -2.42109990366786E-8d, }, // 441 + {+0.3588210940361023d, -1.7188958587407816E-8d, }, // 442 + {+0.35950297117233276d, +1.3711958590112228E-9d, }, // 443 + {+0.3601844310760498d, -2.7501042008405925E-8d, }, // 444 + {+0.36086535453796387d, +1.6036460343275798E-8d, }, // 445 + {+0.3615458607673645d, +1.3405964389498495E-8d, }, // 446 + {+0.36222589015960693d, +2.484237749027735E-8d, }, // 447 + {+0.36290550231933594d, -8.629967484362177E-9d, }, // 448 + {+0.36358463764190674d, -2.6778729562324134E-8d, }, // 449 + {+0.36426329612731934d, -2.8977490516960565E-8d, }, // 450 + {+0.36494147777557373d, -1.4601106624823502E-8d, }, // 451 + {+0.3656191825866699d, +1.69742947894444E-8d, }, // 452 + {+0.3662964701652527d, +6.7666740211281175E-9d, }, // 453 + {+0.36697328090667725d, +1.500201674336832E-8d, }, // 454 + {+0.3676496744155884d, -1.730424167425052E-8d, }, // 455 + {+0.36832553148269653d, +2.9676011119845104E-8d, }, // 456 + {+0.36900103092193604d, -2.2253590346826743E-8d, }, // 457 + {+0.36967599391937256d, +6.3372065441089185E-9d, }, // 458 + {+0.37035053968429565d, -3.145816653215968E-9d, }, // 459 + {+0.37102460861206055d, +9.515812117036965E-9d, }, // 460 + {+0.371698260307312d, -1.4669965113042639E-8d, }, // 461 + {+0.3723714351654053d, -1.548715389333397E-8d, }, // 462 + {+0.37304413318634033d, +7.674361647125109E-9d, }, // 463 + {+0.37371641397476196d, -4.181177882069608E-9d, }, // 464 + {+0.3743882179260254d, +9.158530500130718E-9d, }, // 465 + {+0.3750596046447754d, -1.13047236597869E-8d, }, // 466 + {+0.3757305145263672d, -5.36108186384227E-9d, }, // 467 + {+0.3764009475708008d, +2.7593452284747873E-8d, }, // 468 + {+0.37707096338272095d, +2.8557016344085205E-8d, }, // 469 + {+0.3777405619621277d, -1.868818164036E-9d, }, // 470 + {+0.3784096837043762d, -3.479042513414447E-9d, }, // 471 + {+0.37907832860946655d, +2.432550290565648E-8d, }, // 472 + {+0.37974655628204346d, +2.2538131805476768E-8d, }, // 473 + {+0.38041436672210693d, -8.244395239939089E-9d, }, // 474 + {+0.3810817003250122d, -7.821867597227376E-9d, }, // 475 + {+0.3817485570907593d, +2.4400089062515914E-8d, }, // 476 + {+0.3824149966239929d, +2.9410015940087773E-8d, }, // 477 + {+0.38308101892471313d, +7.799913824734797E-9d, }, // 478 + {+0.38374656438827515d, +1.976524624939355E-8d, }, // 479 + {+0.38441169261932373d, +6.291008309266035E-9d, }, // 480 + {+0.3850763440132141d, +2.757030889767851E-8d, }, // 481 + {+0.38574057817459106d, +2.4585794728405612E-8d, }, // 482 + {+0.3864043951034546d, -2.0764122246389383E-9d, }, // 483 + {+0.3870677351951599d, +7.77328837578952E-9d, }, // 484 + {+0.3877306580543518d, -4.8859560029989374E-9d, }, // 485 + {+0.3883931040763855d, +2.0133131420595028E-8d, }, // 486 + {+0.38905513286590576d, +2.380738071335498E-8d, }, // 487 + {+0.3897167444229126d, +6.7171126157142075E-9d, }, // 488 + {+0.39037787914276123d, +2.9046141593926277E-8d, }, // 489 + {+0.3910386562347412d, -2.7836800219410262E-8d, }, // 490 + {+0.3916988968849182d, +1.545909820981726E-8d, }, // 491 + {+0.39235877990722656d, -1.930436269002062E-8d, }, // 492 + {+0.3930181860923767d, -1.2343297554921835E-8d, }, // 493 + {+0.3936771750450134d, -2.268889128622553E-8d, }, // 494 + {+0.39433568716049194d, +9.835827818608177E-9d, }, // 495 + {+0.39499378204345703d, +2.6197411946856397E-8d, }, // 496 + {+0.3956514596939087d, +2.6965931069318893E-8d, }, // 497 + {+0.3963087201118469d, +1.2710331127772166E-8d, }, // 498 + {+0.39696556329727173d, -1.6001563011916016E-8d, }, // 499 + {+0.39762192964553833d, +1.0016001590267064E-9d, }, // 500 + {+0.3982778787612915d, +4.680767399874334E-9d, }, // 501 + {+0.39893341064453125d, -4.399582029272418E-9d, }, // 502 + {+0.39958852529525757d, -2.5676078228301587E-8d, }, // 503 + {+0.4002431631088257d, +1.0181870233355787E-9d, }, // 504 + {+0.40089738368988037d, +1.6639728835984655E-8d, }, // 505 + {+0.40155118703842163d, +2.174860642202632E-8d, }, // 506 + {+0.40220457315444946d, +1.6903781197123503E-8d, }, // 507 + {+0.40285754203796387d, +2.663119647467697E-9d, }, // 508 + {+0.40351009368896484d, -2.0416603812329616E-8d, }, // 509 + {+0.4041621685028076d, +7.82494078472695E-9d, }, // 510 + {+0.40481382608413696d, +2.833770747113627E-8d, }, // 511 + {+0.40546512603759766d, -1.7929433274271985E-8d, }, // 512 + {+0.40611594915390015d, -1.1214757379328965E-8d, }, // 513 + {+0.4067663550376892d, -1.0571553019207106E-8d, }, // 514 + {+0.40741634368896484d, -1.5449538712332313E-8d, }, // 515 + {+0.40806591510772705d, -2.529950530235105E-8d, }, // 516 + {+0.40871500968933105d, +2.0031331601617008E-8d, }, // 517 + {+0.4093637466430664d, +1.880755298741952E-9d, }, // 518 + {+0.41001206636428833d, -1.9600580584843318E-8d, }, // 519 + {+0.41065990924835205d, +1.573691633515306E-8d, }, // 520 + {+0.4113073945045471d, -1.0772154376548336E-8d, }, // 521 + {+0.411954402923584d, +2.0624330192486066E-8d, }, // 522 + {+0.4126010537147522d, -8.741139170029572E-9d, }, // 523 + {+0.4132472276687622d, +2.0881457123894216E-8d, }, // 524 + {+0.41389304399490356d, -9.177488027521808E-9d, }, // 525 + {+0.4145383834838867d, +2.0829952491625585E-8d, }, // 526 + {+0.4151833653450012d, -7.767915492597301E-9d, }, // 527 + {+0.4158278703689575d, +2.4774753446082082E-8d, }, // 528 + {+0.41647201776504517d, -2.1581119071750435E-10d,}, // 529 + {+0.4171157479286194d, -2.260047972865202E-8d, }, // 530 + {+0.4177590012550354d, +1.775884601423381E-8d, }, // 531 + {+0.41840189695358276d, +2.185301053838889E-9d, }, // 532 + {+0.4190443754196167d, -9.185071463667081E-9d, }, // 533 + {+0.4196864366531372d, -1.5821896727910552E-8d, }, // 534 + {+0.4203280806541443d, -1.719582086188318E-8d, }, // 535 + {+0.42096930742263794d, -1.2778508303324259E-8d, }, // 536 + {+0.42161011695861816d, -2.042639194493364E-9d, }, // 537 + {+0.42225050926208496d, +1.5538093219698803E-8d, }, // 538 + {+0.4228905439376831d, -1.9115659590156936E-8d, }, // 539 + {+0.42353010177612305d, +1.3729680248843432E-8d, }, // 540 + {+0.42416930198669434d, -4.611893838830296E-9d, }, // 541 + {+0.4248080849647522d, -1.4013456880651706E-8d, }, // 542 + {+0.42544645071029663d, -1.3953728897042917E-8d, }, // 543 + {+0.42608439922332764d, -3.912427573594197E-9d, }, // 544 + {+0.4267219305038452d, +1.6629734283189315E-8d, }, // 545 + {+0.42735910415649414d, -1.1413593493354881E-8d, }, // 546 + {+0.42799586057662964d, -2.792046157580119E-8d, }, // 547 + {+0.42863214015960693d, +2.723009182661306E-8d, }, // 548 + {+0.42926812171936035d, -2.4260535621557444E-8d, }, // 549 + {+0.42990362644195557d, -3.064060124024764E-9d, }, // 550 + {+0.43053877353668213d, -2.787640178598121E-8d, }, // 551 + {+0.4311734437942505d, +2.102412085257792E-8d, }, // 552 + {+0.4318077564239502d, +2.4939635093999683E-8d, }, // 553 + {+0.43244171142578125d, -1.5619414792273914E-8d, }, // 554 + {+0.4330751895904541d, +1.9065734894871523E-8d, }, // 555 + {+0.4337083101272583d, +1.0294301092654604E-8d, }, // 556 + {+0.4343410134315491d, +1.8178469851136E-8d, }, // 557 + {+0.4349733591079712d, -1.6379825102473853E-8d, }, // 558 + {+0.4356052279472351d, +2.6334323946685834E-8d, }, // 559 + {+0.43623673915863037d, +2.761628769925529E-8d, }, // 560 + {+0.436867892742157d, -1.2030229087793677E-8d, }, // 561 + {+0.4374985694885254d, +2.7106814809424793E-8d, }, // 562 + {+0.43812888860702515d, +2.631993083235205E-8d, }, // 563 + {+0.43875885009765625d, -1.3890028312254422E-8d, }, // 564 + {+0.43938833475112915d, +2.6186133735555794E-8d, }, // 565 + {+0.4400174617767334d, +2.783809071694788E-8d, }, // 566 + {+0.440646231174469d, -8.436135220472006E-9d, }, // 567 + {+0.44127458333969116d, -2.2534815932619883E-8d, }, // 568 + {+0.4419025182723999d, -1.3961804471714283E-8d, }, // 569 + {+0.4425300359725952d, +1.7778112039716255E-8d, }, // 570 + {+0.4431571960449219d, +1.3574569976673652E-8d, }, // 571 + {+0.4437839984893799d, -2.607907890164073E-8d, }, // 572 + {+0.4444103240966797d, +1.8518879652136628E-8d, }, // 573 + {+0.44503629207611084d, +2.865065604247164E-8d, }, // 574 + {+0.44566190242767334d, +4.806827797299427E-9d, }, // 575 + {+0.4462870955467224d, +7.0816970994232115E-9d, }, // 576 + {+0.44691193103790283d, -2.3640641240074437E-8d, }, // 577 + {+0.4475363492965698d, -2.7267718387865538E-8d, }, // 578 + {+0.4481603503227234d, -3.3126235292976077E-9d, }, // 579 + {+0.4487839937210083d, -1.0894001590268427E-8d, }, // 580 + {+0.4494072198867798d, +1.0077883359971829E-8d, }, // 581 + {+0.4500300884246826d, +4.825712712114668E-10d, }, // 582 + {+0.450652539730072d, +2.0407987470746858E-8d, }, // 583 + {+0.4512746334075928d, +1.073186581170719E-8d, }, // 584 + {+0.4518963694572449d, -2.8064314757880205E-8d, }, // 585 + {+0.45251762866973877d, +2.3709316816226527E-8d, }, // 586 + {+0.4531385898590088d, -1.2281487504266522E-8d, }, // 587 + {+0.4537591338157654d, -1.634864487421458E-8d, }, // 588 + {+0.45437926054000854d, +1.1985747222409522E-8d, }, // 589 + {+0.45499902963638306d, +1.3594057956219485E-8d, }, // 590 + {+0.4556184411048889d, -1.1047585095328619E-8d, }, // 591 + {+0.45623743534088135d, -1.8592937532754405E-9d, }, // 592 + {+0.4568560719490051d, -1.797135137545755E-8d, }, // 593 + {+0.4574742913246155d, +6.943684261645378E-10d, }, // 594 + {+0.4580921530723572d, -4.994175141684681E-9d, }, // 595 + {+0.45870959758758545d, +2.5039391215625133E-8d, }, // 596 + {+0.45932674407958984d, -2.7943366835352838E-8d, }, // 597 + {+0.45994341373443604d, +1.534146910128904E-8d, }, // 598 + {+0.46055978536605835d, -2.3450920230816267E-8d, }, // 599 + {+0.46117573976516724d, -2.4642997069960124E-8d, }, // 600 + {+0.4617912769317627d, +1.2232622070370946E-8d, }, // 601 + {+0.4624064564704895d, +2.80378133047839E-8d, }, // 602 + {+0.46302127838134766d, +2.3238237048117092E-8d, }, // 603 + {+0.46363574266433716d, -1.7013046451109475E-9d, }, // 604 + {+0.46424978971481323d, +1.3287778803035383E-8d, }, // 605 + {+0.46486347913742065d, +9.06393426961373E-9d, }, // 606 + {+0.4654768109321594d, -1.3910598647592876E-8d, }, // 607 + {+0.46608972549438477d, +4.430214458933614E-9d, }, // 608 + {+0.46670228242874146d, +4.942270562885745E-9d, }, // 609 + {+0.4673144817352295d, -1.1914734393460718E-8d, }, // 610 + {+0.4679262638092041d, +1.3922696570638494E-8d, }, // 611 + {+0.46853768825531006d, +2.3307929211781914E-8d, }, // 612 + {+0.46914875507354736d, +1.669813444584674E-8d, }, // 613 + {+0.469759464263916d, -5.450354376430758E-9d, }, // 614 + {+0.47036975622177124d, +1.6922605350647674E-8d, }, // 615 + {+0.4709796905517578d, +2.4667033200046904E-8d, }, // 616 + {+0.47158926725387573d, +1.8236762070433784E-8d, }, // 617 + {+0.472198486328125d, -1.915204563140137E-9d, }, // 618 + {+0.47280728816986084d, +2.426795414605756E-8d, }, // 619 + {+0.4734157919883728d, -2.19717006713618E-8d, }, // 620 + {+0.47402387857437134d, -2.0974352165535873E-8d, }, // 621 + {+0.47463154792785645d, +2.770970558184228E-8d, }, // 622 + {+0.4752389192581177d, +5.32006955298355E-9d, }, // 623 + {+0.47584593296051025d, -2.809054633964104E-8d, }, // 624 + {+0.4764525294303894d, -1.2470243596102937E-8d, }, // 625 + {+0.4770587682723999d, -6.977226702440138E-9d, }, // 626 + {+0.47766464948654175d, -1.1165866833118273E-8d, }, // 627 + {+0.47827017307281494d, -2.4591344661022708E-8d, }, // 628 + {+0.4788752794265747d, +1.2794996377383974E-8d, }, // 629 + {+0.4794800877571106d, -1.7772927065973874E-8d, }, // 630 + {+0.48008447885513306d, +3.35657712457243E-9d, }, // 631 + {+0.48068851232528687d, +1.7020465042442242E-8d, }, // 632 + {+0.481292188167572d, +2.365953779624783E-8d, }, // 633 + {+0.4818955063819885d, +2.3713798664443718E-8d, }, // 634 + {+0.4824984669685364d, +1.7622455019548098E-8d, }, // 635 + {+0.4831010699272156d, +5.823920246566496E-9d, }, // 636 + {+0.4837033152580261d, -1.1244184344361017E-8d, }, // 637 + {+0.48430514335632324d, +2.645961716432205E-8d, }, // 638 + {+0.4849066734313965d, +1.6207809718247905E-10d,}, // 639 + {+0.4855077862739563d, +2.9507744508973654E-8d, }, // 640 + {+0.48610860109329224d, -4.278201128741098E-9d, }, // 641 + {+0.48670899868011475d, +1.844722015961139E-8d, }, // 642 + {+0.4873090982437134d, -2.1092372471088425E-8d, }, // 643 + {+0.4879087805747986d, -3.2555596107382053E-9d, }, // 644 + {+0.48850810527801514d, +1.2784366845429667E-8d, }, // 645 + {+0.48910707235336304d, +2.7457984659996047E-8d, }, // 646 + {+0.48970574140548706d, -1.8409546441412518E-8d, }, // 647 + {+0.49030399322509766d, -5.179903818099661E-9d, }, // 648 + {+0.4909018874168396d, +7.97053127828682E-9d, }, // 649 + {+0.4914994239807129d, +2.146925464473481E-8d, }, // 650 + {+0.4920966625213623d, -2.3861648589988232E-8d, }, // 651 + {+0.4926934838294983d, -8.386923035320549E-9d, }, // 652 + {+0.4932899475097656d, +8.713990131749256E-9d, }, // 653 + {+0.4938860535621643d, +2.7865534085810115E-8d, }, // 654 + {+0.4944818615913391d, -1.011325138560159E-8d, }, // 655 + {+0.4950772523880005d, +1.4409851026316708E-8d, }, // 656 + {+0.495672345161438d, -1.735227547472004E-8d, }, // 657 + {+0.49626702070236206d, +1.4231078209064581E-8d, }, // 658 + {+0.49686139822006226d, -9.628709342929729E-9d, }, // 659 + {+0.4974554181098938d, -2.8907074856577267E-8d, }, // 660 + {+0.4980490207672119d, +1.6419797090870802E-8d, }, // 661 + {+0.49864232540130615d, +7.561041519403049E-9d, }, // 662 + {+0.49923527240753174d, +4.538983468118194E-9d, }, // 663 + {+0.49982786178588867d, +7.770560657946324E-9d, }, // 664 + {+0.500420093536377d, +1.767197002609876E-8d, }, // 665 + {+0.5010119676589966d, +3.46586694799214E-8d, }, // 666 + {+0.5016034841537476d, +5.914537964556077E-8d, }, // 667 + {+0.5021947622299194d, -2.7663203939320167E-8d, }, // 668 + {+0.5027855634689331d, +1.3064749115929298E-8d, }, // 669 + {+0.5033761262893677d, -5.667682106730711E-8d, }, // 670 + {+0.503966212272644d, +1.9424534974370594E-9d, }, // 671 + {+0.5045560598373413d, -4.908494602153544E-8d, }, // 672 + {+0.5051454305648804d, +2.906989285008994E-8d, }, // 673 + {+0.5057345628738403d, -1.602000800745108E-9d, }, // 674 + {+0.5063233375549316d, -2.148245271118002E-8d, }, // 675 + {+0.5069117546081543d, -3.016329994276181E-8d, }, // 676 + {+0.5074998140335083d, -2.7237099632871992E-8d, }, // 677 + {+0.5080875158309937d, -1.2297127301923986E-8d, }, // 678 + {+0.5086748600006104d, +1.5062624834468093E-8d, }, // 679 + {+0.5092618465423584d, +5.524744954836658E-8d, }, // 680 + {+0.5098485946655273d, -1.054736327333046E-8d, }, // 681 + {+0.5104348659515381d, +5.650063324725722E-8d, }, // 682 + {+0.5110208988189697d, +1.8376017791642605E-8d, }, // 683 + {+0.5116065740585327d, -5.309470636324855E-9d, }, // 684 + {+0.512191891670227d, -1.4154089255217218E-8d, }, // 685 + {+0.5127768516540527d, -7.756800301729815E-9d, }, // 686 + {+0.5133614540100098d, +1.4282730618002001E-8d, }, // 687 + {+0.5139456987380981d, +5.2364136172269755E-8d, }, // 688 + {+0.5145297050476074d, -1.2322940607922115E-8d, }, // 689 + {+0.5151132345199585d, +5.903831350855322E-8d, }, // 690 + {+0.5156965255737305d, +2.8426856726994483E-8d, }, // 691 + {+0.5162794589996338d, +1.544882070711032E-8d, }, // 692 + {+0.5168620347976685d, +2.0500353979930155E-8d, }, // 693 + {+0.5174442529678345d, +4.397691311390564E-8d, }, // 694 + {+0.5180262327194214d, -3.2936025225250634E-8d, }, // 695 + {+0.5186077356338501d, +2.857419553449673E-8d, }, // 696 + {+0.5191890001296997d, -9.51761338269325E-9d, }, // 697 + {+0.5197699069976807d, -2.7609457648450225E-8d, }, // 698 + {+0.520350456237793d, -2.5309316441333305E-8d, }, // 699 + {+0.5209306478500366d, -2.2258513086839407E-9d, }, // 700 + {+0.5215104818344116d, +4.203159541613745E-8d, }, // 701 + {+0.5220900774002075d, -1.1356287358852729E-8d, }, // 702 + {+0.5226693153381348d, -4.279090925831093E-8d, }, // 703 + {+0.5232481956481934d, -5.188364552285819E-8d, }, // 704 + {+0.5238267183303833d, -3.82465458937857E-8d, }, // 705 + {+0.5244048833847046d, -1.4923330530645769E-9d, }, // 706 + {+0.5249826908111572d, +5.8765598932137004E-8d, }, // 707 + {+0.5255602598190308d, +2.3703896609663678E-8d, }, // 708 + {+0.5261374711990356d, +1.2917117341231647E-8d, }, // 709 + {+0.5267143249511719d, +2.6789862192139226E-8d, }, // 710 + {+0.527290940284729d, -5.350322253112414E-8d, }, // 711 + {+0.5278670787811279d, +1.0839714455426386E-8d, }, // 712 + {+0.5284429788589478d, -1.821729591343314E-8d, }, // 713 + {+0.5290185213088989d, -2.1083014672301448E-8d, }, // 714 + {+0.5295937061309814d, +2.623848491704216E-9d, }, // 715 + {+0.5301685333251953d, +5.328392630534142E-8d, }, // 716 + {+0.5307431221008301d, +1.206790586971942E-8d, }, // 717 + {+0.5313173532485962d, -1.4356011804377797E-9d, }, // 718 + {+0.5318912267684937d, +1.3152074173459994E-8d, }, // 719 + {+0.5324647426605225d, +5.6208949382936426E-8d, }, // 720 + {+0.5330380201339722d, +8.90310227565917E-9d, }, // 721 + {+0.5336109399795532d, -9.179458802504127E-9d, }, // 722 + {+0.5341835021972656d, +2.337337845617735E-9d, }, // 723 + {+0.5347557067871094d, +4.3828918300477925E-8d, }, // 724 + {+0.535327672958374d, -3.5392250480081715E-9d, }, // 725 + {+0.53589928150177d, -2.0183663375378704E-8d, }, // 726 + {+0.5364705324172974d, -5.730898606435436E-9d, }, // 727 + {+0.537041425704956d, +4.0191927599879235E-8d, }, // 728 + {+0.5376120805740356d, -1.2522542401353875E-9d, }, // 729 + {+0.5381823778152466d, -1.0482571326594316E-8d, }, // 730 + {+0.5387523174285889d, +1.2871924223480165E-8d, }, // 731 + {+0.539322018623352d, -5.002774317612589E-8d, }, // 732 + {+0.539891242980957d, +3.960668706590162E-8d, }, // 733 + {+0.5404602289199829d, +4.372568630242375E-8d, }, // 734 + {+0.5410289764404297d, -3.730232461206926E-8d, }, // 735 + {+0.5415972471237183d, +3.5309026109857795E-8d, }, // 736 + {+0.5421652793884277d, +2.3508325311148225E-8d, }, // 737 + {+0.5427329540252686d, +4.6871403168921666E-8d, }, // 738 + {+0.5433003902435303d, -1.3445113140270216E-8d, }, // 739 + {+0.5438674688339233d, -3.786663982218041E-8d, }, // 740 + {+0.5444341897964478d, -2.602850370608209E-8d, }, // 741 + {+0.5450005531311035d, +2.2433348713144506E-8d, }, // 742 + {+0.5455666780471802d, -1.1326936872620137E-8d, }, // 743 + {+0.5461324453353882d, -7.737252533211342E-9d, }, // 744 + {+0.5466978549957275d, +3.3564604642699844E-8d, }, // 745 + {+0.5472630262374878d, -6.269066061111782E-9d, }, // 746 + {+0.5478278398513794d, -7.667998948729528E-9d, }, // 747 + {+0.5483922958374023d, +2.9728170818998143E-8d, }, // 748 + {+0.5489565134048462d, -1.2930091396008281E-8d, }, // 749 + {+0.5495203733444214d, -1.607434968107079E-8d, }, // 750 + {+0.5500838756561279d, +2.0653935146671156E-8d, }, // 751 + {+0.5506471395492554d, -2.1596593091833788E-8d, }, // 752 + {+0.5512100458145142d, -2.3259315921149476E-8d, }, // 753 + {+0.5517725944519043d, +1.6022492496522704E-8d, }, // 754 + {+0.5523349046707153d, -2.260433328226171E-8d, }, // 755 + {+0.5528968572616577d, -1.957497997726303E-8d, }, // 756 + {+0.5534584522247314d, +2.5465477111883854E-8d, }, // 757 + {+0.5540198087692261d, -6.33792454933092E-9d, }, // 758 + {+0.554580807685852d, +4.577835263278281E-9d, }, // 759 + {+0.5551414489746094d, +5.856589221771548E-8d, }, // 760 + {+0.5557018518447876d, +3.6769498759522324E-8d, }, // 761 + {+0.5562618970870972d, +5.874989409410614E-8d, }, // 762 + {+0.5568217039108276d, +5.649147309876989E-9d, }, // 763 + {+0.5573811531066895d, -2.9726830960751796E-9d, }, // 764 + {+0.5579402446746826d, +3.323458344853057E-8d, }, // 765 + {+0.5584990978240967d, -4.588749093664028E-9d, }, // 766 + {+0.5590575933456421d, +3.115616594184543E-9d, }, // 767 + {+0.5596157312393188d, +5.6696103838614634E-8d, }, // 768 + {+0.5601736307144165d, +3.7291263280048303E-8d, }, // 769 + {+0.5607312917709351d, -5.4751646725093355E-8d, }, // 770 + {+0.5612884759902954d, +1.9332630743320287E-8d, }, // 771 + {+0.5618454217910767d, +2.147161515775941E-8d, }, // 772 + {+0.5624021291732788d, -4.7989172862560625E-8d, }, // 773 + {+0.5629583597183228d, +4.971378973445109E-8d, }, // 774 + {+0.5635144710540771d, -4.2702997139152675E-8d, }, // 775 + {+0.5640701055526733d, +3.273212962622764E-8d, }, // 776 + {+0.5646255016326904d, +3.79438125545842E-8d, }, // 777 + {+0.5651806592941284d, -2.6725298288329835E-8d, }, // 778 + {+0.5657354593276978d, -4.1723833577410244E-8d, }, // 779 + {+0.5662899017333984d, -6.71028256490915E-9d, }, // 780 + {+0.56684410572052d, -4.055299181908475E-8d, }, // 781 + {+0.567397952079773d, -2.3702295314000405E-8d, }, // 782 + {+0.5679514408111572d, +4.4181618172507453E-8d, }, // 783 + {+0.5685046911239624d, +4.4228706309734985E-8d, }, // 784 + {+0.5690577030181885d, -2.3222346436879016E-8d, }, // 785 + {+0.5696103572845459d, -3.862412756175274E-8d, }, // 786 + {+0.5701626539230347d, -1.6390743801589046E-9d, }, // 787 + {+0.5707147121429443d, -3.1139472791083883E-8d, }, // 788 + {+0.5712664127349854d, -7.579587391156013E-9d, }, // 789 + {+0.5718178749084473d, -4.983281844744412E-8d, }, // 790 + {+0.5723689794540405d, -3.835454246739619E-8d, }, // 791 + {+0.5729197263717651d, +2.7190020372374008E-8d, }, // 792 + {+0.5734702348709106d, +2.7925807446276126E-8d, }, // 793 + {+0.574020504951477d, -3.5813506001861646E-8d, }, // 794 + {+0.5745704174041748d, -4.448550564530588E-8d, }, // 795 + {+0.5751199722290039d, +2.2423840341717488E-9d, }, // 796 + {+0.5756692886352539d, -1.450709904687712E-8d, }, // 797 + {+0.5762182474136353d, +2.4806815282282017E-8d, }, // 798 + {+0.5767669677734375d, +1.3057724436551892E-9d, }, // 799 + {+0.5773153305053711d, +3.4529452510568104E-8d, }, // 800 + {+0.5778634548187256d, +5.598413198183808E-9d, }, // 801 + {+0.5784112215042114d, +3.405124925700107E-8d, }, // 802 + {+0.5789587497711182d, +1.0074354568442952E-9d, }, // 803 + {+0.5795059204101562d, +2.600448597385527E-8d, }, // 804 + {+0.5800528526306152d, -9.83920263200211E-9d, }, // 805 + {+0.5805994272232056d, +1.3012807963586057E-8d, }, // 806 + {+0.5811457633972168d, -2.432215917965441E-8d, }, // 807 + {+0.5816917419433594d, -2.308736892479391E-9d, }, // 808 + {+0.5822374820709229d, -3.983067093146514E-8d, }, // 809 + {+0.5827828645706177d, -1.735366061128156E-8d, }, // 810 + {+0.5833280086517334d, -5.376251584638963E-8d, }, // 811 + {+0.5838727951049805d, -2.952399778965259E-8d, }, // 812 + {+0.5844172239303589d, +5.5685313670430624E-8d, }, // 813 + {+0.5849615335464478d, -3.6230268489088716E-8d, }, // 814 + {+0.5855053663253784d, +5.267948957869391E-8d, }, // 815 + {+0.5860490798950195d, -3.489144132234588E-8d, }, // 816 + {+0.5865923166275024d, +5.9006122320612716E-8d, }, // 817 + {+0.5871354341506958d, -2.2934896740542648E-8d, }, // 818 + {+0.5876781940460205d, -4.1975650319859075E-8d, }, // 819 + {+0.5882205963134766d, +2.2036094805348692E-9d, }, // 820 + {+0.5887627601623535d, -9.287179048539306E-9d, }, // 821 + {+0.5893045663833618d, +4.3079982556221595E-8d, }, // 822 + {+0.589846134185791d, +4.041399585161321E-8d, }, // 823 + {+0.5903874635696411d, -1.696746473863933E-8d, }, // 824 + {+0.5909284353256226d, -9.53795080582038E-9d, }, // 825 + {+0.5914691686630249d, -5.619010749352923E-8d, }, // 826 + {+0.5920095443725586d, -3.7398514182529506E-8d, }, // 827 + {+0.5925495624542236d, +4.71524479659295E-8d, }, // 828 + {+0.5930894613265991d, -4.0640692434639215E-8d, }, // 829 + {+0.5936288833618164d, +5.716453096255401E-8d, }, // 830 + {+0.5941681861877441d, -1.6745661720946737E-8d, }, // 831 + {+0.5947071313858032d, -2.3639110433141897E-8d, }, // 832 + {+0.5952457189559937d, +3.67972590471072E-8d, }, // 833 + {+0.595784068107605d, +4.566672575206695E-8d, }, // 834 + {+0.5963221788406372d, +3.2813537149653483E-9d, }, // 835 + {+0.5968599319458008d, +2.916199305533732E-8d, }, // 836 + {+0.5973974466323853d, +4.410412409109416E-9d, }, // 837 + {+0.5979346036911011d, +4.85464582112459E-8d, }, // 838 + {+0.5984715223312378d, +4.267089756924666E-8d, }, // 839 + {+0.5990082025527954d, -1.2906712010774655E-8d, }, // 840 + {+0.5995445251464844d, +1.3319784467641742E-9d, }, // 841 + {+0.6000806093215942d, -3.35137581974451E-8d, }, // 842 + {+0.6006163358688354d, +2.0734340706476473E-9d, }, // 843 + {+0.6011518239974976d, -1.0808162722402073E-8d, }, // 844 + {+0.601686954498291d, +4.735781872502109E-8d, }, // 845 + {+0.6022218465805054d, +5.76686738430634E-8d, }, // 846 + {+0.6027565002441406d, +2.043049589651736E-8d, }, // 847 + {+0.6032907962799072d, +5.515817703577808E-8d, }, // 848 + {+0.6038248538970947d, +4.2947540692649586E-8d, }, // 849 + {+0.6043586730957031d, -1.589678872195875E-8d, }, // 850 + {+0.6048921346664429d, -1.8613847754677912E-9d, }, // 851 + {+0.6054253578186035d, -3.3851886626187444E-8d, }, // 852 + {+0.6059582233428955d, +7.64416021682279E-9d, }, // 853 + {+0.6064908504486084d, +3.7201467248814224E-9d, }, // 854 + {+0.6070232391357422d, -4.532172996647129E-8d, }, // 855 + {+0.6075552701950073d, -1.997046552871766E-8d, }, // 856 + {+0.6080870628356934d, -3.913411606668587E-8d, }, // 857 + {+0.6086184978485107d, +1.6697361107868944E-8d, }, // 858 + {+0.609149694442749d, +2.8614950293715483E-8d, }, // 859 + {+0.6096806526184082d, -3.081552929643174E-9d, }, // 860 + {+0.6102112531661987d, +4.111645931319645E-8d, }, // 861 + {+0.6107416152954102d, +4.2298539553668435E-8d, }, // 862 + {+0.6112717390060425d, +7.630546413718035E-10d, }, // 863 + {+0.6118015050888062d, +3.601718675118614E-8d, }, // 864 + {+0.6123310327529907d, +2.914906573537692E-8d, }, // 865 + {+0.6128603219985962d, -1.9544361222269494E-8d, }, // 866 + {+0.613389253616333d, +9.442671392695732E-9d, }, // 867 + {+0.6139179468154907d, -2.8031202304593286E-9d, }, // 868 + {+0.6144464015960693d, -5.598619958143586E-8d, }, // 869 + {+0.6149744987487793d, -3.060220883766096E-8d, }, // 870 + {+0.6155023574829102d, -4.556583652800433E-8d, }, // 871 + {+0.6160298585891724d, +1.8626341656366314E-8d, }, // 872 + {+0.6165571212768555d, +4.305870564227991E-8d, }, // 873 + {+0.6170841455459595d, +2.8024460607734262E-8d, }, // 874 + {+0.6176109313964844d, -2.6183651590639875E-8d, }, // 875 + {+0.6181373596191406d, -6.406189112730307E-11d, }, // 876 + {+0.6186635494232178d, -1.2534241706168776E-8d, }, // 877 + {+0.6191893815994263d, +5.5906456251308664E-8d, }, // 878 + {+0.6197150945663452d, -3.286964881802063E-8d, }, // 879 + {+0.6202404499053955d, -4.0153537978961E-8d, }, // 880 + {+0.6207654476165771d, +3.434477109643361E-8d, }, // 881 + {+0.6212903261184692d, -4.750377491075032E-8d, }, // 882 + {+0.6218148469924927d, -4.699152670372743E-8d, }, // 883 + {+0.6223390102386475d, +3.617013128065961E-8d, }, // 884 + {+0.6228630542755127d, -3.6149218175202596E-8d, }, // 885 + {+0.6233867406845093d, -2.5243286814648133E-8d, }, // 886 + {+0.6239101886749268d, -5.003410681432538E-8d, }, // 887 + {+0.6244332790374756d, +8.974417915105033E-9d, }, // 888 + {+0.6249561309814453d, +3.285935446876949E-8d, }, // 889 + {+0.6254787445068359d, +2.190661054038537E-8d, }, // 890 + {+0.6260011196136475d, -2.3598354190515998E-8d, }, // 891 + {+0.6265231370925903d, +1.5838762427747586E-8d, }, // 892 + {+0.6270449161529541d, +2.129323729978037E-8d, }, // 893 + {+0.6275664567947388d, -6.950808333865794E-9d, }, // 894 + {+0.6280876398086548d, +5.059959203156465E-8d, }, // 895 + {+0.6286087036132812d, -4.41909071122557E-8d, }, // 896 + {+0.6291294097900391d, -5.262093550784066E-8d, }, // 897 + {+0.6296497583389282d, +2.559185648444699E-8d, }, // 898 + {+0.6301699876785278d, -4.768920119497491E-8d, }, // 899 + {+0.6306898593902588d, -3.376406008397877E-8d, }, // 900 + {+0.6312094926834106d, -5.156097914033476E-8d, }, // 901 + {+0.6317287683486938d, +1.840992392368355E-8d, }, // 902 + {+0.632247805595398d, +5.721951534729663E-8d, }, // 903 + {+0.6327667236328125d, -5.406177467045421E-8d, }, // 904 + {+0.6332851648330688d, +4.247320713683124E-8d, }, // 905 + {+0.6338034868240356d, -1.0524557502830645E-8d, }, // 906 + {+0.6343214511871338d, +2.5641927558519502E-8d, }, // 907 + {+0.6348391771316528d, +3.204135737993823E-8d, }, // 908 + {+0.6353566646575928d, +8.951285029786536E-9d, }, // 909 + {+0.6358739137649536d, -4.335116707228395E-8d, }, // 910 + {+0.6363908052444458d, -5.380016714089483E-9d, }, // 911 + {+0.6369074583053589d, +3.931710344901743E-9d, }, // 912 + {+0.6374238729476929d, -1.5140150088220166E-8d, }, // 913 + {+0.6379399299621582d, +5.688910024377372E-8d, }, // 914 + {+0.638455867767334d, -1.8124135273572568E-8d, }, // 915 + {+0.6389714479446411d, -1.486720391901626E-9d, }, // 916 + {+0.6394867897033691d, -1.2133811978747018E-8d, }, // 917 + {+0.6400018930435181d, -4.9791700939901716E-8d, }, // 918 + {+0.6405166387557983d, +5.022188652837274E-9d, }, // 919 + {+0.6410311460494995d, +3.337143177933685E-8d, }, // 920 + {+0.6415454149246216d, +3.55284719912458E-8d, }, // 921 + {+0.6420594453811646d, +1.1765332726757802E-8d, }, // 922 + {+0.6425732374191284d, -3.7646381826067834E-8d, }, // 923 + {+0.6430866718292236d, +6.773803682579552E-9d, }, // 924 + {+0.6435998678207397d, +2.608736797081283E-8d, }, // 925 + {+0.6441128253936768d, +2.056466263408266E-8d, }, // 926 + {+0.6446255445480347d, -9.524376551107945E-9d, }, // 927 + {+0.6451379060745239d, +5.5299060775883977E-8d, }, // 928 + {+0.6456501483917236d, -2.3114497793159813E-8d, }, // 929 + {+0.6461620330810547d, -6.077779731902102E-9d, }, // 930 + {+0.6466736793518066d, -1.2531793589140273E-8d, }, // 931 + {+0.6471850872039795d, -4.220866994206517E-8d, }, // 932 + {+0.6476961374282837d, +2.4368339445199057E-8d, }, // 933 + {+0.6482070684432983d, -5.095229574221907E-8d, }, // 934 + {+0.6487176418304443d, -2.9485356677301627E-8d, }, // 935 + {+0.6492279767990112d, -3.0173901411577916E-8d, }, // 936 + {+0.649738073348999d, -5.275210583909726E-8d, }, // 937 + {+0.6502478122711182d, +2.2254737134350224E-8d, }, // 938 + {+0.6507574319839478d, -4.330693978322885E-8d, }, // 939 + {+0.6512666940689087d, -1.0753950588009912E-8d, }, // 940 + {+0.6517757177352905d, +9.686179886293545E-10d, }, // 941 + {+0.6522845029830933d, -7.875434494414498E-9d, }, // 942 + {+0.6527930498123169d, -3.702271091849158E-8d, }, // 943 + {+0.6533012390136719d, +3.2999073763758614E-8d, }, // 944 + {+0.6538093090057373d, -3.5966064858620067E-8d, }, // 945 + {+0.6543170213699341d, -5.23735298540578E-9d, }, // 946 + {+0.6548244953155518d, +6.237715351293023E-9d, }, // 947 + {+0.6553317308425903d, -1.279462699936282E-9d, }, // 948 + {+0.6558387279510498d, -2.7527887552743672E-8d, }, // 949 + {+0.6563453674316406d, +4.696233317356646E-8d, }, // 950 + {+0.6568518877029419d, -1.5967172745329108E-8d, }, // 951 + {+0.6573580503463745d, +2.2361985518423144E-8d, }, // 952 + {+0.657863974571228d, +4.2999935789083046E-8d, }, // 953 + {+0.6583696603775024d, +4.620570188811826E-8d, }, // 954 + {+0.6588751077651978d, +3.223791487908353E-8d, }, // 955 + {+0.659380316734314d, +1.3548138612715822E-9d, }, // 956 + {+0.6598852872848511d, -4.618575323863973E-8d, }, // 957 + {+0.6603899002075195d, +9.082960673843353E-9d, }, // 958 + {+0.6608942747116089d, +4.820873399634487E-8d, }, // 959 + {+0.6613985300064087d, -4.776104368314602E-8d, }, // 960 + {+0.6619024276733398d, -4.0151502150238136E-8d, }, // 961 + {+0.6624060869216919d, -4.791602708710648E-8d, }, // 962 + {+0.6629093885421753d, +4.8410188461165925E-8d, }, // 963 + {+0.6634125709533691d, +1.0663697110471944E-8d, }, // 964 + {+0.6639155149459839d, -4.1691464781797555E-8d, }, // 965 + {+0.66441810131073d, +1.080835500478704E-8d, }, // 966 + {+0.664920449256897d, +4.920784622407246E-8d, }, // 967 + {+0.6654226779937744d, -4.544868396511241E-8d, }, // 968 + {+0.6659245491027832d, -3.448944157854234E-8d, }, // 969 + {+0.6664261817932129d, -3.6870882345139385E-8d, }, // 970 + {+0.6669275760650635d, -5.234055273962444E-8d, }, // 971 + {+0.6674286127090454d, +3.856291077979099E-8d, }, // 972 + {+0.6679295301437378d, -2.327375671320742E-9d, }, // 973 + {+0.6684302091598511d, -5.555080534042001E-8d, }, // 974 + {+0.6689305305480957d, -1.6471487337453832E-9d, }, // 975 + {+0.6694306135177612d, +4.042486803683015E-8d, }, // 976 + {+0.6699305772781372d, -4.8293856891818295E-8d, }, // 977 + {+0.6704301834106445d, -2.9134931730784303E-8d, }, // 978 + {+0.6709295511245728d, -2.1058207594753368E-8d, }, // 979 + {+0.6714286804199219d, -2.3814619551682855E-8d, }, // 980 + {+0.6719275712966919d, -3.7155475428252136E-8d, }, // 981 + {+0.6724261045455933d, +5.8376834484391746E-8d, }, // 982 + {+0.6729245185852051d, +2.4611679969129262E-8d, }, // 983 + {+0.6734226942062378d, -1.899407107267079E-8d, }, // 984 + {+0.6739205121994019d, +4.7016079464436395E-8d, }, // 985 + {+0.6744182109832764d, -1.5529608026276525E-8d, }, // 986 + {+0.6749155521392822d, +3.203391672602453E-8d, }, // 987 + {+0.6754127740859985d, -4.8465821804075345E-8d, }, // 988 + {+0.6759096384048462d, -1.8364507801369988E-8d, }, // 989 + {+0.6764062643051147d, +3.3739397633046517E-9d, }, // 990 + {+0.6769026517868042d, +1.6994526063192333E-8d, }, // 991 + {+0.6773988008499146d, +2.2741891590028428E-8d, }, // 992 + {+0.6778947114944458d, +2.0860312877435047E-8d, }, // 993 + {+0.678390383720398d, +1.1593703222523284E-8d, }, // 994 + {+0.678885817527771d, -4.814386594291911E-9d, }, // 995 + {+0.6793810129165649d, -2.812076759125914E-8d, }, // 996 + {+0.6798759698867798d, -5.808261186903479E-8d, }, // 997 + {+0.680370569229126d, +2.4751837654582522E-8d, }, // 998 + {+0.6808650493621826d, -1.7793890245755405E-8d, }, // 999 + {+0.6813591718673706d, +5.294053246347931E-8d, }, // 1000 + {+0.681853175163269d, -1.2220826223585654E-9d, }, // 1001 + {+0.6823468208312988d, +5.8377876767612725E-8d, }, // 1002 + {+0.6828403472900391d, -6.437492120743254E-9d, }, // 1003 + {+0.6833335161209106d, +4.2990710043633113E-8d, }, // 1004 + {+0.6838265657424927d, -3.1516131027023284E-8d, }, // 1005 + {+0.684319257736206d, +8.70017386744679E-9d, }, // 1006 + {+0.6848117113113403d, +4.466959125843237E-8d, }, // 1007 + {+0.6853040456771851d, -4.25782656420497E-8d, }, // 1008 + {+0.6857960224151611d, -1.4386267593671393E-8d, }, // 1009 + {+0.6862877607345581d, +1.0274494061148778E-8d, }, // 1010 + {+0.686779260635376d, +3.164186629229597E-8d, }, // 1011 + {+0.6872705221176147d, +4.995334552140326E-8d, }, // 1012 + {+0.687761664390564d, -5.3763211240398744E-8d, }, // 1013 + {+0.6882524490356445d, -4.0852427502515625E-8d, }, // 1014 + {+0.688742995262146d, -3.0287143914420064E-8d, }, // 1015 + {+0.6892333030700684d, -2.183125937905008E-8d, }, // 1016 + {+0.6897233724594116d, -1.524901992178814E-8d, }, // 1017 + {+0.6902132034301758d, -1.0305018010328949E-8d, }, // 1018 + {+0.6907027959823608d, -6.764191876212205E-9d, }, // 1019 + {+0.6911921501159668d, -4.391824838015402E-9d, }, // 1020 + {+0.6916812658309937d, -2.9535446262017846E-9d, }, // 1021 + {+0.6921701431274414d, -2.2153227096187463E-9d, }, // 1022 + {+0.6926587820053101d, -1.943473623641502E-9d, }, // 1023 + }; + + + /** + * Class contains only static methods. + */ + private FastMathLiteralArrays() {} + + /** + * Load "EXP_INT_A". + * + * @return a clone of the data array. + */ + static double[] loadExpIntA() { + return EXP_INT_A.clone(); + } + /** + * Load "EXP_INT_B". + * + * @return a clone of the data array. + */ + static double[] loadExpIntB() { + return EXP_INT_B.clone(); + } + /** + * Load "EXP_FRAC_A". + * + * @return a clone of the data array. + */ + static double[] loadExpFracA() { + return EXP_FRAC_A.clone(); + } + /** + * Load "EXP_FRAC_B". + * + * @return a clone of the data array. + */ + static double[] loadExpFracB() { + return EXP_FRAC_B.clone(); + } + /** + * Load "LN_MANT". + * + * @return a clone of the data array. + */ + static double[][] loadLnMant() { + return LN_MANT.clone(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Incrementor.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Incrementor.java new file mode 100644 index 000000000..2e96c7e7e --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Incrementor.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 com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; + +/** + * Utility that increments a counter until a maximum is reached, at + * which point, the instance will by default throw a + * {@link MaxCountExceededException}. + * However, the user is able to override this behaviour by defining a + * custom {@link MaxCountExceededCallback callback}, in order to e.g. + * select which exception must be thrown. + * + * @since 3.0 + * @deprecated Use {@link IntegerSequence.Incrementor} instead. + */ +@Deprecated +public class Incrementor { + /** + * Upper limit for the counter. + */ + private int maximalCount; + /** + * Current count. + */ + private int count = 0; + /** + * Function called at counter exhaustion. + */ + private final MaxCountExceededCallback maxCountCallback; + + /** + * Default constructor. + * For the new instance to be useful, the maximal count must be set + * by calling {@link #setMaximalCount(int) setMaximalCount}. + */ + public Incrementor() { + this(0); + } + + /** + * Defines a maximal count. + * + * @param max Maximal count. + */ + public Incrementor(int max) { + this(max, + new MaxCountExceededCallback() { + /** {@inheritDoc} */ + public void trigger(int max) throws MaxCountExceededException { + throw new MaxCountExceededException(max); + } + }); + } + + /** + * Defines a maximal count and a callback method to be triggered at + * counter exhaustion. + * + * @param max Maximal count. + * @param cb Function to be called when the maximal count has been reached. + * @throws NullArgumentException if {@code cb} is {@code null} + */ + public Incrementor(int max, MaxCountExceededCallback cb) + throws NullArgumentException { + if (cb == null){ + throw new NullArgumentException(); + } + maximalCount = max; + maxCountCallback = cb; + } + + /** + * Sets the upper limit for the counter. + * This does not automatically reset the current count to zero (see + * {@link #resetCount()}). + * + * @param max Upper limit of the counter. + */ + public void setMaximalCount(int max) { + maximalCount = max; + } + + /** + * Gets the upper limit of the counter. + * + * @return the counter upper limit. + */ + public int getMaximalCount() { + return maximalCount; + } + + /** + * Gets the current count. + * + * @return the current count. + */ + public int getCount() { + return count; + } + + /** + * Checks whether a single increment is allowed. + * + * @return {@code false} if the next call to {@link #incrementCount(int) + * incrementCount} will trigger a {@code MaxCountExceededException}, + * {@code true} otherwise. + */ + public boolean canIncrement() { + return count < maximalCount; + } + + /** + * Performs multiple increments. + * See the other {@link #incrementCount() incrementCount} method). + * + * @param value Number of increments. + * @throws MaxCountExceededException at counter exhaustion. + */ + public void incrementCount(int value) throws MaxCountExceededException { + for (int i = 0; i < value; i++) { + incrementCount(); + } + } + + /** + * Adds one to the current iteration count. + * At counter exhaustion, this method will call the + * {@link MaxCountExceededCallback#trigger(int) trigger} method of the + * callback object passed to the + * {@link #Incrementor(int,MaxCountExceededCallback) constructor}. + * If not explictly set, a default callback is used that will throw + * a {@code MaxCountExceededException}. + * + * @throws MaxCountExceededException at counter exhaustion, unless a + * custom {@link MaxCountExceededCallback callback} has been set at + * construction. + */ + public void incrementCount() throws MaxCountExceededException { + if (++count > maximalCount) { + maxCountCallback.trigger(maximalCount); + } + } + + /** + * Resets the counter to 0. + */ + public void resetCount() { + count = 0; + } + + /** + * Defines a method to be called at counter exhaustion. + * The {@link #trigger(int) trigger} method should usually throw an exception. + */ + public interface MaxCountExceededCallback { + /** + * Function called when the maximal count has been reached. + * + * @param maximalCount Maximal count. + * @throws MaxCountExceededException at counter exhaustion + */ + void trigger(int maximalCount) throws MaxCountExceededException; + } + + /** Create an instance that delegates everything to a {@link IntegerSequence.Incrementor}. + *

            + * This factory method is intended only as a temporary hack for internal use in + * Apache Commons Math 3.X series, when {@code Incrementor} is required in + * interface (as a return value or in protected fields). It should not + * be used in other cases. The {@link IntegerSequence.Incrementor} class should + * be used instead of {@code Incrementor}. + *

            + *

            + * All methods are mirrored to the underlying {@link IntegerSequence.Incrementor}, + * as long as neither {@link #setMaximalCount(int)} nor {@link #resetCount()} are called. + * If one of these two methods is called, the created instance becomes independent + * of the {@link IntegerSequence.Incrementor} used at creation. The rationale is that + * {@link IntegerSequence.Incrementor} cannot change their maximal count and cannot be reset. + *

            + * @param incrementor wrapped {@link IntegerSequence.Incrementor} + * @return an incrementor wrapping an {@link IntegerSequence.Incrementor} + * @since 3.6 + */ + public static Incrementor wrap(final IntegerSequence.Incrementor incrementor) { + return new Incrementor() { + + /** Underlying incrementor. */ + private IntegerSequence.Incrementor delegate; + + { + // set up matching values at initialization + delegate = incrementor; + super.setMaximalCount(delegate.getMaximalCount()); + super.incrementCount(delegate.getCount()); + } + + /** {@inheritDoc} */ + @Override + public void setMaximalCount(int max) { + super.setMaximalCount(max); + delegate = delegate.withMaximalCount(max); + } + + /** {@inheritDoc} */ + @Override + public void resetCount() { + super.resetCount(); + delegate = delegate.withStart(0); + } + + /** {@inheritDoc} */ + @Override + public void incrementCount() { + super.incrementCount(); + delegate.increment(); + } + + }; + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IntegerSequence.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IntegerSequence.java new file mode 100644 index 000000000..238af7033 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IntegerSequence.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 com.fr.third.org.apache.commons.math3.util; + +import java.util.Iterator; + +import com.fr.third.org.apache.commons.math3.exception.MathUnsupportedOperationException; +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.ZeroException; + +/** + * Provides a sequence of integers. + * + * @since 3.6 + */ +public class IntegerSequence { + /** + * Utility class contains only static methods. + */ + private IntegerSequence() {} + + /** + * Creates a sequence {@code [start .. end]}. + * It calls {@link #range(int,int,int) range(start, end, 1)}. + * + * @param start First value of the range. + * @param end Last value of the range. + * @return a range. + */ + public static Range range(int start, + int end) { + return range(start, end, 1); + } + + /** + * Creates a sequence \( a_i, i < 0 <= n \) + * where \( a_i = start + i * step \) + * and \( n \) is such that \( a_n <= max \) and \( a_{n+1} > max \). + * + * @param start First value of the range. + * @param max Last value of the range that satisfies the above + * construction rule. + * @param step Increment. + * @return a range. + */ + public static Range range(final int start, + final int max, + final int step) { + return new Range(start, max, step); + } + + /** + * Generates a sequence of integers. + */ + public static class Range implements Iterable { + /** Number of integers contained in this range. */ + private final int size; + /** First value. */ + private final int start; + /** Final value. */ + private final int max; + /** Increment. */ + private final int step; + + /** + * Creates a sequence \( a_i, i < 0 <= n \) + * where \( a_i = start + i * step \) + * and \( n \) is such that \( a_n <= max \) and \( a_{n+1} > max \). + * + * @param start First value of the range. + * @param max Last value of the range that satisfies the above + * construction rule. + * @param step Increment. + */ + public Range(int start, + int max, + int step) { + this.start = start; + this.max = max; + this.step = step; + + final int s = (max - start) / step + 1; + this.size = s < 0 ? 0 : s; + } + + /** + * Gets the number of elements contained in the range. + * + * @return the size of the range. + */ + public int size() { + return size; + } + + /** {@inheritDoc} */ + public Iterator iterator() { + return Incrementor.create() + .withStart(start) + .withMaximalCount(max + (step > 0 ? 1 : -1)) + .withIncrement(step); + } + } + + /** + * Utility that increments a counter until a maximum is reached, at + * which point, the instance will by default throw a + * {@link MaxCountExceededException}. + * However, the user is able to override this behaviour by defining a + * custom {@link MaxCountExceededCallback callback}, in order to e.g. + * select which exception must be thrown. + */ + public static class Incrementor implements Iterator { + /** Default callback. */ + private static final MaxCountExceededCallback CALLBACK + = new MaxCountExceededCallback() { + /** {@inheritDoc} */ + public void trigger(int max) throws MaxCountExceededException { + throw new MaxCountExceededException(max); + } + }; + + /** Initial value the counter. */ + private final int init; + /** Upper limit for the counter. */ + private final int maximalCount; + /** Increment. */ + private final int increment; + /** Function called at counter exhaustion. */ + private final MaxCountExceededCallback maxCountCallback; + /** Current count. */ + private int count = 0; + + /** + * Defines a method to be called at counter exhaustion. + * The {@link #trigger(int) trigger} method should usually throw an exception. + */ + public interface MaxCountExceededCallback { + /** + * Function called when the maximal count has been reached. + * + * @param maximalCount Maximal count. + * @throws MaxCountExceededException at counter exhaustion + */ + void trigger(int maximalCount) throws MaxCountExceededException; + } + + /** + * Creates an incrementor. + * The counter will be exhausted either when {@code max} is reached + * or when {@code nTimes} increments have been performed. + * + * @param start Initial value. + * @param max Maximal count. + * @param step Increment. + * @param cb Function to be called when the maximal count has been reached. + * @throws NullArgumentException if {@code cb} is {@code null}. + */ + private Incrementor(int start, + int max, + int step, + MaxCountExceededCallback cb) + throws NullArgumentException { + if (cb == null) { + throw new NullArgumentException(); + } + this.init = start; + this.maximalCount = max; + this.increment = step; + this.maxCountCallback = cb; + this.count = start; + } + + /** + * Factory method that creates a default instance. + * The initial and maximal values are set to 0. + * For the new instance to be useful, the maximal count must be set + * by calling {@link #withMaximalCount(int) withMaximalCount}. + * + * @return an new instance. + */ + public static Incrementor create() { + return new Incrementor(0, 0, 1, CALLBACK); + } + + /** + * Creates a new instance with a given initial value. + * The counter is reset to the initial value. + * + * @param start Initial value of the counter. + * @return a new instance. + */ + public Incrementor withStart(int start) { + return new Incrementor(start, + this.maximalCount, + this.increment, + this.maxCountCallback); + } + + /** + * Creates a new instance with a given maximal count. + * The counter is reset to the initial value. + * + * @param max Maximal count. + * @return a new instance. + */ + public Incrementor withMaximalCount(int max) { + return new Incrementor(this.init, + max, + this.increment, + this.maxCountCallback); + } + + /** + * Creates a new instance with a given increment. + * The counter is reset to the initial value. + * + * @param step Increment. + * @return a new instance. + */ + public Incrementor withIncrement(int step) { + if (step == 0) { + throw new ZeroException(); + } + return new Incrementor(this.init, + this.maximalCount, + step, + this.maxCountCallback); + } + + /** + * Creates a new instance with a given callback. + * The counter is reset to the initial value. + * + * @param cb Callback to be called at counter exhaustion. + * @return a new instance. + */ + public Incrementor withCallback(MaxCountExceededCallback cb) { + return new Incrementor(this.init, + this.maximalCount, + this.increment, + cb); + } + + /** + * Gets the upper limit of the counter. + * + * @return the counter upper limit. + */ + public int getMaximalCount() { + return maximalCount; + } + + /** + * Gets the current count. + * + * @return the current count. + */ + public int getCount() { + return count; + } + + /** + * Checks whether incrementing the counter {@code nTimes} is allowed. + * + * @return {@code false} if calling {@link #increment()} + * will trigger a {@code MaxCountExceededException}, + * {@code true} otherwise. + */ + public boolean canIncrement() { + return canIncrement(1); + } + + /** + * Checks whether incrementing the counter several times is allowed. + * + * @param nTimes Number of increments. + * @return {@code false} if calling {@link #increment(int) + * increment(nTimes)} would call the {@link MaxCountExceededCallback callback} + * {@code true} otherwise. + */ + public boolean canIncrement(int nTimes) { + final int finalCount = count + nTimes * increment; + return increment < 0 ? + finalCount > maximalCount : + finalCount < maximalCount; + } + + /** + * Performs multiple increments. + * + * @param nTimes Number of increments. + * @throws MaxCountExceededException at counter exhaustion. + * @throws NotStrictlyPositiveException if {@code nTimes <= 0}. + * + * @see #increment() + */ + public void increment(int nTimes) throws MaxCountExceededException { + if (nTimes <= 0) { + throw new NotStrictlyPositiveException(nTimes); + } + + if (!canIncrement(0)) { + maxCountCallback.trigger(maximalCount); + } + count += nTimes * increment; + } + + /** + * Adds the increment value to the current iteration count. + * At counter exhaustion, this method will call the + * {@link MaxCountExceededCallback#trigger(int) trigger} method of the + * callback object passed to the + * {@link #withCallback(MaxCountExceededCallback)} method. + * If not explicitly set, a default callback is used that will throw + * a {@code MaxCountExceededException}. + * + * @throws MaxCountExceededException at counter exhaustion, unless a + * custom {@link MaxCountExceededCallback callback} has been set. + * + * @see #increment(int) + */ + public void increment() throws MaxCountExceededException { + increment(1); + } + + /** {@inheritDoc} */ + public boolean hasNext() { + return canIncrement(0); + } + + /** {@inheritDoc} */ + public Integer next() { + final int value = count; + increment(); + return value; + } + + /** + * Not applicable. + * + * @throws MathUnsupportedOperationException + */ + public void remove() { + throw new MathUnsupportedOperationException(); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationEvent.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationEvent.java new file mode 100644 index 000000000..630073a6b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationEvent.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 com.fr.third.org.apache.commons.math3.util; + +import java.util.EventObject; + +/** + * The root class from which all events occurring while running an + * {@link IterationManager} should be derived. + * + */ +public class IterationEvent extends EventObject { + /** */ + private static final long serialVersionUID = 20120128L; + + /** The number of iterations performed so far. */ + private final int iterations; + + /** + * Creates a new instance of this class. + * + * @param source the iterative algorithm on which the event initially + * occurred + * @param iterations the number of iterations performed at the time + * {@code this} event is created + */ + public IterationEvent(final Object source, final int iterations) { + super(source); + this.iterations = iterations; + } + + /** + * Returns the number of iterations performed at the time {@code this} event + * is created. + * + * @return the number of iterations performed + */ + public int getIterations() { + return iterations; + } + } diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationListener.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationListener.java new file mode 100644 index 000000000..1311d5b6b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationListener.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 com.fr.third.org.apache.commons.math3.util; + +import java.util.EventListener; + +/** + * The listener interface for receiving events occurring in an iterative + * algorithm. + * + */ +public interface IterationListener extends EventListener { + /** + * Invoked after completion of the initial phase of the iterative algorithm + * (prior to the main iteration loop). + * + * @param e The {@link IterationEvent} object. + */ + void initializationPerformed(IterationEvent e); + + /** + * Invoked each time an iteration is completed (in the main iteration loop). + * + * @param e The {@link IterationEvent} object. + */ + void iterationPerformed(IterationEvent e); + + /** + * Invoked each time a new iteration is completed (in the main iteration + * loop). + * + * @param e The {@link IterationEvent} object. + */ + void iterationStarted(IterationEvent e); + + /** + * Invoked after completion of the operations which occur after breaking out + * of the main iteration loop. + * + * @param e The {@link IterationEvent} object. + */ + void terminationPerformed(IterationEvent e); +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationManager.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationManager.java new file mode 100644 index 000000000..c0f6ca533 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/IterationManager.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.fr.third.org.apache.commons.math3.exception.MaxCountExceededException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; + +/** + * This abstract class provides a general framework for managing iterative + * algorithms. The maximum number of iterations can be set, and methods are + * provided to monitor the current iteration count. A lightweight event + * framework is also provided. + * + */ +public class IterationManager { + + /** Keeps a count of the number of iterations. */ + private IntegerSequence.Incrementor iterations; + + /** The collection of all listeners attached to this iterative algorithm. */ + private final Collection listeners; + + /** + * Creates a new instance of this class. + * + * @param maxIterations the maximum number of iterations + */ + public IterationManager(final int maxIterations) { + this.iterations = IntegerSequence.Incrementor.create().withMaximalCount(maxIterations); + this.listeners = new CopyOnWriteArrayList(); + } + + /** + * Creates a new instance of this class. + * + * @param maxIterations the maximum number of iterations + * @param callBack the function to be called when the maximum number of + * iterations has been reached + * @throws NullArgumentException if {@code callBack} is {@code null} + * @since 3.1 + * @deprecated as of 3.6, replaced with {@link #IterationManager(int, + * IntegerSequence.Incrementor.MaxCountExceededCallback)} + */ + @Deprecated + public IterationManager(final int maxIterations, + final Incrementor.MaxCountExceededCallback callBack) { + this(maxIterations, new IntegerSequence.Incrementor.MaxCountExceededCallback() { + /** {@inheritDoc} */ + public void trigger(final int maximalCount) throws MaxCountExceededException { + callBack.trigger(maximalCount); + } + }); + } + + /** + * Creates a new instance of this class. + * + * @param maxIterations the maximum number of iterations + * @param callBack the function to be called when the maximum number of + * iterations has been reached + * @throws NullArgumentException if {@code callBack} is {@code null} + * @since 3.6 + */ + public IterationManager(final int maxIterations, + final IntegerSequence.Incrementor.MaxCountExceededCallback callBack) { + this.iterations = IntegerSequence.Incrementor.create().withMaximalCount(maxIterations).withCallback(callBack); + this.listeners = new CopyOnWriteArrayList(); + } + + /** + * Attaches a listener to this manager. + * + * @param listener A {@code IterationListener} object. + */ + public void addIterationListener(final IterationListener listener) { + listeners.add(listener); + } + + /** + * Informs all registered listeners that the initial phase (prior to the + * main iteration loop) has been completed. + * + * @param e The {@link IterationEvent} object. + */ + public void fireInitializationEvent(final IterationEvent e) { + for (IterationListener l : listeners) { + l.initializationPerformed(e); + } + } + + /** + * Informs all registered listeners that a new iteration (in the main + * iteration loop) has been performed. + * + * @param e The {@link IterationEvent} object. + */ + public void fireIterationPerformedEvent(final IterationEvent e) { + for (IterationListener l : listeners) { + l.iterationPerformed(e); + } + } + + /** + * Informs all registered listeners that a new iteration (in the main + * iteration loop) has been started. + * + * @param e The {@link IterationEvent} object. + */ + public void fireIterationStartedEvent(final IterationEvent e) { + for (IterationListener l : listeners) { + l.iterationStarted(e); + } + } + + /** + * Informs all registered listeners that the final phase (post-iterations) + * has been completed. + * + * @param e The {@link IterationEvent} object. + */ + public void fireTerminationEvent(final IterationEvent e) { + for (IterationListener l : listeners) { + l.terminationPerformed(e); + } + } + + /** + * Returns the number of iterations of this solver, 0 if no iterations has + * been performed yet. + * + * @return the number of iterations. + */ + public int getIterations() { + return iterations.getCount(); + } + + /** + * Returns the maximum number of iterations. + * + * @return the maximum number of iterations. + */ + public int getMaxIterations() { + return iterations.getMaximalCount(); + } + + /** + * Increments the iteration count by one, and throws an exception if the + * maximum number of iterations is reached. This method should be called at + * the beginning of a new iteration. + * + * @throws MaxCountExceededException if the maximum number of iterations is + * reached. + */ + public void incrementIterationCount() + throws MaxCountExceededException { + iterations.increment(); + } + + /** + * Removes the specified iteration listener from the list of listeners + * currently attached to {@code this} object. Attempting to remove a + * listener which was not previously registered does not cause any + * error. + * + * @param listener The {@link IterationListener} to be removed. + */ + public void removeIterationListener(final IterationListener listener) { + listeners.remove(listener); + } + + /** + * Sets the iteration count to 0. This method must be called during the + * initial phase. + */ + public void resetIterationCount() { + iterations = iterations.withStart(0); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/KthSelector.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/KthSelector.java new file mode 100644 index 000000000..9d7cbee0a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/KthSelector.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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; + + +/** + * A Simple Kth selector implementation to pick up the + * Kth ordered element from a work array containing the input + * numbers. + * @since 3.4 + */ +public class KthSelector implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140713L; + + /** Minimum selection size for insertion sort rather than selection. */ + private static final int MIN_SELECT_SIZE = 15; + + /** A {@link PivotingStrategyInterface} used for pivoting */ + private final PivotingStrategyInterface pivotingStrategy; + + /** + * Constructor with default {@link MedianOf3PivotingStrategy median of 3} pivoting strategy + */ + public KthSelector() { + this.pivotingStrategy = new MedianOf3PivotingStrategy(); + } + + /** + * Constructor with specified pivoting strategy + * + * @param pivotingStrategy pivoting strategy to use + * @throws NullArgumentException when pivotingStrategy is null + * @see MedianOf3PivotingStrategy + * @see RandomPivotingStrategy + * @see CentralPivotingStrategy + */ + public KthSelector(final PivotingStrategyInterface pivotingStrategy) + throws NullArgumentException { + MathUtils.checkNotNull(pivotingStrategy); + this.pivotingStrategy = pivotingStrategy; + } + + /** Get the pivotin strategy. + * @return pivoting strategy + */ + public PivotingStrategyInterface getPivotingStrategy() { + return pivotingStrategy; + } + + /** + * Select Kth value in the array. + * + * @param work work array to use to find out the Kth value + * @param pivotsHeap cached pivots heap that can be used for efficient estimation + * @param k the index whose value in the array is of interest + * @return Kth value + */ + public double select(final double[] work, final int[] pivotsHeap, final int k) { + int begin = 0; + int end = work.length; + int node = 0; + final boolean usePivotsHeap = pivotsHeap != null; + while (end - begin > MIN_SELECT_SIZE) { + final int pivot; + + if (usePivotsHeap && node < pivotsHeap.length && + pivotsHeap[node] >= 0) { + // the pivot has already been found in a previous call + // and the array has already been partitioned around it + pivot = pivotsHeap[node]; + } else { + // select a pivot and partition work array around it + pivot = partition(work, begin, end, pivotingStrategy.pivotIndex(work, begin, end)); + if (usePivotsHeap && node < pivotsHeap.length) { + pivotsHeap[node] = pivot; + } + } + + if (k == pivot) { + // the pivot was exactly the element we wanted + return work[k]; + } else if (k < pivot) { + // the element is in the left partition + end = pivot; + node = FastMath.min(2 * node + 1, usePivotsHeap ? pivotsHeap.length : end); + } else { + // the element is in the right partition + begin = pivot + 1; + node = FastMath.min(2 * node + 2, usePivotsHeap ? pivotsHeap.length : end); + } + } + Arrays.sort(work, begin, end); + return work[k]; + } + + /** + * Partition an array slice around a pivot.Partitioning exchanges array + * elements such that all elements smaller than pivot are before it and + * all elements larger than pivot are after it. + * + * @param work work array + * @param begin index of the first element of the slice of work array + * @param end index after the last element of the slice of work array + * @param pivot initial index of the pivot + * @return index of the pivot after partition + */ + private int partition(final double[] work, final int begin, final int end, final int pivot) { + + final double value = work[pivot]; + work[pivot] = work[begin]; + + int i = begin + 1; + int j = end - 1; + while (i < j) { + while (i < j && work[j] > value) { + --j; + } + while (i < j && work[i] < value) { + ++i; + } + + if (i < j) { + final double tmp = work[i]; + work[i++] = work[j]; + work[j--] = tmp; + } + } + + if (i >= end || work[i] > value) { + --i; + } + work[begin] = work[i]; + work[i] = value; + return i; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathArrays.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathArrays.java new file mode 100644 index 000000000..aa00a8da3 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathArrays.java @@ -0,0 +1,1935 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.util; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +import com.fr.third.org.apache.commons.math3.distribution.UniformIntegerDistribution; +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NoDataException; +import com.fr.third.org.apache.commons.math3.exception.NonMonotonicSequenceException; +import com.fr.third.org.apache.commons.math3.exception.NotANumberException; +import com.fr.third.org.apache.commons.math3.exception.NotPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooLargeException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; +import com.fr.third.org.apache.commons.math3.random.Well19937c; +import com.fr.third.org.apache.commons.math3.Field; + +/** + * Arrays utilities. + * + * @since 3.0 + */ +public class MathArrays { + + /** + * Private constructor. + */ + private MathArrays() {} + + /** + * Real-valued function that operate on an array or a part of it. + * @since 3.1 + */ + public interface Function { + /** + * Operates on an entire array. + * + * @param array Array to operate on. + * @return the result of the operation. + */ + double evaluate(double[] array); + /** + * @param array Array to operate on. + * @param startIndex Index of the first element to take into account. + * @param numElements Number of elements to take into account. + * @return the result of the operation. + */ + double evaluate(double[] array, + int startIndex, + int numElements); + } + + /** + * Create a copy of an array scaled by a value. + * + * @param arr Array to scale. + * @param val Scalar. + * @return scaled copy of array with each entry multiplied by val. + * @since 3.2 + */ + public static double[] scale(double val, final double[] arr) { + double[] newArr = new double[arr.length]; + for (int i = 0; i < arr.length; i++) { + newArr[i] = arr[i] * val; + } + return newArr; + } + + /** + *

            Multiply each element of an array by a value.

            + * + *

            The array is modified in place (no copy is created).

            + * + * @param arr Array to scale + * @param val Scalar + * @since 3.2 + */ + public static void scaleInPlace(double val, final double[] arr) { + for (int i = 0; i < arr.length; i++) { + arr[i] *= val; + } + } + + /** + * Creates an array whose contents will be the element-by-element + * addition of the arguments. + * + * @param a First term of the addition. + * @param b Second term of the addition. + * @return a new array {@code r} where {@code r[i] = a[i] + b[i]}. + * @throws DimensionMismatchException if the array lengths differ. + * @since 3.1 + */ + public static double[] ebeAdd(double[] a, double[] b) + throws DimensionMismatchException { + checkEqualLength(a, b); + + final double[] result = a.clone(); + for (int i = 0; i < a.length; i++) { + result[i] += b[i]; + } + return result; + } + /** + * Creates an array whose contents will be the element-by-element + * subtraction of the second argument from the first. + * + * @param a First term. + * @param b Element to be subtracted. + * @return a new array {@code r} where {@code r[i] = a[i] - b[i]}. + * @throws DimensionMismatchException if the array lengths differ. + * @since 3.1 + */ + public static double[] ebeSubtract(double[] a, double[] b) + throws DimensionMismatchException { + checkEqualLength(a, b); + + final double[] result = a.clone(); + for (int i = 0; i < a.length; i++) { + result[i] -= b[i]; + } + return result; + } + /** + * Creates an array whose contents will be the element-by-element + * multiplication of the arguments. + * + * @param a First factor of the multiplication. + * @param b Second factor of the multiplication. + * @return a new array {@code r} where {@code r[i] = a[i] * b[i]}. + * @throws DimensionMismatchException if the array lengths differ. + * @since 3.1 + */ + public static double[] ebeMultiply(double[] a, double[] b) + throws DimensionMismatchException { + checkEqualLength(a, b); + + final double[] result = a.clone(); + for (int i = 0; i < a.length; i++) { + result[i] *= b[i]; + } + return result; + } + /** + * Creates an array whose contents will be the element-by-element + * division of the first argument by the second. + * + * @param a Numerator of the division. + * @param b Denominator of the division. + * @return a new array {@code r} where {@code r[i] = a[i] / b[i]}. + * @throws DimensionMismatchException if the array lengths differ. + * @since 3.1 + */ + public static double[] ebeDivide(double[] a, double[] b) + throws DimensionMismatchException { + checkEqualLength(a, b); + + final double[] result = a.clone(); + for (int i = 0; i < a.length; i++) { + result[i] /= b[i]; + } + return result; + } + + /** + * Calculates the L1 (sum of abs) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L1 distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static double distance1(double[] p1, double[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + double sum = 0; + for (int i = 0; i < p1.length; i++) { + sum += FastMath.abs(p1[i] - p2[i]); + } + return sum; + } + + /** + * Calculates the L1 (sum of abs) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L1 distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static int distance1(int[] p1, int[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + int sum = 0; + for (int i = 0; i < p1.length; i++) { + sum += FastMath.abs(p1[i] - p2[i]); + } + return sum; + } + + /** + * Calculates the L2 (Euclidean) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L2 distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static double distance(double[] p1, double[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + double sum = 0; + for (int i = 0; i < p1.length; i++) { + final double dp = p1[i] - p2[i]; + sum += dp * dp; + } + return FastMath.sqrt(sum); + } + + /** + * Calculates the cosine of the angle between two vectors. + * + * @param v1 Cartesian coordinates of the first vector. + * @param v2 Cartesian coordinates of the second vector. + * @return the cosine of the angle between the vectors. + * @since 3.6 + */ + public static double cosAngle(double[] v1, double[] v2) { + return linearCombination(v1, v2) / (safeNorm(v1) * safeNorm(v2)); + } + + /** + * Calculates the L2 (Euclidean) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L2 distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static double distance(int[] p1, int[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + double sum = 0; + for (int i = 0; i < p1.length; i++) { + final double dp = p1[i] - p2[i]; + sum += dp * dp; + } + return FastMath.sqrt(sum); + } + + /** + * Calculates the L (max of abs) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static double distanceInf(double[] p1, double[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + double max = 0; + for (int i = 0; i < p1.length; i++) { + max = FastMath.max(max, FastMath.abs(p1[i] - p2[i])); + } + return max; + } + + /** + * Calculates the L (max of abs) distance between two points. + * + * @param p1 the first point + * @param p2 the second point + * @return the L distance between the two points + * @throws DimensionMismatchException if the array lengths differ. + */ + public static int distanceInf(int[] p1, int[] p2) + throws DimensionMismatchException { + checkEqualLength(p1, p2); + int max = 0; + for (int i = 0; i < p1.length; i++) { + max = FastMath.max(max, FastMath.abs(p1[i] - p2[i])); + } + return max; + } + + /** + * Specification of ordering direction. + */ + public enum OrderDirection { + /** Constant for increasing direction. */ + INCREASING, + /** Constant for decreasing direction. */ + DECREASING + } + + /** + * Check that an array is monotonically increasing or decreasing. + * + * @param the type of the elements in the specified array + * @param val Values. + * @param dir Ordering direction. + * @param strict Whether the order should be strict. + * @return {@code true} if sorted, {@code false} otherwise. + */ + public static > boolean isMonotonic(T[] val, + OrderDirection dir, + boolean strict) { + T previous = val[0]; + final int max = val.length; + for (int i = 1; i < max; i++) { + final int comp; + switch (dir) { + case INCREASING: + comp = previous.compareTo(val[i]); + if (strict) { + if (comp >= 0) { + return false; + } + } else { + if (comp > 0) { + return false; + } + } + break; + case DECREASING: + comp = val[i].compareTo(previous); + if (strict) { + if (comp >= 0) { + return false; + } + } else { + if (comp > 0) { + return false; + } + } + break; + default: + // Should never happen. + throw new MathInternalError(); + } + + previous = val[i]; + } + return true; + } + + /** + * Check that an array is monotonically increasing or decreasing. + * + * @param val Values. + * @param dir Ordering direction. + * @param strict Whether the order should be strict. + * @return {@code true} if sorted, {@code false} otherwise. + */ + public static boolean isMonotonic(double[] val, OrderDirection dir, boolean strict) { + return checkOrder(val, dir, strict, false); + } + + /** + * Check that both arrays have the same length. + * + * @param a Array. + * @param b Array. + * @param abort Whether to throw an exception if the check fails. + * @return {@code true} if the arrays have the same length. + * @throws DimensionMismatchException if the lengths differ and + * {@code abort} is {@code true}. + * @since 3.6 + */ + public static boolean checkEqualLength(double[] a, + double[] b, + boolean abort) { + if (a.length == b.length) { + return true; + } else { + if (abort) { + throw new DimensionMismatchException(a.length, b.length); + } + return false; + } + } + + /** + * Check that both arrays have the same length. + * + * @param a Array. + * @param b Array. + * @throws DimensionMismatchException if the lengths differ. + * @since 3.6 + */ + public static void checkEqualLength(double[] a, + double[] b) { + checkEqualLength(a, b, true); + } + + + /** + * Check that both arrays have the same length. + * + * @param a Array. + * @param b Array. + * @param abort Whether to throw an exception if the check fails. + * @return {@code true} if the arrays have the same length. + * @throws DimensionMismatchException if the lengths differ and + * {@code abort} is {@code true}. + * @since 3.6 + */ + public static boolean checkEqualLength(int[] a, + int[] b, + boolean abort) { + if (a.length == b.length) { + return true; + } else { + if (abort) { + throw new DimensionMismatchException(a.length, b.length); + } + return false; + } + } + + /** + * Check that both arrays have the same length. + * + * @param a Array. + * @param b Array. + * @throws DimensionMismatchException if the lengths differ. + * @since 3.6 + */ + public static void checkEqualLength(int[] a, + int[] b) { + checkEqualLength(a, b, true); + } + + /** + * Check that the given array is sorted. + * + * @param val Values. + * @param dir Ordering direction. + * @param strict Whether the order should be strict. + * @param abort Whether to throw an exception if the check fails. + * @return {@code true} if the array is sorted. + * @throws NonMonotonicSequenceException if the array is not sorted + * and {@code abort} is {@code true}. + */ + public static boolean checkOrder(double[] val, OrderDirection dir, + boolean strict, boolean abort) + throws NonMonotonicSequenceException { + double previous = val[0]; + final int max = val.length; + + int index; + ITEM: + for (index = 1; index < max; index++) { + switch (dir) { + case INCREASING: + if (strict) { + if (val[index] <= previous) { + break ITEM; + } + } else { + if (val[index] < previous) { + break ITEM; + } + } + break; + case DECREASING: + if (strict) { + if (val[index] >= previous) { + break ITEM; + } + } else { + if (val[index] > previous) { + break ITEM; + } + } + break; + default: + // Should never happen. + throw new MathInternalError(); + } + + previous = val[index]; + } + + if (index == max) { + // Loop completed. + return true; + } + + // Loop early exit means wrong ordering. + if (abort) { + throw new NonMonotonicSequenceException(val[index], previous, index, dir, strict); + } else { + return false; + } + } + + /** + * Check that the given array is sorted. + * + * @param val Values. + * @param dir Ordering direction. + * @param strict Whether the order should be strict. + * @throws NonMonotonicSequenceException if the array is not sorted. + * @since 2.2 + */ + public static void checkOrder(double[] val, OrderDirection dir, + boolean strict) throws NonMonotonicSequenceException { + checkOrder(val, dir, strict, true); + } + + /** + * Check that the given array is sorted in strictly increasing order. + * + * @param val Values. + * @throws NonMonotonicSequenceException if the array is not sorted. + * @since 2.2 + */ + public static void checkOrder(double[] val) throws NonMonotonicSequenceException { + checkOrder(val, OrderDirection.INCREASING, true); + } + + /** + * Throws DimensionMismatchException if the input array is not rectangular. + * + * @param in array to be tested + * @throws NullArgumentException if input array is null + * @throws DimensionMismatchException if input array is not rectangular + * @since 3.1 + */ + public static void checkRectangular(final long[][] in) + throws NullArgumentException, DimensionMismatchException { + MathUtils.checkNotNull(in); + for (int i = 1; i < in.length; i++) { + if (in[i].length != in[0].length) { + throw new DimensionMismatchException( + LocalizedFormats.DIFFERENT_ROWS_LENGTHS, + in[i].length, in[0].length); + } + } + } + + /** + * Check that all entries of the input array are strictly positive. + * + * @param in Array to be tested + * @throws NotStrictlyPositiveException if any entries of the array are not + * strictly positive. + * @since 3.1 + */ + public static void checkPositive(final double[] in) + throws NotStrictlyPositiveException { + for (int i = 0; i < in.length; i++) { + if (in[i] <= 0) { + throw new NotStrictlyPositiveException(in[i]); + } + } + } + + /** + * Check that no entry of the input array is {@code NaN}. + * + * @param in Array to be tested. + * @throws NotANumberException if an entry is {@code NaN}. + * @since 3.4 + */ + public static void checkNotNaN(final double[] in) + throws NotANumberException { + for(int i = 0; i < in.length; i++) { + if (Double.isNaN(in[i])) { + throw new NotANumberException(); + } + } + } + + /** + * Check that all entries of the input array are >= 0. + * + * @param in Array to be tested + * @throws NotPositiveException if any array entries are less than 0. + * @since 3.1 + */ + public static void checkNonNegative(final long[] in) + throws NotPositiveException { + for (int i = 0; i < in.length; i++) { + if (in[i] < 0) { + throw new NotPositiveException(in[i]); + } + } + } + + /** + * Check all entries of the input array are >= 0. + * + * @param in Array to be tested + * @throws NotPositiveException if any array entries are less than 0. + * @since 3.1 + */ + public static void checkNonNegative(final long[][] in) + throws NotPositiveException { + for (int i = 0; i < in.length; i ++) { + for (int j = 0; j < in[i].length; j++) { + if (in[i][j] < 0) { + throw new NotPositiveException(in[i][j]); + } + } + } + } + + /** + * Returns the Cartesian norm (2-norm), handling both overflow and underflow. + * Translation of the minpack enorm subroutine. + * + * The redistribution policy for MINPACK is available + * here, for + * convenience, it is reproduced below.

            + * + * + * + * + *
            + * 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. + *
            3. 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.
            4. + *
            5. The end-user documentation included with the redistribution, if any, + * must include the following acknowledgment: + * {@code This product includes software developed by the University of + * Chicago, as Operator of Argonne National Laboratory.} + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear.
            6. + *
            7. 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.
            8. + *
            9. 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.
            10. + *
              + * + * @param v Vector of doubles. + * @return the 2-norm of the vector. + * @since 2.2 + */ + public static double safeNorm(double[] v) { + double rdwarf = 3.834e-20; + double rgiant = 1.304e+19; + double s1 = 0; + double s2 = 0; + double s3 = 0; + double x1max = 0; + double x3max = 0; + double floatn = v.length; + double agiant = rgiant / floatn; + for (int i = 0; i < v.length; i++) { + double xabs = FastMath.abs(v[i]); + if (xabs < rdwarf || xabs > agiant) { + if (xabs > rdwarf) { + if (xabs > x1max) { + double r = x1max / xabs; + s1= 1 + s1 * r * r; + x1max = xabs; + } else { + double r = xabs / x1max; + s1 += r * r; + } + } else { + if (xabs > x3max) { + double r = x3max / xabs; + s3= 1 + s3 * r * r; + x3max = xabs; + } else { + if (xabs != 0) { + double r = xabs / x3max; + s3 += r * r; + } + } + } + } else { + s2 += xabs * xabs; + } + } + double norm; + if (s1 != 0) { + norm = x1max * Math.sqrt(s1 + (s2 / x1max) / x1max); + } else { + if (s2 == 0) { + norm = x3max * Math.sqrt(s3); + } else { + if (s2 >= x3max) { + norm = Math.sqrt(s2 * (1 + (x3max / s2) * (x3max * s3))); + } else { + norm = Math.sqrt(x3max * ((s2 / x3max) + (x3max * s3))); + } + } + } + return norm; + } + + /** + * A helper data structure holding a double and an integer value. + */ + private static class PairDoubleInteger { + /** Key */ + private final double key; + /** Value */ + private final int value; + + /** + * @param key Key. + * @param value Value. + */ + PairDoubleInteger(double key, int value) { + this.key = key; + this.value = value; + } + + /** @return the key. */ + public double getKey() { + return key; + } + + /** @return the value. */ + public int getValue() { + return value; + } + } + + /** + * Sort an array in ascending order in place and perform the same reordering + * of entries on other arrays. For example, if + * {@code x = [3, 1, 2], y = [1, 2, 3]} and {@code z = [0, 5, 7]}, then + * {@code sortInPlace(x, y, z)} will update {@code x} to {@code [1, 2, 3]}, + * {@code y} to {@code [2, 3, 1]} and {@code z} to {@code [5, 7, 0]}. + * + * @param x Array to be sorted and used as a pattern for permutation + * of the other arrays. + * @param yList Set of arrays whose permutations of entries will follow + * those performed on {@code x}. + * @throws DimensionMismatchException if any {@code y} is not the same + * size as {@code x}. + * @throws NullArgumentException if {@code x} or any {@code y} is null. + * @since 3.0 + */ + public static void sortInPlace(double[] x, double[] ... yList) + throws DimensionMismatchException, NullArgumentException { + sortInPlace(x, OrderDirection.INCREASING, yList); + } + + /** + * Sort an array in place and perform the same reordering of entries on + * other arrays. This method works the same as the other + * {@link #sortInPlace(double[], double[][]) sortInPlace} method, but + * allows the order of the sort to be provided in the {@code dir} + * parameter. + * + * @param x Array to be sorted and used as a pattern for permutation + * of the other arrays. + * @param dir Order direction. + * @param yList Set of arrays whose permutations of entries will follow + * those performed on {@code x}. + * @throws DimensionMismatchException if any {@code y} is not the same + * size as {@code x}. + * @throws NullArgumentException if {@code x} or any {@code y} is null + * @since 3.0 + */ + public static void sortInPlace(double[] x, + final OrderDirection dir, + double[] ... yList) + throws NullArgumentException, + DimensionMismatchException { + + // Consistency checks. + if (x == null) { + throw new NullArgumentException(); + } + + final int yListLen = yList.length; + final int len = x.length; + + for (int j = 0; j < yListLen; j++) { + final double[] y = yList[j]; + if (y == null) { + throw new NullArgumentException(); + } + if (y.length != len) { + throw new DimensionMismatchException(y.length, len); + } + } + + // Associate each abscissa "x[i]" with its index "i". + final List list + = new ArrayList(len); + for (int i = 0; i < len; i++) { + list.add(new PairDoubleInteger(x[i], i)); + } + + // Create comparators for increasing and decreasing orders. + final Comparator comp + = dir == MathArrays.OrderDirection.INCREASING ? + new Comparator() { + /** {@inheritDoc} */ + public int compare(PairDoubleInteger o1, + PairDoubleInteger o2) { + return Double.compare(o1.getKey(), o2.getKey()); + } + } : new Comparator() { + /** {@inheritDoc} */ + public int compare(PairDoubleInteger o1, + PairDoubleInteger o2) { + return Double.compare(o2.getKey(), o1.getKey()); + } + }; + + // Sort. + Collections.sort(list, comp); + + // Modify the original array so that its elements are in + // the prescribed order. + // Retrieve indices of original locations. + final int[] indices = new int[len]; + for (int i = 0; i < len; i++) { + final PairDoubleInteger e = list.get(i); + x[i] = e.getKey(); + indices[i] = e.getValue(); + } + + // In each of the associated arrays, move the + // elements to their new location. + for (int j = 0; j < yListLen; j++) { + // Input array will be modified in place. + final double[] yInPlace = yList[j]; + final double[] yOrig = yInPlace.clone(); + + for (int i = 0; i < len; i++) { + yInPlace[i] = yOrig[indices[i]]; + } + } + } + + /** + * Creates a copy of the {@code source} array. + * + * @param source Array to be copied. + * @return the copied array. + */ + public static int[] copyOf(int[] source) { + return copyOf(source, source.length); + } + + /** + * Creates a copy of the {@code source} array. + * + * @param source Array to be copied. + * @return the copied array. + */ + public static double[] copyOf(double[] source) { + return copyOf(source, source.length); + } + + /** + * Creates a copy of the {@code source} array. + * + * @param source Array to be copied. + * @param len Number of entries to copy. If smaller then the source + * length, the copy will be truncated, if larger it will padded with + * zeroes. + * @return the copied array. + */ + public static int[] copyOf(int[] source, int len) { + final int[] output = new int[len]; + System.arraycopy(source, 0, output, 0, FastMath.min(len, source.length)); + return output; + } + + /** + * Creates a copy of the {@code source} array. + * + * @param source Array to be copied. + * @param len Number of entries to copy. If smaller then the source + * length, the copy will be truncated, if larger it will padded with + * zeroes. + * @return the copied array. + */ + public static double[] copyOf(double[] source, int len) { + final double[] output = new double[len]; + System.arraycopy(source, 0, output, 0, FastMath.min(len, source.length)); + return output; + } + + /** + * Creates a copy of the {@code source} array. + * + * @param source Array to be copied. + * @param from Initial index of the range to be copied, inclusive. + * @param to Final index of the range to be copied, exclusive. (This index may lie outside the array.) + * @return the copied array. + */ + public static double[] copyOfRange(double[] source, int from, int to) { + final int len = to - from; + final double[] output = new double[len]; + System.arraycopy(source, from, output, 0, FastMath.min(len, source.length - from)); + return output; + } + + /** + * Compute a linear combination accurately. + * This method computes the sum of the products + * ai bi to high accuracy. + * It does so by using specific multiplication and addition algorithms to + * preserve accuracy and reduce cancellation effects. + *
              + * It is based on the 2005 paper + * + * Accurate Sum and Dot Product by Takeshi Ogita, Siegfried M. Rump, + * and Shin'ichi Oishi published in SIAM J. Sci. Comput. + * + * @param a Factors. + * @param b Factors. + * @return Σi ai bi. + * @throws DimensionMismatchException if arrays dimensions don't match + */ + public static double linearCombination(final double[] a, final double[] b) + throws DimensionMismatchException { + checkEqualLength(a, b); + final int len = a.length; + + if (len == 1) { + // Revert to scalar multiplication. + return a[0] * b[0]; + } + + final double[] prodHigh = new double[len]; + double prodLowSum = 0; + + for (int i = 0; i < len; i++) { + final double ai = a[i]; + final double aHigh = Double.longBitsToDouble(Double.doubleToRawLongBits(ai) & ((-1L) << 27)); + final double aLow = ai - aHigh; + + final double bi = b[i]; + final double bHigh = Double.longBitsToDouble(Double.doubleToRawLongBits(bi) & ((-1L) << 27)); + final double bLow = bi - bHigh; + prodHigh[i] = ai * bi; + final double prodLow = aLow * bLow - (((prodHigh[i] - + aHigh * bHigh) - + aLow * bHigh) - + aHigh * bLow); + prodLowSum += prodLow; + } + + + final double prodHighCur = prodHigh[0]; + double prodHighNext = prodHigh[1]; + double sHighPrev = prodHighCur + prodHighNext; + double sPrime = sHighPrev - prodHighNext; + double sLowSum = (prodHighNext - (sHighPrev - sPrime)) + (prodHighCur - sPrime); + + final int lenMinusOne = len - 1; + for (int i = 1; i < lenMinusOne; i++) { + prodHighNext = prodHigh[i + 1]; + final double sHighCur = sHighPrev + prodHighNext; + sPrime = sHighCur - prodHighNext; + sLowSum += (prodHighNext - (sHighCur - sPrime)) + (sHighPrev - sPrime); + sHighPrev = sHighCur; + } + + double result = sHighPrev + (prodLowSum + sLowSum); + + if (Double.isNaN(result)) { + // either we have split infinite numbers or some coefficients were NaNs, + // just rely on the naive implementation and let IEEE754 handle this + result = 0; + for (int i = 0; i < len; ++i) { + result += a[i] * b[i]; + } + } + + return result; + } + + /** + * Compute a linear combination accurately. + *

              + * This method computes a1×b1 + + * a2×b2 to high accuracy. It does + * so by using specific multiplication and addition algorithms to + * preserve accuracy and reduce cancellation effects. It is based + * on the 2005 paper + * Accurate Sum and Dot Product by Takeshi Ogita, + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. Comput. + *

              + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @return a1×b1 + + * a2×b2 + * @see #linearCombination(double, double, double, double, double, double) + * @see #linearCombination(double, double, double, double, double, double, double, double) + */ + public static double linearCombination(final double a1, final double b1, + final double a2, final double b2) { + + // the code below is split in many additions/subtractions that may + // appear redundant. However, they should NOT be simplified, as they + // use IEEE754 floating point arithmetic rounding properties. + // The variable naming conventions are that xyzHigh contains the most significant + // bits of xyz and xyzLow contains its least significant bits. So theoretically + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot + // be represented in only one double precision number so we preserve two numbers + // to hold it as long as we can, combining the high and low order bits together + // only at the end, after cancellation may have occurred on high order bits + + // split a1 and b1 as one 26 bits number and one 27 bits number + final double a1High = Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27)); + final double a1Low = a1 - a1High; + final double b1High = Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27)); + final double b1Low = b1 - b1High; + + // accurate multiplication a1 * b1 + final double prod1High = a1 * b1; + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low); + + // split a2 and b2 as one 26 bits number and one 27 bits number + final double a2High = Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27)); + final double a2Low = a2 - a2High; + final double b2High = Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27)); + final double b2Low = b2 - b2High; + + // accurate multiplication a2 * b2 + final double prod2High = a2 * b2; + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low); + + // accurate addition a1 * b1 + a2 * b2 + final double s12High = prod1High + prod2High; + final double s12Prime = s12High - prod2High; + final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime); + + // final rounding, s12 may have suffered many cancellations, we try + // to recover some bits from the extra words we have saved up to now + double result = s12High + (prod1Low + prod2Low + s12Low); + + if (Double.isNaN(result)) { + // either we have split infinite numbers or some coefficients were NaNs, + // just rely on the naive implementation and let IEEE754 handle this + result = a1 * b1 + a2 * b2; + } + + return result; + } + + /** + * Compute a linear combination accurately. + *

              + * This method computes a1×b1 + + * a2×b2 + a3×b3 + * to high accuracy. It does so by using specific multiplication and + * addition algorithms to preserve accuracy and reduce cancellation effects. + * It is based on the 2005 paper + * Accurate Sum and Dot Product by Takeshi Ogita, + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. Comput. + *

              + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + * @see #linearCombination(double, double, double, double) + * @see #linearCombination(double, double, double, double, double, double, double, double) + */ + public static double linearCombination(final double a1, final double b1, + final double a2, final double b2, + final double a3, final double b3) { + + // the code below is split in many additions/subtractions that may + // appear redundant. However, they should NOT be simplified, as they + // do use IEEE754 floating point arithmetic rounding properties. + // The variables naming conventions are that xyzHigh contains the most significant + // bits of xyz and xyzLow contains its least significant bits. So theoretically + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot + // be represented in only one double precision number so we preserve two numbers + // to hold it as long as we can, combining the high and low order bits together + // only at the end, after cancellation may have occurred on high order bits + + // split a1 and b1 as one 26 bits number and one 27 bits number + final double a1High = Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27)); + final double a1Low = a1 - a1High; + final double b1High = Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27)); + final double b1Low = b1 - b1High; + + // accurate multiplication a1 * b1 + final double prod1High = a1 * b1; + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low); + + // split a2 and b2 as one 26 bits number and one 27 bits number + final double a2High = Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27)); + final double a2Low = a2 - a2High; + final double b2High = Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27)); + final double b2Low = b2 - b2High; + + // accurate multiplication a2 * b2 + final double prod2High = a2 * b2; + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low); + + // split a3 and b3 as one 26 bits number and one 27 bits number + final double a3High = Double.longBitsToDouble(Double.doubleToRawLongBits(a3) & ((-1L) << 27)); + final double a3Low = a3 - a3High; + final double b3High = Double.longBitsToDouble(Double.doubleToRawLongBits(b3) & ((-1L) << 27)); + final double b3Low = b3 - b3High; + + // accurate multiplication a3 * b3 + final double prod3High = a3 * b3; + final double prod3Low = a3Low * b3Low - (((prod3High - a3High * b3High) - a3Low * b3High) - a3High * b3Low); + + // accurate addition a1 * b1 + a2 * b2 + final double s12High = prod1High + prod2High; + final double s12Prime = s12High - prod2High; + final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime); + + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 + final double s123High = s12High + prod3High; + final double s123Prime = s123High - prod3High; + final double s123Low = (prod3High - (s123High - s123Prime)) + (s12High - s123Prime); + + // final rounding, s123 may have suffered many cancellations, we try + // to recover some bits from the extra words we have saved up to now + double result = s123High + (prod1Low + prod2Low + prod3Low + s12Low + s123Low); + + if (Double.isNaN(result)) { + // either we have split infinite numbers or some coefficients were NaNs, + // just rely on the naive implementation and let IEEE754 handle this + result = a1 * b1 + a2 * b2 + a3 * b3; + } + + return result; + } + + /** + * Compute a linear combination accurately. + *

              + * This method computes a1×b1 + + * a2×b2 + a3×b3 + + * a4×b4 + * to high accuracy. It does so by using specific multiplication and + * addition algorithms to preserve accuracy and reduce cancellation effects. + * It is based on the 2005 paper + * Accurate Sum and Dot Product by Takeshi Ogita, + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. Comput. + *

              + * @param a1 first factor of the first term + * @param b1 second factor of the first term + * @param a2 first factor of the second term + * @param b2 second factor of the second term + * @param a3 first factor of the third term + * @param b3 second factor of the third term + * @param a4 first factor of the third term + * @param b4 second factor of the third term + * @return a1×b1 + + * a2×b2 + a3×b3 + + * a4×b4 + * @see #linearCombination(double, double, double, double) + * @see #linearCombination(double, double, double, double, double, double) + */ + public static double linearCombination(final double a1, final double b1, + final double a2, final double b2, + final double a3, final double b3, + final double a4, final double b4) { + + // the code below is split in many additions/subtractions that may + // appear redundant. However, they should NOT be simplified, as they + // do use IEEE754 floating point arithmetic rounding properties. + // The variables naming conventions are that xyzHigh contains the most significant + // bits of xyz and xyzLow contains its least significant bits. So theoretically + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot + // be represented in only one double precision number so we preserve two numbers + // to hold it as long as we can, combining the high and low order bits together + // only at the end, after cancellation may have occurred on high order bits + + // split a1 and b1 as one 26 bits number and one 27 bits number + final double a1High = Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27)); + final double a1Low = a1 - a1High; + final double b1High = Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27)); + final double b1Low = b1 - b1High; + + // accurate multiplication a1 * b1 + final double prod1High = a1 * b1; + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low); + + // split a2 and b2 as one 26 bits number and one 27 bits number + final double a2High = Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27)); + final double a2Low = a2 - a2High; + final double b2High = Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27)); + final double b2Low = b2 - b2High; + + // accurate multiplication a2 * b2 + final double prod2High = a2 * b2; + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low); + + // split a3 and b3 as one 26 bits number and one 27 bits number + final double a3High = Double.longBitsToDouble(Double.doubleToRawLongBits(a3) & ((-1L) << 27)); + final double a3Low = a3 - a3High; + final double b3High = Double.longBitsToDouble(Double.doubleToRawLongBits(b3) & ((-1L) << 27)); + final double b3Low = b3 - b3High; + + // accurate multiplication a3 * b3 + final double prod3High = a3 * b3; + final double prod3Low = a3Low * b3Low - (((prod3High - a3High * b3High) - a3Low * b3High) - a3High * b3Low); + + // split a4 and b4 as one 26 bits number and one 27 bits number + final double a4High = Double.longBitsToDouble(Double.doubleToRawLongBits(a4) & ((-1L) << 27)); + final double a4Low = a4 - a4High; + final double b4High = Double.longBitsToDouble(Double.doubleToRawLongBits(b4) & ((-1L) << 27)); + final double b4Low = b4 - b4High; + + // accurate multiplication a4 * b4 + final double prod4High = a4 * b4; + final double prod4Low = a4Low * b4Low - (((prod4High - a4High * b4High) - a4Low * b4High) - a4High * b4Low); + + // accurate addition a1 * b1 + a2 * b2 + final double s12High = prod1High + prod2High; + final double s12Prime = s12High - prod2High; + final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime); + + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 + final double s123High = s12High + prod3High; + final double s123Prime = s123High - prod3High; + final double s123Low = (prod3High - (s123High - s123Prime)) + (s12High - s123Prime); + + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4 + final double s1234High = s123High + prod4High; + final double s1234Prime = s1234High - prod4High; + final double s1234Low = (prod4High - (s1234High - s1234Prime)) + (s123High - s1234Prime); + + // final rounding, s1234 may have suffered many cancellations, we try + // to recover some bits from the extra words we have saved up to now + double result = s1234High + (prod1Low + prod2Low + prod3Low + prod4Low + s12Low + s123Low + s1234Low); + + if (Double.isNaN(result)) { + // either we have split infinite numbers or some coefficients were NaNs, + // just rely on the naive implementation and let IEEE754 handle this + result = a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4; + } + + return result; + } + + /** + * Returns true iff both arguments are null or have same dimensions and all + * their elements are equal as defined by + * {@link Precision#equals(float,float)}. + * + * @param x first array + * @param y second array + * @return true if the values are both null or have same dimension + * and equal elements. + */ + public static boolean equals(float[] x, float[] y) { + if ((x == null) || (y == null)) { + return !((x == null) ^ (y == null)); + } + if (x.length != y.length) { + return false; + } + for (int i = 0; i < x.length; ++i) { + if (!Precision.equals(x[i], y[i])) { + return false; + } + } + return true; + } + + /** + * Returns true iff both arguments are null or have same dimensions and all + * their elements are equal as defined by + * {@link Precision#equalsIncludingNaN(double,double) this method}. + * + * @param x first array + * @param y second array + * @return true if the values are both null or have same dimension and + * equal elements + * @since 2.2 + */ + public static boolean equalsIncludingNaN(float[] x, float[] y) { + if ((x == null) || (y == null)) { + return !((x == null) ^ (y == null)); + } + if (x.length != y.length) { + return false; + } + for (int i = 0; i < x.length; ++i) { + if (!Precision.equalsIncludingNaN(x[i], y[i])) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} iff both arguments are {@code null} or have same + * dimensions and all their elements are equal as defined by + * {@link Precision#equals(double,double)}. + * + * @param x First array. + * @param y Second array. + * @return {@code true} if the values are both {@code null} or have same + * dimension and equal elements. + */ + public static boolean equals(double[] x, double[] y) { + if ((x == null) || (y == null)) { + return !((x == null) ^ (y == null)); + } + if (x.length != y.length) { + return false; + } + for (int i = 0; i < x.length; ++i) { + if (!Precision.equals(x[i], y[i])) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} iff both arguments are {@code null} or have same + * dimensions and all their elements are equal as defined by + * {@link Precision#equalsIncludingNaN(double,double) this method}. + * + * @param x First array. + * @param y Second array. + * @return {@code true} if the values are both {@code null} or have same + * dimension and equal elements. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(double[] x, double[] y) { + if ((x == null) || (y == null)) { + return !((x == null) ^ (y == null)); + } + if (x.length != y.length) { + return false; + } + for (int i = 0; i < x.length; ++i) { + if (!Precision.equalsIncludingNaN(x[i], y[i])) { + return false; + } + } + return true; + } + + /** + * Normalizes an array to make it sum to a specified value. + * Returns the result of the transformation + *
              +     *    x |-> x * normalizedSum / sum
              +     * 
              + * 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. + *

              + * Throws IllegalArgumentException if {@code normalizedSum} is infinite + * or NaN and ArithmeticException if the input array contains any infinite elements + * or sums to 0. + *

              + * Ignores (i.e., copies unchanged to the output array) NaNs in the input array. + * + * @param values Input array to be normalized + * @param normalizedSum Target sum for the normalized array + * @return the normalized array. + * @throws MathArithmeticException if the input array contains infinite + * elements or sums to zero. + * @throws MathIllegalArgumentException if the target sum is infinite or {@code NaN}. + * @since 2.1 + */ + public static double[] normalizeArray(double[] values, double normalizedSum) + throws MathIllegalArgumentException, MathArithmeticException { + if (Double.isInfinite(normalizedSum)) { + throw new MathIllegalArgumentException(LocalizedFormats.NORMALIZE_INFINITE); + } + if (Double.isNaN(normalizedSum)) { + throw new MathIllegalArgumentException(LocalizedFormats.NORMALIZE_NAN); + } + double sum = 0d; + final int len = values.length; + double[] out = new double[len]; + for (int i = 0; i < len; i++) { + if (Double.isInfinite(values[i])) { + throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_ARRAY_ELEMENT, values[i], i); + } + if (!Double.isNaN(values[i])) { + sum += values[i]; + } + } + if (sum == 0) { + throw new MathArithmeticException(LocalizedFormats.ARRAY_SUMS_TO_ZERO); + } + for (int i = 0; i < len; i++) { + if (Double.isNaN(values[i])) { + out[i] = Double.NaN; + } else { + out[i] = values[i] * normalizedSum / sum; + } + } + return out; + } + + /** Build an array of elements. + *

              + * Arrays are filled with field.getZero() + * + * @param the type of the field elements + * @param field field to which array elements belong + * @param length of the array + * @return a new array + * @since 3.2 + */ + public static T[] buildArray(final Field field, final int length) { + @SuppressWarnings("unchecked") // OK because field must be correct class + T[] array = (T[]) Array.newInstance(field.getRuntimeClass(), length); + Arrays.fill(array, field.getZero()); + return array; + } + + /** Build a double dimension array of elements. + *

              + * Arrays are filled with field.getZero() + * + * @param the type of the field elements + * @param field field to which array elements belong + * @param rows number of rows in the array + * @param columns number of columns (may be negative to build partial + * arrays in the same way new Field[rows][] works) + * @return a new array + * @since 3.2 + */ + @SuppressWarnings("unchecked") + public static T[][] buildArray(final Field field, final int rows, final int columns) { + final T[][] array; + if (columns < 0) { + T[] dummyRow = buildArray(field, 0); + array = (T[][]) Array.newInstance(dummyRow.getClass(), rows); + } else { + array = (T[][]) Array.newInstance(field.getRuntimeClass(), + new int[] { + rows, columns + }); + for (int i = 0; i < rows; ++i) { + Arrays.fill(array[i], field.getZero()); + } + } + return array; + } + + /** + * Calculates the + * convolution between two sequences. + *

              + * The solution is obtained via straightforward computation of the + * convolution sum (and not via FFT). Whenever the computation needs + * an element that would be located at an index outside the input arrays, + * the value is assumed to be zero. + * + * @param x First sequence. + * Typically, this sequence will represent an input signal to a system. + * @param h Second sequence. + * Typically, this sequence will represent the impulse response of the system. + * @return the convolution of {@code x} and {@code h}. + * This array's length will be {@code x.length + h.length - 1}. + * @throws NullArgumentException if either {@code x} or {@code h} is {@code null}. + * @throws NoDataException if either {@code x} or {@code h} is empty. + * + * @since 3.3 + */ + public static double[] convolve(double[] x, double[] h) + throws NullArgumentException, + NoDataException { + MathUtils.checkNotNull(x); + MathUtils.checkNotNull(h); + + final int xLen = x.length; + final int hLen = h.length; + + if (xLen == 0 || hLen == 0) { + throw new NoDataException(); + } + + // initialize the output array + final int totalLength = xLen + hLen - 1; + final double[] y = new double[totalLength]; + + // straightforward implementation of the convolution sum + for (int n = 0; n < totalLength; n++) { + double yn = 0; + int k = FastMath.max(0, n + 1 - xLen); + int j = n - k; + while (k < hLen && j >= 0) { + yn += x[j--] * h[k++]; + } + y[n] = yn; + } + + return y; + } + + /** + * Specification for indicating that some operation applies + * before or after a given index. + */ + public enum Position { + /** Designates the beginning of the array (near index 0). */ + HEAD, + /** Designates the end of the array. */ + TAIL + } + + /** + * Shuffle the entries of the given array. + * The {@code start} and {@code pos} parameters select which portion + * of the array is randomized and which is left untouched. + * + * @see #shuffle(int[],int,Position, RandomGenerator) + * + * @param list Array whose entries will be shuffled (in-place). + * @param start Index at which shuffling begins. + * @param pos Shuffling is performed for index positions between + * {@code start} and either the end (if {@link Position#TAIL}) + * or the beginning (if {@link Position#HEAD}) of the array. + */ + public static void shuffle(int[] list, + int start, + Position pos) { + shuffle(list, start, pos, new Well19937c()); + } + + /** + * Shuffle the entries of the given array, using the + * + * Fisher–Yates algorithm. + * The {@code start} and {@code pos} parameters select which portion + * of the array is randomized and which is left untouched. + * + * @param list Array whose entries will be shuffled (in-place). + * @param start Index at which shuffling begins. + * @param pos Shuffling is performed for index positions between + * {@code start} and either the end (if {@link Position#TAIL}) + * or the beginning (if {@link Position#HEAD}) of the array. + * @param rng Random number generator. + */ + public static void shuffle(int[] list, + int start, + Position pos, + RandomGenerator rng) { + switch (pos) { + case TAIL: { + for (int i = list.length - 1; i >= start; i--) { + final int target; + if (i == start) { + target = start; + } else { + // NumberIsTooLargeException cannot occur. + target = new UniformIntegerDistribution(rng, start, i).sample(); + } + final int temp = list[target]; + list[target] = list[i]; + list[i] = temp; + } + } + break; + case HEAD: { + for (int i = 0; i <= start; i++) { + final int target; + if (i == start) { + target = start; + } else { + // NumberIsTooLargeException cannot occur. + target = new UniformIntegerDistribution(rng, i, start).sample(); + } + final int temp = list[target]; + list[target] = list[i]; + list[i] = temp; + } + } + break; + default: + throw new MathInternalError(); // Should never happen. + } + } + + /** + * Shuffle the entries of the given array. + * + * @see #shuffle(int[],int,Position,RandomGenerator) + * + * @param list Array whose entries will be shuffled (in-place). + * @param rng Random number generator. + */ + public static void shuffle(int[] list, + RandomGenerator rng) { + shuffle(list, 0, Position.TAIL, rng); + } + + /** + * Shuffle the entries of the given array. + * + * @see #shuffle(int[],int,Position,RandomGenerator) + * + * @param list Array whose entries will be shuffled (in-place). + */ + public static void shuffle(int[] list) { + shuffle(list, new Well19937c()); + } + + /** + * Returns an array representing the natural number {@code n}. + * + * @param n Natural number. + * @return an array whose entries are the numbers 0, 1, ..., {@code n}-1. + * If {@code n == 0}, the returned array is empty. + */ + public static int[] natural(int n) { + return sequence(n, 0, 1); + } + /** + * Returns an array of {@code size} integers starting at {@code start}, + * skipping {@code stride} numbers. + * + * @param size Natural number. + * @param start Natural number. + * @param stride Natural number. + * @return an array whose entries are the numbers + * {@code start, start + stride, ..., start + (size - 1) * stride}. + * If {@code size == 0}, the returned array is empty. + * + * @since 3.4 + */ + public static int[] sequence(int size, + int start, + int stride) { + final int[] a = new int[size]; + for (int i = 0; i < size; i++) { + a[i] = start + i * stride; + } + return a; + } + /** + * This method is used + * to verify that the input parameters designate a subarray of positive length. + *

              + *

                + *
              • returns true iff the parameters designate a subarray of + * positive length
              • + *
              • throws MathIllegalArgumentException if the array is null or + * or the indices are invalid
              • + *
              • returns false
              • if the array is non-null, but + * length is 0. + *

              + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return true if the parameters are valid and designate a subarray of positive length + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.3 + */ + public static boolean verifyValues(final double[] values, final int begin, final int length) + throws MathIllegalArgumentException { + return verifyValues(values, begin, length, false); + } + + /** + * This method is used + * to verify that the input parameters designate a subarray of positive length. + *

              + *

                + *
              • returns true iff the parameters designate a subarray of + * non-negative length
              • + *
              • throws IllegalArgumentException if the array is null or + * or the indices are invalid
              • + *
              • returns false
              • if the array is non-null, but + * length is 0 unless allowEmpty is true + *

              + * + * @param values the input array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @param allowEmpty if true then zero length arrays are allowed + * @return true if the parameters are valid + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.3 + */ + public static boolean verifyValues(final double[] values, final int begin, + final int length, final boolean allowEmpty) throws MathIllegalArgumentException { + + if (values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + + if (begin < 0) { + throw new NotPositiveException(LocalizedFormats.START_POSITION, Integer.valueOf(begin)); + } + + if (length < 0) { + throw new NotPositiveException(LocalizedFormats.LENGTH, Integer.valueOf(length)); + } + + if (begin + length > values.length) { + throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END, + Integer.valueOf(begin + length), Integer.valueOf(values.length), true); + } + + if (length == 0 && !allowEmpty) { + return false; + } + + return true; + + } + + /** + * This method is used + * to verify that the begin and length parameters designate a subarray of positive length + * and the weights are all non-negative, non-NaN, finite, and not all zero. + *

              + *

                + *
              • returns true iff the parameters designate a subarray of + * positive length and the weights array contains legitimate values.
              • + *
              • throws IllegalArgumentException if any of the following are true: + *
                • the values array is null
                • + *
                • the weights array is null
                • + *
                • the weights array does not have the same length as the values array
                • + *
                • the weights array contains one or more infinite values
                • + *
                • the weights array contains one or more NaN values
                • + *
                • the weights array contains negative values
                • + *
                • the start and length arguments do not determine a valid array
                + *
              • + *
              • returns false
              • if the array is non-null, but + * length is 0. + *

              + * + * @param values the input array + * @param weights the weights array + * @param begin index of the first array element to include + * @param length the number of elements to include + * @return true if the parameters are valid and designate a subarray of positive length + * @throws MathIllegalArgumentException if the indices are invalid or the array is null + * @since 3.3 + */ + public static boolean verifyValues( + final double[] values, + final double[] weights, + final int begin, + final int length) throws MathIllegalArgumentException { + return verifyValues(values, weights, begin, length, false); + } + + /** + * This method is used + * to verify that the begin and length parameters designate a subarray of positive length + * and the weights are all non-negative, non-NaN, finite, and not all zero. + *

              + *

                + *
              • returns true iff the parameters designate a subarray of + * non-negative length and the weights array contains legitimate values.
              • + *
              • throws MathIllegalArgumentException if any of the following are true: + *
                • the values array is null
                • + *
                • the weights array is null
                • + *
                • the weights array does not have the same length as the values array
                • + *
                • the weights array contains one or more infinite values
                • + *
                • the weights array contains one or more NaN values
                • + *
                • the weights array contains negative values
                • + *
                • the start and length arguments do not determine a valid array
                + *
              • + *
              • returns false
              • if the array is non-null, but + * length is 0 unless allowEmpty is true. + *

              + * + * @param values the input array. + * @param weights the weights array. + * @param begin index of the first array element to include. + * @param length the number of elements to include. + * @param allowEmpty if {@code true} than allow zero length arrays to pass. + * @return {@code true} if the parameters are valid. + * @throws NullArgumentException if either of the arrays are null + * @throws MathIllegalArgumentException if the array indices are not valid, + * the weights array contains NaN, infinite or negative elements, or there + * are no positive weights. + * @since 3.3 + */ + public static boolean verifyValues(final double[] values, final double[] weights, + final int begin, final int length, final boolean allowEmpty) throws MathIllegalArgumentException { + + if (weights == null || values == null) { + throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY); + } + + checkEqualLength(weights, values); + + boolean containsPositiveWeight = false; + for (int i = begin; i < begin + length; i++) { + final double weight = weights[i]; + if (Double.isNaN(weight)) { + throw new MathIllegalArgumentException(LocalizedFormats.NAN_ELEMENT_AT_INDEX, Integer.valueOf(i)); + } + if (Double.isInfinite(weight)) { + throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_ARRAY_ELEMENT, Double.valueOf(weight), Integer.valueOf(i)); + } + if (weight < 0) { + throw new MathIllegalArgumentException(LocalizedFormats.NEGATIVE_ELEMENT_AT_INDEX, Integer.valueOf(i), Double.valueOf(weight)); + } + if (!containsPositiveWeight && weight > 0.0) { + containsPositiveWeight = true; + } + } + + if (!containsPositiveWeight) { + throw new MathIllegalArgumentException(LocalizedFormats.WEIGHT_AT_LEAST_ONE_NON_ZERO); + } + + return verifyValues(values, begin, length, allowEmpty); + } + + /** + * Concatenates a sequence of arrays. The return array consists of the + * entries of the input arrays concatenated in the order they appear in + * the argument list. Null arrays cause NullPointerExceptions; zero + * length arrays are allowed (contributing nothing to the output array). + * + * @param x list of double[] arrays to concatenate + * @return a new array consisting of the entries of the argument arrays + * @throws NullPointerException if any of the arrays are null + * @since 3.6 + */ + public static double[] concatenate(double[] ...x) { + int combinedLength = 0; + for (double[] a : x) { + combinedLength += a.length; + } + int offset = 0; + int curLength = 0; + final double[] combined = new double[combinedLength]; + for (int i = 0; i < x.length; i++) { + curLength = x[i].length; + System.arraycopy(x[i], 0, combined, offset, curLength); + offset += curLength; + } + return combined; + } + + /** + * Returns an array consisting of the unique values in {@code data}. + * The return array is sorted in descending order. Empty arrays + * are allowed, but null arrays result in NullPointerException. + * Infinities are allowed. NaN values are allowed with maximum + * sort order - i.e., if there are NaN values in {@code data}, + * {@code Double.NaN} will be the first element of the output array, + * even if the array also contains {@code Double.POSITIVE_INFINITY}. + * + * @param data array to scan + * @return descending list of values included in the input array + * @throws NullPointerException if data is null + * @since 3.6 + */ + public static double[] unique(double[] data) { + TreeSet values = new TreeSet(); + for (int i = 0; i < data.length; i++) { + values.add(data[i]); + } + final int count = values.size(); + final double[] out = new double[count]; + Iterator iterator = values.iterator(); + int i = 0; + while (iterator.hasNext()) { + out[count - ++i] = iterator.next(); + } + return out; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathUtils.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathUtils.java new file mode 100644 index 000000000..97b47965b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MathUtils.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 com.fr.third.org.apache.commons.math3.util; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.NotFiniteNumberException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.Localizable; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; +import com.fr.third.org.apache.commons.math3.RealFieldElement; + +/** + * Miscellaneous utility functions. + * + * @see ArithmeticUtils + * @see Precision + * @see MathArrays + * + */ +public final class MathUtils { + /** + * \(2\pi\) + * @since 2.1 + */ + public static final double TWO_PI = 2 * FastMath.PI; + + /** + * \(\pi^2\) + * @since 3.4 + */ + public static final double PI_SQUARED = FastMath.PI * FastMath.PI; + + + /** + * Class contains only static methods. + */ + private MathUtils() {} + + + /** + * Returns an integer hash code representing the given double value. + * + * @param value the value to be hashed + * @return the hash code + */ + public static int hash(double value) { + return new Double(value).hashCode(); + } + + /** + * Returns {@code true} if the values are equal according to semantics of + * {@link Double#equals(Object)}. + * + * @param x Value + * @param y Value + * @return {@code new Double(x).equals(new Double(y))} + */ + public static boolean equals(double x, double y) { + return new Double(x).equals(new Double(y)); + } + + /** + * Returns an integer hash code representing the given double array. + * + * @param value the value to be hashed (may be null) + * @return the hash code + * @since 1.2 + */ + public static int hash(double[] value) { + return Arrays.hashCode(value); + } + + /** + * Normalize an angle in a 2π wide interval around a center value. + *

              This method has three main uses:

              + *
                + *
              • normalize an angle between 0 and 2π:
                + * {@code a = MathUtils.normalizeAngle(a, FastMath.PI);}
              • + *
              • normalize an angle between -π and +π
                + * {@code a = MathUtils.normalizeAngle(a, 0.0);}
              • + *
              • compute the angle between two defining angular positions:
                + * {@code angle = MathUtils.normalizeAngle(end, start) - start;}
              • + *
              + *

              Note that due to numerical accuracy and since π cannot be represented + * exactly, the result interval is closed, it cannot be half-closed + * as would be more satisfactory in a purely mathematical view.

              + * @param a angle to normalize + * @param center center of the desired 2π 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); + } + + /** Find the maximum of two field elements. + * @param the type of the field elements + * @param e1 first element + * @param e2 second element + * @return max(a1, e2) + * @since 3.6 + */ + public static > T max(final T e1, final T e2) { + return e1.subtract(e2).getReal() >= 0 ? e1 : e2; + } + + /** Find the minimum of two field elements. + * @param the type of the field elements + * @param e1 first element + * @param e2 second element + * @return min(a1, e2) + * @since 3.6 + */ + public static > T min(final T e1, final T e2) { + return e1.subtract(e2).getReal() >= 0 ? e2 : e1; + } + + /** + *

              Reduce {@code |a - offset|} to the primary interval + * {@code [0, |period|)}.

              + * + *

              Specifically, the value returned is
              + * {@code a - |period| * floor((a - offset) / |period|) - offset}.

              + * + *

              If any of the parameters are {@code NaN} or infinite, the result is + * {@code NaN}.

              + * + * @param a Value to reduce. + * @param period Period. + * @param offset Value that will be mapped to {@code 0}. + * @return the value, within the interval {@code [0 |period|)}, + * that corresponds to {@code a}. + */ + public static double reduce(double a, + double period, + double offset) { + final double p = FastMath.abs(period); + return a - p * FastMath.floor((a - offset) / p) - offset; + } + + /** + * Returns the first argument with the sign of the second argument. + * + * @param magnitude Magnitude of the returned value. + * @param sign Sign of the returned value. + * @return a value with magnitude equal to {@code magnitude} and with the + * same sign as the {@code sign} argument. + * @throws MathArithmeticException if {@code magnitude == Byte.MIN_VALUE} + * and {@code sign >= 0}. + */ + public static byte copySign(byte magnitude, byte sign) + throws MathArithmeticException { + if ((magnitude >= 0 && sign >= 0) || + (magnitude < 0 && sign < 0)) { // Sign is OK. + return magnitude; + } else if (sign >= 0 && + magnitude == Byte.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } else { + return (byte) -magnitude; // Flip sign. + } + } + + /** + * Returns the first argument with the sign of the second argument. + * + * @param magnitude Magnitude of the returned value. + * @param sign Sign of the returned value. + * @return a value with magnitude equal to {@code magnitude} and with the + * same sign as the {@code sign} argument. + * @throws MathArithmeticException if {@code magnitude == Short.MIN_VALUE} + * and {@code sign >= 0}. + */ + public static short copySign(short magnitude, short sign) + throws MathArithmeticException { + if ((magnitude >= 0 && sign >= 0) || + (magnitude < 0 && sign < 0)) { // Sign is OK. + return magnitude; + } else if (sign >= 0 && + magnitude == Short.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } else { + return (short) -magnitude; // Flip sign. + } + } + + /** + * Returns the first argument with the sign of the second argument. + * + * @param magnitude Magnitude of the returned value. + * @param sign Sign of the returned value. + * @return a value with magnitude equal to {@code magnitude} and with the + * same sign as the {@code sign} argument. + * @throws MathArithmeticException if {@code magnitude == Integer.MIN_VALUE} + * and {@code sign >= 0}. + */ + public static int copySign(int magnitude, int sign) + throws MathArithmeticException { + if ((magnitude >= 0 && sign >= 0) || + (magnitude < 0 && sign < 0)) { // Sign is OK. + return magnitude; + } else if (sign >= 0 && + magnitude == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } else { + return -magnitude; // Flip sign. + } + } + + /** + * Returns the first argument with the sign of the second argument. + * + * @param magnitude Magnitude of the returned value. + * @param sign Sign of the returned value. + * @return a value with magnitude equal to {@code magnitude} and with the + * same sign as the {@code sign} argument. + * @throws MathArithmeticException if {@code magnitude == Long.MIN_VALUE} + * and {@code sign >= 0}. + */ + public static long copySign(long magnitude, long sign) + throws MathArithmeticException { + if ((magnitude >= 0 && sign >= 0) || + (magnitude < 0 && sign < 0)) { // Sign is OK. + return magnitude; + } else if (sign >= 0 && + magnitude == Long.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW); + } else { + return -magnitude; // Flip sign. + } + } + /** + * Check that the argument is a real number. + * + * @param x Argument. + * @throws NotFiniteNumberException if {@code x} is not a + * finite real number. + */ + public static void checkFinite(final double x) + throws NotFiniteNumberException { + if (Double.isInfinite(x) || Double.isNaN(x)) { + throw new NotFiniteNumberException(x); + } + } + + /** + * Check that all the elements are real numbers. + * + * @param val Arguments. + * @throws NotFiniteNumberException if any values of the array is not a + * finite real number. + */ + public static void checkFinite(final double[] val) + throws NotFiniteNumberException { + for (int i = 0; i < val.length; i++) { + final double x = val[i]; + if (Double.isInfinite(x) || Double.isNaN(x)) { + throw new NotFiniteNumberException(LocalizedFormats.ARRAY_ELEMENT, x, i); + } + } + } + + /** + * Checks that an object is not null. + * + * @param o Object to be checked. + * @param pattern Message pattern. + * @param args Arguments to replace the placeholders in {@code pattern}. + * @throws NullArgumentException if {@code o} is {@code null}. + */ + public static void checkNotNull(Object o, + Localizable pattern, + Object ... args) + throws NullArgumentException { + if (o == null) { + throw new NullArgumentException(pattern, args); + } + } + + /** + * Checks that an object is not null. + * + * @param o Object to be checked. + * @throws NullArgumentException if {@code o} is {@code null}. + */ + public static void checkNotNull(Object o) + throws NullArgumentException { + if (o == null) { + throw new NullArgumentException(); + } + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java new file mode 100644 index 000000000..fdfe308c0 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + + +/** + * Classic median of 3 strategy given begin and end indices. + * @since 3.4 + */ +public class MedianOf3PivotingStrategy implements PivotingStrategyInterface, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140713L; + + /**{@inheritDoc} + * This in specific makes use of median of 3 pivoting. + * @return The index corresponding to a pivot chosen between the + * first, middle and the last indices of the array slice + * @throws MathIllegalArgumentException when indices exceeds range + */ + public int pivotIndex(final double[] work, final int begin, final int end) + throws MathIllegalArgumentException { + MathArrays.verifyValues(work, begin, end-begin); + final int inclusiveEnd = end - 1; + final int middle = begin + (inclusiveEnd - begin) / 2; + final double wBegin = work[begin]; + final double wMiddle = work[middle]; + final double wEnd = work[inclusiveEnd]; + + if (wBegin < wMiddle) { + if (wMiddle < wEnd) { + return middle; + } else { + return wBegin < wEnd ? inclusiveEnd : begin; + } + } else { + if (wBegin < wEnd) { + return begin; + } else { + return wMiddle < wEnd ? inclusiveEnd : middle; + } + } + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MultidimensionalCounter.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MultidimensionalCounter.java new file mode 100644 index 000000000..674873a6f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/MultidimensionalCounter.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 com.fr.third.org.apache.commons.math3.util; + +import java.util.NoSuchElementException; + +import com.fr.third.org.apache.commons.math3.exception.DimensionMismatchException; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.OutOfRangeException; + +/** + * 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: + *
                + *
              • (0, 0, 0) corresponds to 0
              • + *
              • (0, 0, 1) corresponds to 1
              • + *
              • (0, 0, 2) corresponds to 2
              • + *
              • (0, 1, 0) corresponds to 3
              • + *
              • ...
              • + *
              • (1, 0, 0) corresponds to 12
              • + *
              • ...
              • + *
              • (1, 3, 2) corresponds to 23
              • + *
              + * + * @since 2.2 + */ +public class MultidimensionalCounter implements Iterable { + /** + * 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 { + /** + * Multidimensional counter. + */ + private final int[] counter = new int[dimension]; + /** + * Unidimensional counter. + */ + private int count = -1; + /** + * Maximum value for {@link #count}. + */ + private final int maxCount = totalSize - 1; + + /** + * Create an iterator + * @see #iterator() + */ + Iterator() { + counter[last] = -1; + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return count < maxCount; + } + + /** + * @return the unidimensional count after the counter has been + * incremented by {@code 1}. + * @throws NoSuchElementException if {@link #hasNext()} would have + * returned {@code false}. + */ + public Integer next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + for (int i = last; i >= 0; i--) { + if (counter[i] == size[i] - 1) { + counter[i] = 0; + } else { + ++counter[i]; + break; + } + } + + return ++count; + } + + /** + * Get the current unidimensional counter slot. + * + * @return the index within the unidimensionl counter. + */ + public int getCount() { + return count; + } + /** + * Get the current multidimensional counter slots. + * + * @return the indices within the multidimensional counter. + */ + public int[] getCounts() { + return MathArrays.copyOf(counter); + } + + /** + * Get the current count in the selected dimension. + * + * @param dim Dimension index. + * @return the count at the corresponding index for the current state + * of the iterator. + * @throws IndexOutOfBoundsException if {@code index} is not in the + * correct interval (as defined by the length of the argument in the + * {@link MultidimensionalCounter#MultidimensionalCounter(int[]) + * constructor of the enclosing class}). + */ + public int getCount(int dim) { + return counter[dim]; + } + + /** + * @throws UnsupportedOperationException + */ + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Create a counter. + * + * @param size Counter sizes (number of slots in each dimension). + * @throws NotStrictlyPositiveException if one of the sizes is + * negative or zero. + */ + public MultidimensionalCounter(int ... size) throws NotStrictlyPositiveException { + dimension = size.length; + this.size = MathArrays.copyOf(size); + + uniCounterOffset = new int[dimension]; + + last = dimension - 1; + int tS = size[last]; + for (int i = 0; i < last; i++) { + int count = 1; + for (int j = i + 1; j < dimension; j++) { + count *= size[j]; + } + uniCounterOffset[i] = count; + tS *= size[i]; + } + uniCounterOffset[last] = 0; + + if (tS <= 0) { + throw new NotStrictlyPositiveException(tS); + } + + totalSize = tS; + } + + /** + * Create an iterator over this counter. + * + * @return the iterator. + */ + public Iterator iterator() { + return new Iterator(); + } + + /** + * Get the number of dimensions of the multidimensional counter. + * + * @return the number of dimensions. + */ + public int getDimension() { + return dimension; + } + + /** + * Convert to multidimensional counter. + * + * @param index Index in unidimensional counter. + * @return the multidimensional counts. + * @throws OutOfRangeException if {@code index} is not between + * {@code 0} and the value returned by {@link #getSize()} (excluded). + */ + public int[] getCounts(int index) throws OutOfRangeException { + if (index < 0 || + index >= totalSize) { + throw new OutOfRangeException(index, 0, totalSize); + } + + final int[] indices = new int[dimension]; + + int count = 0; + for (int i = 0; i < last; i++) { + int idx = 0; + final int offset = uniCounterOffset[i]; + while (count <= index) { + count += offset; + ++idx; + } + --idx; + count -= offset; + indices[i] = idx; + } + + indices[last] = index - count; + + return indices; + } + + /** + * Convert to unidimensional counter. + * + * @param c Indices in multidimensional counter. + * @return the index within the unidimensionl counter. + * @throws DimensionMismatchException if the size of {@code c} + * does not match the size of the array given in the constructor. + * @throws OutOfRangeException if a value of {@code c} is not in + * the range of the corresponding dimension, as defined in the + * {@link MultidimensionalCounter#MultidimensionalCounter(int...) constructor}. + */ + public int getCount(int ... c) + throws OutOfRangeException, DimensionMismatchException { + if (c.length != dimension) { + throw new DimensionMismatchException(c.length, dimension); + } + int count = 0; + for (int i = 0; i < dimension; i++) { + final int index = c[i]; + if (index < 0 || + index >= size[i]) { + throw new OutOfRangeException(index, 0, size[i] - 1); + } + count += uniCounterOffset[i] * c[i]; + } + return count + c[last]; + } + + /** + * Get the total number of elements. + * + * @return the total size of the unidimensional counter. + */ + public int getSize() { + return totalSize; + } + /** + * Get the number of multidimensional counter slots in each dimension. + * + * @return the sizes of the multidimensional counter in each dimension. + */ + public int[] getSizes() { + return MathArrays.copyOf(size); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + sb.append("[").append(getCount(i)).append("]"); + } + return sb.toString(); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/NumberTransformer.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/NumberTransformer.java new file mode 100644 index 000000000..d7f29ef47 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/NumberTransformer.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 com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * Subclasses implementing this interface can transform Objects to doubles. + * + * No longer extends Serializable since 2.0 + * + */ +public interface NumberTransformer { + + /** + * Implementing this interface provides a facility to transform + * from Object to Double. + * + * @param o the Object to be transformed. + * @return the double value of the Object. + * @throws MathIllegalArgumentException if the Object can not be transformed into a Double. + */ + double transform(Object o) throws MathIllegalArgumentException; +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java new file mode 100644 index 000000000..eb435a47b --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java @@ -0,0 +1,596 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; + +/** + * Open addressed map from int to double. + *

              This class provides a dedicated map from integers to doubles with a + * much smaller memory overhead than standard java.util.Map.

              + *

              This class is not synchronized. The specialized iterators returned by + * {@link #iterator()} are fail-fast: they throw a + * ConcurrentModificationException when they detect the map has been + * modified during iteration.

              + * @since 2.0 + */ +public class OpenIntToDoubleHashMap implements Serializable { + + /** Status indicator for free table entries. */ + protected static final byte FREE = 0; + + /** Status indicator for full table entries. */ + protected static final byte FULL = 1; + + /** Status indicator for removed table entries. */ + protected static final byte REMOVED = 2; + + /** Serializable version identifier */ + private static final long serialVersionUID = -3646337053166149105L; + + /** Load factor for the map. */ + private static final float LOAD_FACTOR = 0.5f; + + /** Default starting size. + *

              This must be a power of two for bit mask to work properly.

              + */ + private static final int DEFAULT_EXPECTED_SIZE = 16; + + /** Multiplier for size growth when map fills up. + *

              This must be a power of two for bit mask to work properly.

              + */ + private static final int RESIZE_MULTIPLIER = 2; + + /** Number of bits to perturb the index when probing for collision resolution. */ + private static final int PERTURB_SHIFT = 5; + + /** Keys table. */ + private int[] keys; + + /** Values table. */ + private double[] values; + + /** States table. */ + private byte[] states; + + /** Return value for missing entries. */ + private final double missingEntries; + + /** Current size of the map. */ + private int size; + + /** Bit mask for hash values. */ + private int mask; + + /** Modifications count. */ + private transient int count; + + /** + * Build an empty map with default size and using NaN for missing entries. + */ + public OpenIntToDoubleHashMap() { + this(DEFAULT_EXPECTED_SIZE, Double.NaN); + } + + /** + * Build an empty map with default size + * @param missingEntries value to return when a missing entry is fetched + */ + public OpenIntToDoubleHashMap(final double missingEntries) { + this(DEFAULT_EXPECTED_SIZE, missingEntries); + } + + /** + * Build an empty map with specified size and using NaN for missing entries. + * @param expectedSize expected number of elements in the map + */ + public OpenIntToDoubleHashMap(final int expectedSize) { + this(expectedSize, Double.NaN); + } + + /** + * Build an empty map with specified size. + * @param expectedSize expected number of elements in the map + * @param missingEntries value to return when a missing entry is fetched + */ + public OpenIntToDoubleHashMap(final int expectedSize, + final double missingEntries) { + final int capacity = computeCapacity(expectedSize); + keys = new int[capacity]; + values = new double[capacity]; + states = new byte[capacity]; + this.missingEntries = missingEntries; + mask = capacity - 1; + } + + /** + * Copy constructor. + * @param source map to copy + */ + public OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) { + final int length = source.keys.length; + keys = new int[length]; + System.arraycopy(source.keys, 0, keys, 0, length); + values = new double[length]; + System.arraycopy(source.values, 0, values, 0, length); + states = new byte[length]; + System.arraycopy(source.states, 0, states, 0, length); + missingEntries = source.missingEntries; + size = source.size; + mask = source.mask; + count = source.count; + } + + /** + * Compute the capacity needed for a given size. + * @param expectedSize expected size of the map + * @return capacity to use for the specified size + */ + private static int computeCapacity(final int expectedSize) { + if (expectedSize == 0) { + return 1; + } + final int capacity = (int) FastMath.ceil(expectedSize / LOAD_FACTOR); + final int powerOfTwo = Integer.highestOneBit(capacity); + if (powerOfTwo == capacity) { + return capacity; + } + return nextPowerOfTwo(capacity); + } + + /** + * Find the smallest power of two greater than the input value + * @param i input value + * @return smallest power of two greater than the input value + */ + private static int nextPowerOfTwo(final int i) { + return Integer.highestOneBit(i) << 1; + } + + /** + * Get the stored value associated with the given key + * @param key key associated with the data + * @return data associated with the key + */ + public double get(final int key) { + + final int hash = hashOf(key); + int index = hash & mask; + if (containsKey(key, index)) { + return values[index]; + } + + if (states[index] == FREE) { + return missingEntries; + } + + int j = index; + for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { + j = probe(perturb, j); + index = j & mask; + if (containsKey(key, index)) { + return values[index]; + } + } + + return missingEntries; + + } + + /** + * Check if a value is associated with a key. + * @param key key to check + * @return true if a value is associated with key + */ + public boolean containsKey(final int key) { + + final int hash = hashOf(key); + int index = hash & mask; + if (containsKey(key, index)) { + return true; + } + + if (states[index] == FREE) { + return false; + } + + int j = index; + for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { + j = probe(perturb, j); + index = j & mask; + if (containsKey(key, index)) { + return true; + } + } + + return false; + + } + + /** + * Get an iterator over map elements. + *

              The specialized iterators returned are fail-fast: they throw a + * ConcurrentModificationException when they detect the map + * has been modified during iteration.

              + * @return iterator over the map elements + */ + public Iterator iterator() { + return new Iterator(); + } + + /** + * Perturb the hash for starting probing. + * @param hash initial hash + * @return perturbed hash + */ + private static int perturb(final int hash) { + return hash & 0x7fffffff; + } + + /** + * Find the index at which a key should be inserted + * @param key key to lookup + * @return index at which key should be inserted + */ + private int findInsertionIndex(final int key) { + return findInsertionIndex(keys, states, key, mask); + } + + /** + * Find the index at which a key should be inserted + * @param keys keys table + * @param states states table + * @param key key to lookup + * @param mask bit mask for hash values + * @return index at which key should be inserted + */ + private static int findInsertionIndex(final int[] keys, final byte[] states, + final int key, final int mask) { + final int hash = hashOf(key); + int index = hash & mask; + if (states[index] == FREE) { + return index; + } else if (states[index] == FULL && keys[index] == key) { + return changeIndexSign(index); + } + + int perturb = perturb(hash); + int j = index; + if (states[index] == FULL) { + while (true) { + j = probe(perturb, j); + index = j & mask; + perturb >>= PERTURB_SHIFT; + + if (states[index] != FULL || keys[index] == key) { + break; + } + } + } + + if (states[index] == FREE) { + return index; + } else if (states[index] == FULL) { + // due to the loop exit condition, + // if (states[index] == FULL) then keys[index] == key + return changeIndexSign(index); + } + + final int firstRemoved = index; + while (true) { + j = probe(perturb, j); + index = j & mask; + + if (states[index] == FREE) { + return firstRemoved; + } else if (states[index] == FULL && keys[index] == key) { + return changeIndexSign(index); + } + + perturb >>= PERTURB_SHIFT; + + } + + } + + /** + * Compute next probe for collision resolution + * @param perturb perturbed hash + * @param j previous probe + * @return next probe + */ + private static int probe(final int perturb, final int j) { + return (j << 2) + j + perturb + 1; + } + + /** + * Change the index sign + * @param index initial index + * @return changed index + */ + private static int changeIndexSign(final int index) { + return -index - 1; + } + + /** + * Get the number of elements stored in the map. + * @return number of elements stored in the map + */ + public int size() { + return size; + } + + + /** + * Remove the value associated with a key. + * @param key key to which the value is associated + * @return removed value + */ + public double remove(final int key) { + + final int hash = hashOf(key); + int index = hash & mask; + if (containsKey(key, index)) { + return doRemove(index); + } + + if (states[index] == FREE) { + return missingEntries; + } + + int j = index; + for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { + j = probe(perturb, j); + index = j & mask; + if (containsKey(key, index)) { + return doRemove(index); + } + } + + return missingEntries; + + } + + /** + * Check if the tables contain an element associated with specified key + * at specified index. + * @param key key to check + * @param index index to check + * @return true if an element is associated with key at index + */ + private boolean containsKey(final int key, final int index) { + return (key != 0 || states[index] == FULL) && keys[index] == key; + } + + /** + * Remove an element at specified index. + * @param index index of the element to remove + * @return removed value + */ + private double doRemove(int index) { + keys[index] = 0; + states[index] = REMOVED; + final double previous = values[index]; + values[index] = missingEntries; + --size; + ++count; + return previous; + } + + /** + * Put a value associated with a key in the map. + * @param key key to which value is associated + * @param value value to put in the map + * @return previous value associated with the key + */ + public double put(final int key, final double value) { + int index = findInsertionIndex(key); + double previous = missingEntries; + boolean newMapping = true; + if (index < 0) { + index = changeIndexSign(index); + previous = values[index]; + newMapping = false; + } + keys[index] = key; + states[index] = FULL; + values[index] = value; + if (newMapping) { + ++size; + if (shouldGrowTable()) { + growTable(); + } + ++count; + } + return previous; + + } + + /** + * Grow the tables. + */ + private void growTable() { + + final int oldLength = states.length; + final int[] oldKeys = keys; + final double[] oldValues = values; + final byte[] oldStates = states; + + final int newLength = RESIZE_MULTIPLIER * oldLength; + final int[] newKeys = new int[newLength]; + final double[] newValues = new double[newLength]; + final byte[] newStates = new byte[newLength]; + final int newMask = newLength - 1; + for (int i = 0; i < oldLength; ++i) { + if (oldStates[i] == FULL) { + final int key = oldKeys[i]; + final int index = findInsertionIndex(newKeys, newStates, key, newMask); + newKeys[index] = key; + newValues[index] = oldValues[i]; + newStates[index] = FULL; + } + } + + mask = newMask; + keys = newKeys; + values = newValues; + states = newStates; + + } + + /** + * Check if tables should grow due to increased size. + * @return true if tables should grow + */ + private boolean shouldGrowTable() { + return size > (mask + 1) * LOAD_FACTOR; + } + + /** + * Compute the hash value of a key + * @param key key to hash + * @return hash value of the key + */ + private static int hashOf(final int key) { + final int h = key ^ ((key >>> 20) ^ (key >>> 12)); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + + /** Iterator class for the map. */ + public class Iterator { + + /** Reference modification count. */ + private final int referenceCount; + + /** Index of current element. */ + private int current; + + /** Index of next element. */ + private int next; + + /** + * Simple constructor. + */ + private Iterator() { + + // preserve the modification count of the map to detect concurrent modifications later + referenceCount = count; + + // initialize current index + next = -1; + try { + advance(); + } catch (NoSuchElementException nsee) { // NOPMD + // ignored + } + + } + + /** + * Check if there is a next element in the map. + * @return true if there is a next element + */ + public boolean hasNext() { + return next >= 0; + } + + /** + * Get the key of current entry. + * @return key of current entry + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public int key() + throws ConcurrentModificationException, NoSuchElementException { + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + if (current < 0) { + throw new NoSuchElementException(); + } + return keys[current]; + } + + /** + * Get the value of current entry. + * @return value of current entry + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public double value() + throws ConcurrentModificationException, NoSuchElementException { + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + if (current < 0) { + throw new NoSuchElementException(); + } + return values[current]; + } + + /** + * Advance iterator one step further. + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public void advance() + throws ConcurrentModificationException, NoSuchElementException { + + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + + // advance on step + current = next; + + // prepare next step + try { + while (states[++next] != FULL) { // NOPMD + // nothing to do + } + } catch (ArrayIndexOutOfBoundsException e) { + next = -2; + if (current < 0) { + throw new NoSuchElementException(); + } + } + + } + + } + + /** + * Read a serialized object. + * @param stream input stream + * @throws IOException if object cannot be read + * @throws ClassNotFoundException if the class corresponding + * to the serialized object cannot be found + */ + private void readObject(final ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + count = 0; + } + + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToFieldHashMap.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToFieldHashMap.java new file mode 100644 index 000000000..468d28705 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/OpenIntToFieldHashMap.java @@ -0,0 +1,617 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.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 com.fr.third.org.apache.commons.math3.Field; +import com.fr.third.org.apache.commons.math3.FieldElement; + +/** + * Open addressed map from int to FieldElement. + *

              This class provides a dedicated map from integers to FieldElements with a + * much smaller memory overhead than standard java.util.Map.

              + *

              This class is not synchronized. The specialized iterators returned by + * {@link #iterator()} are fail-fast: they throw a + * ConcurrentModificationException when they detect the map has been + * modified during iteration.

              + * @param the type of the field elements + * @since 2.0 + */ +public class OpenIntToFieldHashMap> 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. + *

              This must be a power of two for bit mask to work properly.

              + */ + private static final int DEFAULT_EXPECTED_SIZE = 16; + + /** Multiplier for size growth when map fills up. + *

              This must be a power of two for bit mask to work properly.

              + */ + private static final int RESIZE_MULTIPLIER = 2; + + /** Number of bits to perturb the index when probing for collision resolution. */ + private static final int PERTURB_SHIFT = 5; + + /** Field to which the elements belong. */ + private final Field 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 Fieldfield) { + 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 Fieldfield, 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 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 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 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. + *

              The specialized iterators returned are fail-fast: they throw a + * ConcurrentModificationException when they detect the map + * has been modified during iteration.

              + * @return iterator over the map elements + */ + public Iterator iterator() { + return new Iterator(); + } + + /** + * Perturb the hash for starting probing. + * @param hash initial hash + * @return perturbed hash + */ + private static int perturb(final int hash) { + return hash & 0x7fffffff; + } + + /** + * Find the index at which a key should be inserted + * @param key key to lookup + * @return index at which key should be inserted + */ + private int findInsertionIndex(final int key) { + return findInsertionIndex(keys, states, key, mask); + } + + /** + * Find the index at which a key should be inserted + * @param keys keys table + * @param states states table + * @param key key to lookup + * @param mask bit mask for hash values + * @return index at which key should be inserted + */ + private static int findInsertionIndex(final int[] keys, final byte[] states, + final int key, final int mask) { + final int hash = hashOf(key); + int index = hash & mask; + if (states[index] == FREE) { + return index; + } else if (states[index] == FULL && keys[index] == key) { + return changeIndexSign(index); + } + + int perturb = perturb(hash); + int j = index; + if (states[index] == FULL) { + while (true) { + j = probe(perturb, j); + index = j & mask; + perturb >>= PERTURB_SHIFT; + + if (states[index] != FULL || keys[index] == key) { + break; + } + } + } + + if (states[index] == FREE) { + return index; + } else if (states[index] == FULL) { + // due to the loop exit condition, + // if (states[index] == FULL) then keys[index] == key + return changeIndexSign(index); + } + + final int firstRemoved = index; + while (true) { + j = probe(perturb, j); + index = j & mask; + + if (states[index] == FREE) { + return firstRemoved; + } else if (states[index] == FULL && keys[index] == key) { + return changeIndexSign(index); + } + + perturb >>= PERTURB_SHIFT; + + } + + } + + /** + * Compute next probe for collision resolution + * @param perturb perturbed hash + * @param j previous probe + * @return next probe + */ + private static int probe(final int perturb, final int j) { + return (j << 2) + j + perturb + 1; + } + + /** + * Change the index sign + * @param index initial index + * @return changed index + */ + private static int changeIndexSign(final int index) { + return -index - 1; + } + + /** + * Get the number of elements stored in the map. + * @return number of elements stored in the map + */ + public int size() { + return size; + } + + + /** + * Remove the value associated with a key. + * @param key key to which the value is associated + * @return removed value + */ + public T remove(final int key) { + + final int hash = hashOf(key); + int index = hash & mask; + if (containsKey(key, index)) { + return doRemove(index); + } + + if (states[index] == FREE) { + return missingEntries; + } + + int j = index; + for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { + j = probe(perturb, j); + index = j & mask; + if (containsKey(key, index)) { + return doRemove(index); + } + } + + return missingEntries; + + } + + /** + * Check if the tables contain an element associated with specified key + * at specified index. + * @param key key to check + * @param index index to check + * @return true if an element is associated with key at index + */ + private boolean containsKey(final int key, final int index) { + return (key != 0 || states[index] == FULL) && keys[index] == key; + } + + /** + * Remove an element at specified index. + * @param index index of the element to remove + * @return removed value + */ + private T doRemove(int index) { + keys[index] = 0; + states[index] = REMOVED; + final T previous = values[index]; + values[index] = missingEntries; + --size; + ++count; + return previous; + } + + /** + * Put a value associated with a key in the map. + * @param key key to which value is associated + * @param value value to put in the map + * @return previous value associated with the key + */ + public T put(final int key, final T value) { + int index = findInsertionIndex(key); + T previous = missingEntries; + boolean newMapping = true; + if (index < 0) { + index = changeIndexSign(index); + previous = values[index]; + newMapping = false; + } + keys[index] = key; + states[index] = FULL; + values[index] = value; + if (newMapping) { + ++size; + if (shouldGrowTable()) { + growTable(); + } + ++count; + } + return previous; + + } + + /** + * Grow the tables. + */ + private void growTable() { + + final int oldLength = states.length; + final int[] oldKeys = keys; + final T[] oldValues = values; + final byte[] oldStates = states; + + final int newLength = RESIZE_MULTIPLIER * oldLength; + final int[] newKeys = new int[newLength]; + final T[] newValues = buildArray(newLength); + final byte[] newStates = new byte[newLength]; + final int newMask = newLength - 1; + for (int i = 0; i < oldLength; ++i) { + if (oldStates[i] == FULL) { + final int key = oldKeys[i]; + final int index = findInsertionIndex(newKeys, newStates, key, newMask); + newKeys[index] = key; + newValues[index] = oldValues[i]; + newStates[index] = FULL; + } + } + + mask = newMask; + keys = newKeys; + values = newValues; + states = newStates; + + } + + /** + * Check if tables should grow due to increased size. + * @return true if tables should grow + */ + private boolean shouldGrowTable() { + return size > (mask + 1) * LOAD_FACTOR; + } + + /** + * Compute the hash value of a key + * @param key key to hash + * @return hash value of the key + */ + private static int hashOf(final int key) { + final int h = key ^ ((key >>> 20) ^ (key >>> 12)); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + + /** Iterator class for the map. */ + public class Iterator { + + /** Reference modification count. */ + private final int referenceCount; + + /** Index of current element. */ + private int current; + + /** Index of next element. */ + private int next; + + /** + * Simple constructor. + */ + private Iterator() { + + // preserve the modification count of the map to detect concurrent modifications later + referenceCount = count; + + // initialize current index + next = -1; + try { + advance(); + } catch (NoSuchElementException nsee) { // NOPMD + // ignored + } + + } + + /** + * Check if there is a next element in the map. + * @return true if there is a next element + */ + public boolean hasNext() { + return next >= 0; + } + + /** + * Get the key of current entry. + * @return key of current entry + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public int key() + throws ConcurrentModificationException, NoSuchElementException { + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + if (current < 0) { + throw new NoSuchElementException(); + } + return keys[current]; + } + + /** + * Get the value of current entry. + * @return value of current entry + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public T value() + throws ConcurrentModificationException, NoSuchElementException { + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + if (current < 0) { + throw new NoSuchElementException(); + } + return values[current]; + } + + /** + * Advance iterator one step further. + * @exception ConcurrentModificationException if the map is modified during iteration + * @exception NoSuchElementException if there is no element left in the map + */ + public void advance() + throws ConcurrentModificationException, NoSuchElementException { + + if (referenceCount != count) { + throw new ConcurrentModificationException(); + } + + // advance on step + current = next; + + // prepare next step + try { + while (states[++next] != FULL) { // NOPMD + // nothing to do + } + } catch (ArrayIndexOutOfBoundsException e) { + next = -2; + if (current < 0) { + throw new NoSuchElementException(); + } + } + + } + + } + + /** + * Read a serialized object. + * @param stream input stream + * @throws IOException if object cannot be read + * @throws ClassNotFoundException if the class corresponding + * to the serialized object cannot be found + */ + private void readObject(final ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + count = 0; + } + + /** Build an array of elements. + * @param length size of the array to build + * @return a new array + */ + @SuppressWarnings("unchecked") // field is of type T + private T[] buildArray(final int length) { + return (T[]) Array.newInstance(field.getRuntimeClass(), length); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Pair.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Pair.java new file mode 100644 index 000000000..2e91b7be6 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Pair.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 com.fr.third.org.apache.commons.math3.util; + +/** + * Generic pair. + *
              + * Although the instances of this class are immutable, it is impossible + * to ensure that the references passed to the constructor will not be + * modified by the caller. + * + * @param Key type. + * @param Value type. + * + * @since 3.0 + */ +public class Pair { + /** Key. */ + private final K key; + /** Value. */ + private final V value; + + /** + * Create an entry representing a mapping from the specified key to the + * specified value. + * + * @param k Key (first element of the pair). + * @param v Value (second element of the pair). + */ + public Pair(K k, V v) { + key = k; + value = v; + } + + /** + * Create an entry representing the same mapping as the specified entry. + * + * @param entry Entry to copy. + */ + public Pair(Pair entry) { + this(entry.getKey(), entry.getValue()); + } + + /** + * Get the key. + * + * @return the key (first element of the pair). + */ + public K getKey() { + return key; + } + + /** + * Get the value. + * + * @return the value (second element of the pair). + */ + public V getValue() { + return value; + } + + /** + * Get the first element of the pair. + * + * @return the first element of the pair. + * @since 3.1 + */ + public K getFirst() { + return key; + } + + /** + * Get the second element of the pair. + * + * @return the second element of the pair. + * @since 3.1 + */ + public V getSecond() { + return value; + } + + /** + * Compare the specified object with this entry for equality. + * + * @param o Object. + * @return {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Pair)) { + return false; + } else { + Pair oP = (Pair) o; + return (key == null ? + oP.key == null : + key.equals(oP.key)) && + (value == null ? + oP.value == null : + value.equals(oP.value)); + } + } + + /** + * Compute a hash code. + * + * @return the hash code value. + */ + @Override + public int hashCode() { + int result = key == null ? 0 : key.hashCode(); + + final int h = value == null ? 0 : value.hashCode(); + result = 37 * result + h ^ (h >>> 16); + + return result; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "[" + getKey() + ", " + getValue() + "]"; + } + + /** + * Convenience factory method that calls the + * {@link #Pair(Object, Object) constructor}. + * + * @param the key type + * @param the value type + * @param k First element of the pair. + * @param v Second element of the pair. + * @return a new {@code Pair} containing {@code k} and {@code v}. + * @since 3.3 + */ + public static Pair create(K k, V v) { + return new Pair(k, v); + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/PivotingStrategyInterface.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/PivotingStrategyInterface.java new file mode 100644 index 000000000..f3ddc1500 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/PivotingStrategyInterface.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 com.fr.third.org.apache.commons.math3.util; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + + +/** + * A strategy to pick a pivoting index of an array for doing partitioning. + * @see MedianOf3PivotingStrategy + * @see RandomPivotingStrategy + * @see CentralPivotingStrategy + * @since 3.4 + */ +public interface PivotingStrategyInterface { + + /** + * Find pivot index of the array so that partition and Kth + * element selection can be made + * @param work data array + * @param begin index of the first element of the slice + * @param end index after the last element of the slice + * @return the index of the pivot element chosen between the + * first and the last element of the array slice + * @throws MathIllegalArgumentException when indices exceeds range + */ + int pivotIndex(double[] work, int begin, int end) + throws MathIllegalArgumentException; + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Precision.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Precision.java new file mode 100644 index 000000000..25ee26b9f --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/Precision.java @@ -0,0 +1,608 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.math3.util; + +import java.math.BigDecimal; + +import com.fr.third.org.apache.commons.math3.exception.MathArithmeticException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Utilities for comparing numbers. + * + * @since 3.0 + */ +public class Precision { + /** + *

              + * Largest double-precision floating-point number such that + * {@code 1 + EPSILON} is numerically equal to 1. This value is an upper + * bound on the relative error due to rounding real numbers to double + * precision floating-point numbers. + *

              + *

              + * In IEEE 754 arithmetic, this is 2-53. + *

              + * + * @see Machine epsilon + */ + public static final double EPSILON; + + /** + * Safe minimum, such that {@code 1 / SAFE_MIN} does not overflow. + *
              + * In IEEE 754 arithmetic, this is also the smallest normalized + * number 2-1022. + */ + public static final double SAFE_MIN; + + /** Exponent offset in IEEE754 representation. */ + private static final long EXPONENT_OFFSET = 1023l; + + /** Offset to order signed double numbers lexicographically. */ + private static final long SGN_MASK = 0x8000000000000000L; + /** Offset to order signed double numbers lexicographically. */ + private static final int SGN_MASK_FLOAT = 0x80000000; + /** Positive zero. */ + private static final double POSITIVE_ZERO = 0d; + /** Positive zero bits. */ + private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0); + /** Negative zero bits. */ + private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0); + /** Positive zero bits. */ + private static final int POSITIVE_ZERO_FLOAT_BITS = Float.floatToRawIntBits(+0.0f); + /** Negative zero bits. */ + private static final int NEGATIVE_ZERO_FLOAT_BITS = Float.floatToRawIntBits(-0.0f); + + static { + /* + * This was previously expressed as = 0x1.0p-53; + * However, OpenJDK (Sparc Solaris) cannot handle such small + * constants: MATH-721 + */ + EPSILON = Double.longBitsToDouble((EXPONENT_OFFSET - 53l) << 52); + + /* + * This was previously expressed as = 0x1.0p-1022; + * However, OpenJDK (Sparc Solaris) cannot handle such small + * constants: MATH-721 + */ + SAFE_MIN = Double.longBitsToDouble((EXPONENT_OFFSET - 1022l) << 52); + } + + /** + * Private constructor. + */ + private Precision() {} + + /** + * Compares two numbers given some amount of allowed error. + * + * @param x the first number + * @param y the second number + * @param eps the amount of error to allow when checking for equality + * @return
              • 0 if {@link #equals(double, double, double) equals(x, y, eps)}
              • + *
              • < 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && x < y
              • + *
              • > 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && x > y or + * either argument is NaN
              + */ + public static int compareTo(double x, double y, double eps) { + if (equals(x, y, eps)) { + return 0; + } else if (x < y) { + return -1; + } + return 1; + } + + /** + * Compares two numbers given some amount of allowed error. + * Two float numbers are considered equal if there are {@code (maxUlps - 1)} + * (or fewer) floating point numbers between them, i.e. two adjacent floating + * point numbers are considered equal. + * Adapted from + * Bruce Dawson. Returns {@code false} if either of the arguments is NaN. + * + * @param x first value + * @param y second value + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between {@code x} and {@code y}. + * @return
              • 0 if {@link #equals(double, double, int) equals(x, y, maxUlps)}
              • + *
              • < 0 if !{@link #equals(double, double, int) equals(x, y, maxUlps)} && x < y
              • + *
              • > 0 if !{@link #equals(double, double, int) equals(x, y, maxUlps)} && x > y + * or either argument is NaN
              + */ + public static int compareTo(final double x, final double y, final int maxUlps) { + if (equals(x, y, maxUlps)) { + return 0; + } else if (x < y) { + return -1; + } + return 1; + } + + /** + * Returns true iff they are equal as defined by + * {@link #equals(float,float,int) equals(x, y, 1)}. + * + * @param x first value + * @param y second value + * @return {@code true} if the values are equal. + */ + public static boolean equals(float x, float y) { + return equals(x, y, 1); + } + + /** + * Returns true if both arguments are NaN or they are + * equal as defined by {@link #equals(float,float) equals(x, y, 1)}. + * + * @param x first value + * @param y second value + * @return {@code true} if the values are equal or both are NaN. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(float x, float y) { + return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, 1); + } + + /** + * Returns true if the arguments are equal or within the range of allowed + * error (inclusive). Returns {@code false} if either of the arguments + * is NaN. + * + * @param x first value + * @param y second value + * @param eps the amount of absolute error to allow. + * @return {@code true} if the values are equal or within range of each other. + * @since 2.2 + */ + public static boolean equals(float x, float y, float eps) { + return equals(x, y, 1) || FastMath.abs(y - x) <= eps; + } + + /** + * Returns true if the arguments are both NaN, are equal, or are within the range + * of allowed error (inclusive). + * + * @param x first value + * @param y second value + * @param eps the amount of absolute error to allow. + * @return {@code true} if the values are equal or within range of each other, + * or both are NaN. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(float x, float y, float eps) { + return equalsIncludingNaN(x, y) || (FastMath.abs(y - x) <= eps); + } + + /** + * Returns true if the arguments are equal or within the range of allowed + * error (inclusive). + * Two float numbers are considered equal if there are {@code (maxUlps - 1)} + * (or fewer) floating point numbers between them, i.e. two adjacent floating + * point numbers are considered equal. + * Adapted from + * Bruce Dawson. Returns {@code false} if either of the arguments is NaN. + * + * @param x first value + * @param y second value + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between {@code x} and {@code y}. + * @return {@code true} if there are fewer than {@code maxUlps} floating + * point values between {@code x} and {@code y}. + * @since 2.2 + */ + public static boolean equals(final float x, final float y, final int maxUlps) { + + final int xInt = Float.floatToRawIntBits(x); + final int yInt = Float.floatToRawIntBits(y); + + final boolean isEqual; + if (((xInt ^ yInt) & SGN_MASK_FLOAT) == 0) { + // number have same sign, there is no risk of overflow + isEqual = FastMath.abs(xInt - yInt) <= maxUlps; + } else { + // number have opposite signs, take care of overflow + final int deltaPlus; + final int deltaMinus; + if (xInt < yInt) { + deltaPlus = yInt - POSITIVE_ZERO_FLOAT_BITS; + deltaMinus = xInt - NEGATIVE_ZERO_FLOAT_BITS; + } else { + deltaPlus = xInt - POSITIVE_ZERO_FLOAT_BITS; + deltaMinus = yInt - NEGATIVE_ZERO_FLOAT_BITS; + } + + if (deltaPlus > maxUlps) { + isEqual = false; + } else { + isEqual = deltaMinus <= (maxUlps - deltaPlus); + } + + } + + return isEqual && !Float.isNaN(x) && !Float.isNaN(y); + + } + + /** + * Returns true if the arguments are both NaN or if they are equal as defined + * by {@link #equals(float,float,int) equals(x, y, maxUlps)}. + * + * @param x first value + * @param y second value + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between {@code x} and {@code y}. + * @return {@code true} if both arguments are NaN or if there are less than + * {@code maxUlps} floating point values between {@code x} and {@code y}. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(float x, float y, int maxUlps) { + return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, maxUlps); + } + + /** + * Returns true iff they are equal as defined by + * {@link #equals(double,double,int) equals(x, y, 1)}. + * + * @param x first value + * @param y second value + * @return {@code true} if the values are equal. + */ + public static boolean equals(double x, double y) { + return equals(x, y, 1); + } + + /** + * Returns true if the arguments are both NaN or they are + * equal as defined by {@link #equals(double,double) equals(x, y, 1)}. + * + * @param x first value + * @param y second value + * @return {@code true} if the values are equal or both are NaN. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(double x, double y) { + return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, 1); + } + + /** + * Returns {@code true} if there is no double value strictly between the + * arguments or the difference between them is within the range of allowed + * error (inclusive). Returns {@code false} if either of the arguments + * is NaN. + * + * @param x First value. + * @param y Second value. + * @param eps Amount of allowed absolute error. + * @return {@code true} if the values are two adjacent floating point + * numbers or they are within range of each other. + */ + public static boolean equals(double x, double y, double eps) { + return equals(x, y, 1) || FastMath.abs(y - x) <= eps; + } + + /** + * Returns {@code true} if there is no double value strictly between the + * arguments or the relative difference between them is less than or equal + * to the given tolerance. Returns {@code false} if either of the arguments + * is NaN. + * + * @param x First value. + * @param y Second value. + * @param eps Amount of allowed relative error. + * @return {@code true} if the values are two adjacent floating point + * numbers or they are within range of each other. + * @since 3.1 + */ + public static boolean equalsWithRelativeTolerance(double x, double y, double eps) { + if (equals(x, y, 1)) { + return true; + } + + final double absoluteMax = FastMath.max(FastMath.abs(x), FastMath.abs(y)); + final double relativeDifference = FastMath.abs((x - y) / absoluteMax); + + return relativeDifference <= eps; + } + + /** + * Returns true if the arguments are both NaN, are equal or are within the range + * of allowed error (inclusive). + * + * @param x first value + * @param y second value + * @param eps the amount of absolute error to allow. + * @return {@code true} if the values are equal or within range of each other, + * or both are NaN. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(double x, double y, double eps) { + return equalsIncludingNaN(x, y) || (FastMath.abs(y - x) <= eps); + } + + /** + * Returns true if the arguments are equal or within the range of allowed + * error (inclusive). + *

              + * 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 + * Bruce Dawson. Returns {@code false} if either of the arguments is NaN. + *

              + * + * @param x first value + * @param y second value + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between {@code x} and {@code y}. + * @return {@code true} if there are fewer than {@code maxUlps} floating + * point values between {@code x} and {@code y}. + */ + public static boolean equals(final double x, final double y, final int maxUlps) { + + final long xInt = Double.doubleToRawLongBits(x); + final long yInt = Double.doubleToRawLongBits(y); + + final boolean isEqual; + if (((xInt ^ yInt) & SGN_MASK) == 0l) { + // number have same sign, there is no risk of overflow + isEqual = FastMath.abs(xInt - yInt) <= maxUlps; + } else { + // number have opposite signs, take care of overflow + final long deltaPlus; + final long deltaMinus; + if (xInt < yInt) { + deltaPlus = yInt - POSITIVE_ZERO_DOUBLE_BITS; + deltaMinus = xInt - NEGATIVE_ZERO_DOUBLE_BITS; + } else { + deltaPlus = xInt - POSITIVE_ZERO_DOUBLE_BITS; + deltaMinus = yInt - NEGATIVE_ZERO_DOUBLE_BITS; + } + + if (deltaPlus > maxUlps) { + isEqual = false; + } else { + isEqual = deltaMinus <= (maxUlps - deltaPlus); + } + + } + + return isEqual && !Double.isNaN(x) && !Double.isNaN(y); + + } + + /** + * Returns true if both arguments are NaN or if they are equal as defined + * by {@link #equals(double,double,int) equals(x, y, maxUlps)}. + * + * @param x first value + * @param y second value + * @param maxUlps {@code (maxUlps - 1)} is the number of floating point + * values between {@code x} and {@code y}. + * @return {@code true} if both arguments are NaN or if there are less than + * {@code maxUlps} floating point values between {@code x} and {@code y}. + * @since 2.2 + */ + public static boolean equalsIncludingNaN(double x, double y, int maxUlps) { + return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, maxUlps); + } + + /** + * Rounds the given value to the specified number of decimal places. + * The value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method. + * + * @param x Value to round. + * @param scale Number of digits to the right of the decimal point. + * @return the rounded value. + * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0) + */ + public static double round(double x, int scale) { + return round(x, scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * Rounds the given value to the specified number of decimal places. + * The value is rounded using the given method which is any method defined + * in {@link BigDecimal}. + * If {@code x} is infinite or {@code NaN}, then the value of {@code x} is + * returned unchanged, regardless of the other parameters. + * + * @param x Value to round. + * @param scale Number of digits to the right of the decimal point. + * @param roundingMethod Rounding method as defined in {@link BigDecimal}. + * @return the rounded value. + * @throws ArithmeticException if {@code roundingMethod == ROUND_UNNECESSARY} + * and the specified scaling operation would require rounding. + * @throws IllegalArgumentException if {@code roundingMethod} does not + * represent a valid rounding mode. + * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0) + */ + public static double round(double x, int scale, int roundingMethod) { + try { + final double rounded = (new BigDecimal(Double.toString(x)) + .setScale(scale, roundingMethod)) + .doubleValue(); + // MATH-1089: negative values rounded to zero should result in negative zero + return rounded == POSITIVE_ZERO ? POSITIVE_ZERO * x : rounded; + } catch (NumberFormatException ex) { + if (Double.isInfinite(x)) { + return x; + } else { + return Double.NaN; + } + } + } + + /** + * Rounds the given value to the specified number of decimal places. + * The value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method. + * + * @param x Value to round. + * @param scale Number of digits to the right of the decimal point. + * @return the rounded value. + * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0) + */ + public static float round(float x, int scale) { + return round(x, scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * Rounds the given value to the specified number of decimal places. + * The value is rounded using the given method which is any method defined + * in {@link BigDecimal}. + * + * @param x Value to round. + * @param scale Number of digits to the right of the decimal point. + * @param roundingMethod Rounding method as defined in {@link BigDecimal}. + * @return the rounded value. + * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0) + * @throws MathArithmeticException if an exact operation is required but result is not exact + * @throws MathIllegalArgumentException if {@code roundingMethod} is not a valid rounding method. + */ + public static float round(float x, int scale, int roundingMethod) + throws MathArithmeticException, MathIllegalArgumentException { + final float sign = FastMath.copySign(1f, x); + final float factor = (float) FastMath.pow(10.0f, scale) * sign; + return (float) roundUnscaled(x * factor, sign, roundingMethod) / factor; + } + + /** + * Rounds the given non-negative value to the "nearest" integer. Nearest is + * determined by the rounding method specified. Rounding methods are defined + * in {@link BigDecimal}. + * + * @param unscaled Value to round. + * @param sign Sign of the original, scaled value. + * @param roundingMethod Rounding method, as defined in {@link BigDecimal}. + * @return the rounded value. + * @throws MathArithmeticException if an exact operation is required but result is not exact + * @throws MathIllegalArgumentException if {@code roundingMethod} is not a valid rounding method. + * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0) + */ + private static double roundUnscaled(double unscaled, + double sign, + int roundingMethod) + throws MathArithmeticException, MathIllegalArgumentException { + switch (roundingMethod) { + case BigDecimal.ROUND_CEILING : + if (sign == -1) { + unscaled = FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY)); + } else { + unscaled = FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY)); + } + break; + case BigDecimal.ROUND_DOWN : + unscaled = FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY)); + break; + case BigDecimal.ROUND_FLOOR : + if (sign == -1) { + unscaled = FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY)); + } else { + unscaled = FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY)); + } + break; + case BigDecimal.ROUND_HALF_DOWN : { + unscaled = FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY); + double fraction = unscaled - FastMath.floor(unscaled); + if (fraction > 0.5) { + unscaled = FastMath.ceil(unscaled); + } else { + unscaled = FastMath.floor(unscaled); + } + break; + } + case BigDecimal.ROUND_HALF_EVEN : { + double fraction = unscaled - FastMath.floor(unscaled); + if (fraction > 0.5) { + unscaled = FastMath.ceil(unscaled); + } else if (fraction < 0.5) { + unscaled = FastMath.floor(unscaled); + } else { + // The following equality test is intentional and needed for rounding purposes + if (FastMath.floor(unscaled) / 2.0 == FastMath.floor(FastMath.floor(unscaled) / 2.0)) { // even + unscaled = FastMath.floor(unscaled); + } else { // odd + unscaled = FastMath.ceil(unscaled); + } + } + break; + } + case BigDecimal.ROUND_HALF_UP : { + unscaled = FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY); + double fraction = unscaled - FastMath.floor(unscaled); + if (fraction >= 0.5) { + unscaled = FastMath.ceil(unscaled); + } else { + unscaled = FastMath.floor(unscaled); + } + break; + } + case BigDecimal.ROUND_UNNECESSARY : + if (unscaled != FastMath.floor(unscaled)) { + throw new MathArithmeticException(); + } + break; + case BigDecimal.ROUND_UP : + // do not round if the discarded fraction is equal to zero + if (unscaled != FastMath.floor(unscaled)) { + unscaled = FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY)); + } + break; + default : + throw new MathIllegalArgumentException(LocalizedFormats.INVALID_ROUNDING_METHOD, + roundingMethod, + "ROUND_CEILING", BigDecimal.ROUND_CEILING, + "ROUND_DOWN", BigDecimal.ROUND_DOWN, + "ROUND_FLOOR", BigDecimal.ROUND_FLOOR, + "ROUND_HALF_DOWN", BigDecimal.ROUND_HALF_DOWN, + "ROUND_HALF_EVEN", BigDecimal.ROUND_HALF_EVEN, + "ROUND_HALF_UP", BigDecimal.ROUND_HALF_UP, + "ROUND_UNNECESSARY", BigDecimal.ROUND_UNNECESSARY, + "ROUND_UP", BigDecimal.ROUND_UP); + } + return unscaled; + } + + + /** + * Computes a number {@code delta} close to {@code originalDelta} with + * the property that
              
              +     *   x + delta - x
              +     * 
              + * is exactly machine-representable. + * This is useful when computing numerical derivatives, in order to reduce + * roundoff errors. + * + * @param x Value. + * @param originalDelta Offset value. + * @return a number {@code delta} so that {@code x + delta} and {@code x} + * differ by a representable floating number. + */ + public static double representableDelta(double x, + double originalDelta) { + return x + originalDelta - x; + } +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/RandomPivotingStrategy.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/RandomPivotingStrategy.java new file mode 100644 index 000000000..f2d601663 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/RandomPivotingStrategy.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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.random.RandomGenerator; + + +/** + * A strategy of selecting random index between begin and end indices. + * @since 3.4 + */ +public class RandomPivotingStrategy implements PivotingStrategyInterface, Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140713L; + + /** Random generator to use for selecting pivot. */ + private final RandomGenerator random; + + /** Simple constructor. + * @param random random generator to use for selecting pivot + */ + public RandomPivotingStrategy(final RandomGenerator random) { + this.random = random; + } + + /** + * {@inheritDoc} + * A uniform random pivot selection between begin and end indices + * @return The index corresponding to a random uniformly selected + * value between first and the last indices of the array slice + * @throws MathIllegalArgumentException when indices exceeds range + */ + public int pivotIndex(final double[] work, final int begin, final int end) + throws MathIllegalArgumentException { + MathArrays.verifyValues(work, begin, end-begin); + return begin + random.nextInt(end - begin - 1); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ResizableDoubleArray.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ResizableDoubleArray.java new file mode 100644 index 000000000..d87d1f786 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/ResizableDoubleArray.java @@ -0,0 +1,1210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; +import com.fr.third.org.apache.commons.math3.exception.MathIllegalStateException; +import com.fr.third.org.apache.commons.math3.exception.MathInternalError; +import com.fr.third.org.apache.commons.math3.exception.NotStrictlyPositiveException; +import com.fr.third.org.apache.commons.math3.exception.NullArgumentException; +import com.fr.third.org.apache.commons.math3.exception.NumberIsTooSmallException; +import com.fr.third.org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + *

              + * A variable length {@link DoubleArray} implementation that automatically + * handles expanding and contracting its internal storage array as elements + * are added and removed. + *

              + *

              Important note: Usage should not assume that this class is thread-safe + * even though some of the methods are {@code synchronized}. + * This qualifier will be dropped in the next major release (4.0).

              + *

              + * The internal storage array starts with capacity determined by the + * {@code initialCapacity} property, which can be set by the constructor. + * The default initial capacity is 16. Adding elements using + * {@link #addElement(double)} appends elements to the end of the array. + * When there are no open entries at the end of the internal storage array, + * the array is expanded. The size of the expanded array depends on the + * {@code expansionMode} and {@code expansionFactor} properties. + * The {@code expansionMode} determines whether the size of the array is + * multiplied by the {@code expansionFactor} + * ({@link ExpansionMode#MULTIPLICATIVE}) or if the expansion is additive + * ({@link ExpansionMode#ADDITIVE} -- {@code expansionFactor} storage + * locations added). + * The default {@code expansionMode} is {@code MULTIPLICATIVE} and the default + * {@code expansionFactor} is 2. + *

              + *

              + * The {@link #addElementRolling(double)} method adds a new element to the end + * of the internal storage array and adjusts the "usable window" of the + * internal array forward by one position (effectively making what was the + * second element the first, and so on). Repeated activations of this method + * (or activation of {@link #discardFrontElements(int)}) will effectively orphan + * the storage locations at the beginning of the internal storage array. To + * reclaim this storage, each time one of these methods is activated, the size + * of the internal storage array is compared to the number of addressable + * elements (the {@code numElements} property) and if the difference + * is too large, the internal array is contracted to size + * {@code numElements + 1}. The determination of when the internal + * storage array is "too large" depends on the {@code expansionMode} and + * {@code contractionFactor} properties. If the {@code expansionMode} + * is {@code MULTIPLICATIVE}, contraction is triggered when the + * ratio between storage array length and {@code numElements} exceeds + * {@code contractionFactor.} If the {@code expansionMode} + * is {@code ADDITIVE}, the number of excess storage locations + * is compared to {@code contractionFactor}. + *

              + *

              + * To avoid cycles of expansions and contractions, the + * {@code expansionFactor} must not exceed the {@code contractionFactor}. + * Constructors and mutators for both of these properties enforce this + * requirement, throwing a {@code MathIllegalArgumentException} if it is + * violated. + *

              + */ +public class ResizableDoubleArray implements DoubleArray, Serializable { + /** Additive expansion mode. + * @deprecated As of 3.1. Please use {@link ExpansionMode#ADDITIVE} instead. + */ + @Deprecated + public static final int ADDITIVE_MODE = 1; + /** Multiplicative expansion mode. + * @deprecated As of 3.1. Please use {@link ExpansionMode#MULTIPLICATIVE} instead. + */ + @Deprecated + public static final int MULTIPLICATIVE_MODE = 0; + /** Serializable version identifier. */ + private static final long serialVersionUID = -3485529955529426875L; + + /** Default value for initial capacity. */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + /** Default value for array size modifier. */ + private static final double DEFAULT_EXPANSION_FACTOR = 2.0; + /** + * Default value for the difference between {@link #contractionCriterion} + * and {@link #expansionFactor}. + */ + private static final double DEFAULT_CONTRACTION_DELTA = 0.5; + + /** + * The contraction criteria determines when the internal array will be + * contracted to fit the number of elements contained in the element + * array + 1. + */ + private double contractionCriterion = 2.5; + + /** + * The expansion factor of the array. When the array needs to be expanded, + * the new array size will be + * {@code internalArray.length * expansionFactor} + * if {@code expansionMode} is set to MULTIPLICATIVE_MODE, or + * {@code internalArray.length + expansionFactor} if + * {@code expansionMode} is set to ADDITIVE_MODE. + */ + private double expansionFactor = 2.0; + + /** + * Determines whether array expansion by {@code expansionFactor} + * is additive or multiplicative. + */ + private ExpansionMode expansionMode = ExpansionMode.MULTIPLICATIVE; + + /** + * The internal storage array. + */ + private double[] internalArray; + + /** + * The number of addressable elements in the array. Note that this + * has nothing to do with the length of the internal storage array. + */ + private int numElements = 0; + + /** + * The position of the first addressable element in the internal storage + * array. The addressable elements in the array are + * {@code internalArray[startIndex],...,internalArray[startIndex + numElements - 1]}. + */ + private int startIndex = 0; + + /** + * Specification of expansion algorithm. + * @since 3.1 + */ + public enum ExpansionMode { + /** Multiplicative expansion mode. */ + MULTIPLICATIVE, + /** Additive expansion mode. */ + ADDITIVE + } + + /** + * Creates an instance with default properties. + *
                + *
              • {@code initialCapacity = 16}
              • + *
              • {@code expansionMode = MULTIPLICATIVE}
              • + *
              • {@code expansionFactor = 2.0}
              • + *
              • {@code contractionCriterion = 2.5}
              • + *
              + */ + public ResizableDoubleArray() { + this(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Creates an instance with the specified initial capacity. + * Other properties take default values: + *
                + *
              • {@code expansionMode = MULTIPLICATIVE}
              • + *
              • {@code expansionFactor = 2.0}
              • + *
              • {@code contractionCriterion = 2.5}
              • + *
              + * @param initialCapacity Initial size of the internal storage array. + * @throws MathIllegalArgumentException if {@code initialCapacity <= 0}. + */ + public ResizableDoubleArray(int initialCapacity) + throws MathIllegalArgumentException { + this(initialCapacity, DEFAULT_EXPANSION_FACTOR); + } + + /** + * Creates an instance from an existing {@code double[]} with the + * initial capacity and numElements corresponding to the size of + * the supplied {@code double[]} array. + * If the supplied array is null, a new empty array with the default + * initial capacity will be created. + * The input array is copied, not referenced. + * Other properties take default values: + *
                + *
              • {@code initialCapacity = 16}
              • + *
              • {@code expansionMode = MULTIPLICATIVE}
              • + *
              • {@code expansionFactor = 2.0}
              • + *
              • {@code contractionCriterion = 2.5}
              • + *
              + * + * @param initialArray initial array + * @since 2.2 + */ + public ResizableDoubleArray(double[] initialArray) { + this(DEFAULT_INITIAL_CAPACITY, + DEFAULT_EXPANSION_FACTOR, + DEFAULT_CONTRACTION_DELTA + DEFAULT_EXPANSION_FACTOR, + ExpansionMode.MULTIPLICATIVE, + initialArray); + } + + /** + * Creates an instance with the specified initial capacity + * and expansion factor. + * The remaining properties take default values: + *
                + *
              • {@code expansionMode = MULTIPLICATIVE}
              • + *
              • {@code contractionCriterion = 0.5 + expansionFactor}
              • + *
              + *
              + * Throws IllegalArgumentException if the following conditions are + * not met: + *
                + *
              • {@code initialCapacity > 0}
              • + *
              • {@code expansionFactor > 1}
              • + *
              + * + * @param initialCapacity Initial size of the internal storage array. + * @param expansionFactor The array will be expanded based on this + * parameter. + * @throws MathIllegalArgumentException if parameters are not valid. + * @deprecated As of 3.1. Please use + * {@link #ResizableDoubleArray(int,double)} instead. + */ + @Deprecated + public ResizableDoubleArray(int initialCapacity, + float expansionFactor) + throws MathIllegalArgumentException { + this(initialCapacity, + (double) expansionFactor); + } + + /** + * Creates an instance with the specified initial capacity + * and expansion factor. + * The remaining properties take default values: + *
                + *
              • {@code expansionMode = MULTIPLICATIVE}
              • + *
              • {@code contractionCriterion = 0.5 + expansionFactor}
              • + *
              + *
              + * Throws IllegalArgumentException if the following conditions are + * not met: + *
                + *
              • {@code initialCapacity > 0}
              • + *
              • {@code expansionFactor > 1}
              • + *
              + * + * @param initialCapacity Initial size of the internal storage array. + * @param expansionFactor The array will be expanded based on this + * parameter. + * @throws MathIllegalArgumentException if parameters are not valid. + * @since 3.1 + */ + public ResizableDoubleArray(int initialCapacity, + double expansionFactor) + throws MathIllegalArgumentException { + this(initialCapacity, + expansionFactor, + DEFAULT_CONTRACTION_DELTA + expansionFactor); + } + + /** + * Creates an instance with the specified initialCapacity, + * expansionFactor, and contractionCriterion. + * The expansion mode will default to {@code MULTIPLICATIVE}. + *
              + * Throws IllegalArgumentException if the following conditions are + * not met: + *
                + *
              • {@code initialCapacity > 0}
              • + *
              • {@code expansionFactor > 1}
              • + *
              • {@code contractionCriterion >= expansionFactor}
              • + *
              + * + * @param initialCapacity Initial size of the internal storage array.. + * @param expansionFactor The array will be expanded based on this + * parameter. + * @param contractionCriteria Contraction criteria. + * @throws MathIllegalArgumentException if parameters are not valid. + * @deprecated As of 3.1. Please use + * {@link #ResizableDoubleArray(int,double,double)} instead. + */ + @Deprecated + public ResizableDoubleArray(int initialCapacity, + float expansionFactor, + float contractionCriteria) + throws MathIllegalArgumentException { + this(initialCapacity, + (double) expansionFactor, + (double) contractionCriteria); + } + + /** + * Creates an instance with the specified initial capacity, + * expansion factor, and contraction criteria. + * The expansion mode will default to {@code MULTIPLICATIVE}. + *
              + * Throws IllegalArgumentException if the following conditions are + * not met: + *
                + *
              • {@code initialCapacity > 0}
              • + *
              • {@code expansionFactor > 1}
              • + *
              • {@code contractionCriterion >= expansionFactor}
              • + *
              + * + * @param initialCapacity Initial size of the internal storage array.. + * @param expansionFactor The array will be expanded based on this + * parameter. + * @param contractionCriterion Contraction criterion. + * @throws MathIllegalArgumentException if the parameters are not valid. + * @since 3.1 + */ + public ResizableDoubleArray(int initialCapacity, + double expansionFactor, + double contractionCriterion) + throws MathIllegalArgumentException { + this(initialCapacity, + expansionFactor, + contractionCriterion, + ExpansionMode.MULTIPLICATIVE, + null); + } + + /** + *

              + * Create a ResizableArray with the specified properties.

              + *

              + * Throws IllegalArgumentException if the following conditions are + * not met: + *

                + *
              • initialCapacity > 0
              • + *
              • expansionFactor > 1
              • + *
              • contractionFactor >= expansionFactor
              • + *
              • expansionMode in {MULTIPLICATIVE_MODE, ADDITIVE_MODE} + *
              • + *

              + * + * @param initialCapacity the initial size of the internal storage array + * @param expansionFactor the array will be expanded based on this + * parameter + * @param contractionCriteria the contraction Criteria + * @param expansionMode the expansion mode + * @throws MathIllegalArgumentException if parameters are not valid + * @deprecated As of 3.1. Please use + * {@link #ResizableDoubleArray(int,double,double,ExpansionMode,double[])} + * instead. + */ + @Deprecated + public ResizableDoubleArray(int initialCapacity, float expansionFactor, + float contractionCriteria, int expansionMode) throws MathIllegalArgumentException { + this(initialCapacity, + expansionFactor, + contractionCriteria, + expansionMode == ADDITIVE_MODE ? + ExpansionMode.ADDITIVE : + ExpansionMode.MULTIPLICATIVE, + null); + // XXX Just ot retain the expected failure in a unit test. + // With the new "enum", that test will become obsolete. + setExpansionMode(expansionMode); + } + + /** + * Creates an instance with the specified properties. + *
              + * Throws MathIllegalArgumentException if the following conditions are + * not met: + *
                + *
              • {@code initialCapacity > 0}
              • + *
              • {@code expansionFactor > 1}
              • + *
              • {@code contractionCriterion >= expansionFactor}
              • + *
              + * + * @param initialCapacity Initial size of the internal storage array. + * @param expansionFactor The array will be expanded based on this + * parameter. + * @param contractionCriterion Contraction criteria. + * @param expansionMode Expansion mode. + * @param data Initial contents of the array. + * @throws MathIllegalArgumentException if the parameters are not valid. + */ + public ResizableDoubleArray(int initialCapacity, + double expansionFactor, + double contractionCriterion, + ExpansionMode expansionMode, + double ... data) + throws MathIllegalArgumentException { + if (initialCapacity <= 0) { + throw new NotStrictlyPositiveException(LocalizedFormats.INITIAL_CAPACITY_NOT_POSITIVE, + initialCapacity); + } + checkContractExpand(contractionCriterion, expansionFactor); + + this.expansionFactor = expansionFactor; + this.contractionCriterion = contractionCriterion; + this.expansionMode = expansionMode; + internalArray = new double[initialCapacity]; + numElements = 0; + startIndex = 0; + + if (data != null && data.length > 0) { + addElements(data); + } + } + + /** + * Copy constructor. Creates a new ResizableDoubleArray that is a deep, + * fresh copy of the original. Needs to acquire synchronization lock + * on original. Original may not be null; otherwise a {@link NullArgumentException} + * is thrown. + * + * @param original array to copy + * @exception NullArgumentException if original is null + * @since 2.0 + */ + public ResizableDoubleArray(ResizableDoubleArray original) + throws NullArgumentException { + MathUtils.checkNotNull(original); + copy(original, this); + } + + /** + * Adds an element to the end of this expandable array. + * + * @param value Value to be added to end of array. + */ + public synchronized void addElement(double value) { + if (internalArray.length <= startIndex + numElements) { + expand(); + } + internalArray[startIndex + numElements++] = value; + } + + /** + * Adds several element to the end of this expandable array. + * + * @param values Values to be added to end of array. + * @since 2.2 + */ + public synchronized void addElements(double[] values) { + final double[] tempArray = new double[numElements + values.length + 1]; + System.arraycopy(internalArray, startIndex, tempArray, 0, numElements); + System.arraycopy(values, 0, tempArray, numElements, values.length); + internalArray = tempArray; + startIndex = 0; + numElements += values.length; + } + + /** + *

              + * Adds an element to the end of the array and removes the first + * element in the array. Returns the discarded first element. + * The effect is similar to a push operation in a FIFO queue. + *

              + *

              + * Example: If the array contains the elements 1, 2, 3, 4 (in that order) + * and addElementRolling(5) is invoked, the result is an array containing + * the entries 2, 3, 4, 5 and the value returned is 1. + *

              + * + * @param value Value to be added to the array. + * @return the value which has been discarded or "pushed" out of the array + * by this rolling insert. + */ + public synchronized double addElementRolling(double value) { + double discarded = internalArray[startIndex]; + + if ((startIndex + (numElements + 1)) > internalArray.length) { + expand(); + } + // Increment the start index + startIndex += 1; + + // Add the new value + internalArray[startIndex + (numElements - 1)] = value; + + // Check the contraction criterion. + if (shouldContract()) { + contract(); + } + return discarded; + } + + /** + * Substitutes value for the most recently added value. + * Returns the value that has been replaced. If the array is empty (i.e. + * if {@link #numElements} is zero), an IllegalStateException is thrown. + * + * @param value New value to substitute for the most recently added value + * @return the value that has been replaced in the array. + * @throws MathIllegalStateException if the array is empty + * @since 2.0 + */ + public synchronized double substituteMostRecentElement(double value) + throws MathIllegalStateException { + if (numElements < 1) { + throw new MathIllegalStateException( + LocalizedFormats.CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY); + } + + final int substIndex = startIndex + (numElements - 1); + final double discarded = internalArray[substIndex]; + + internalArray[substIndex] = value; + + return discarded; + } + + /** + * Checks the expansion factor and the contraction criterion and throws an + * IllegalArgumentException if the contractionCriteria is less than the + * expansionCriteria + * + * @param expansion factor to be checked + * @param contraction criteria to be checked + * @throws MathIllegalArgumentException if the contractionCriteria is less than + * the expansionCriteria. + * @deprecated As of 3.1. Please use + * {@link #checkContractExpand(double,double)} instead. + */ + @Deprecated + protected void checkContractExpand(float contraction, float expansion) + throws MathIllegalArgumentException { + checkContractExpand((double) contraction, + (double) expansion); + } + + /** + * Checks the expansion factor and the contraction criterion and raises + * an exception if the contraction criterion is smaller than the + * expansion criterion. + * + * @param contraction Criterion to be checked. + * @param expansion Factor to be checked. + * @throws NumberIsTooSmallException if {@code contraction < expansion}. + * @throws NumberIsTooSmallException if {@code contraction <= 1}. + * @throws NumberIsTooSmallException if {@code expansion <= 1 }. + * @since 3.1 + */ + protected void checkContractExpand(double contraction, + double expansion) + throws NumberIsTooSmallException { + if (contraction < expansion) { + final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, true); + e.getContext().addMessage(LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR, + contraction, expansion); + throw e; + } + + if (contraction <= 1) { + final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, false); + e.getContext().addMessage(LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_ONE, + contraction); + throw e; + } + + if (expansion <= 1) { + final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, false); + e.getContext().addMessage(LocalizedFormats.EXPANSION_FACTOR_SMALLER_THAN_ONE, + expansion); + throw e; + } + } + + /** + * Clear the array contents, resetting the number of elements to zero. + */ + public synchronized void clear() { + numElements = 0; + startIndex = 0; + } + + /** + * Contracts the storage array to the (size of the element set) + 1 - to + * avoid a zero length array. This function also resets the startIndex to + * zero. + */ + public synchronized void contract() { + final double[] tempArray = new double[numElements + 1]; + + // Copy and swap - copy only the element array from the src array. + System.arraycopy(internalArray, startIndex, tempArray, 0, numElements); + internalArray = tempArray; + + // Reset the start index to zero + startIndex = 0; + } + + /** + * Discards the i initial elements of the array. For example, + * if the array contains the elements 1,2,3,4, invoking + * discardFrontElements(2) will cause the first two elements + * to be discarded, leaving 3,4 in the array. Throws illegalArgumentException + * if i exceeds numElements. + * + * @param i the number of elements to discard from the front of the array + * @throws MathIllegalArgumentException if i is greater than numElements. + * @since 2.0 + */ + public synchronized void discardFrontElements(int i) + throws MathIllegalArgumentException { + discardExtremeElements(i,true); + } + + /** + * Discards the i last elements of the array. For example, + * if the array contains the elements 1,2,3,4, invoking + * discardMostRecentElements(2) will cause the last two elements + * to be discarded, leaving 1,2 in the array. Throws illegalArgumentException + * if i exceeds numElements. + * + * @param i the number of elements to discard from the end of the array + * @throws MathIllegalArgumentException if i is greater than numElements. + * @since 2.0 + */ + public synchronized void discardMostRecentElements(int i) + throws MathIllegalArgumentException { + discardExtremeElements(i,false); + } + + /** + * Discards the i first or last elements of the array, + * depending on the value of front. + * For example, if the array contains the elements 1,2,3,4, invoking + * discardExtremeElements(2,false) 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 + * discardExtremeElements(2,true) will cause the first two elements + * to be discarded, leaving 3,4 in the array. + * Throws illegalArgumentException + * if i exceeds numElements. + * + * @param i the number of elements to discard from the front/end of the array + * @param front true if elements are to be discarded from the front + * of the array, false if elements are to be discarded from the end + * of the array + * @throws MathIllegalArgumentException if i is greater than numElements. + * @since 2.0 + */ + private synchronized void discardExtremeElements(int i, + boolean front) + throws MathIllegalArgumentException { + if (i > numElements) { + throw new MathIllegalArgumentException( + LocalizedFormats.TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY, + i, numElements); + } else if (i < 0) { + throw new MathIllegalArgumentException( + LocalizedFormats.CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS, + i); + } else { + // "Subtract" this number of discarded from numElements + numElements -= i; + if (front) { + startIndex += i; + } + } + if (shouldContract()) { + contract(); + } + } + + /** + * Expands the internal storage array using the expansion factor. + *

              + * if expansionMode is set to MULTIPLICATIVE_MODE, + * the new array size will be internalArray.length * expansionFactor. + * If expansionMode is set to ADDITIVE_MODE, the length + * after expansion will be internalArray.length + expansionFactor + *

              + */ + protected synchronized void expand() { + // notice the use of FastMath.ceil(), this guarantees that we will always + // have an array of at least currentSize + 1. Assume that the + // current initial capacity is 1 and the expansion factor + // is 1.000000000000000001. The newly calculated size will be + // rounded up to 2 after the multiplication is performed. + int newSize = 0; + if (expansionMode == ExpansionMode.MULTIPLICATIVE) { + newSize = (int) FastMath.ceil(internalArray.length * expansionFactor); + } else { + newSize = (int) (internalArray.length + FastMath.round(expansionFactor)); + } + final double[] tempArray = new double[newSize]; + + // Copy and swap + System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); + internalArray = tempArray; + } + + /** + * Expands the internal storage array to the specified size. + * + * @param size Size of the new internal storage array. + */ + private synchronized void expandTo(int size) { + final double[] tempArray = new double[size]; + // Copy and swap + System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); + internalArray = tempArray; + } + + /** + * The contraction criteria defines when the internal array will contract + * to store only the number of elements in the element array. + * If the expansionMode is MULTIPLICATIVE_MODE, + * contraction is triggered when the ratio between storage array length + * and numElements exceeds contractionFactor. + * If the expansionMode is ADDITIVE_MODE, the + * number of excess storage locations is compared to + * contractionFactor. + * + * @return the contraction criteria used to reclaim memory. + * @deprecated As of 3.1. Please use {@link #getContractionCriterion()} + * instead. + */ + @Deprecated + public float getContractionCriteria() { + return (float) getContractionCriterion(); + } + + /** + * The contraction criterion defines when the internal array will contract + * to store only the number of elements in the element array. + * If the expansionMode is MULTIPLICATIVE_MODE, + * contraction is triggered when the ratio between storage array length + * and numElements exceeds contractionFactor. + * If the expansionMode is ADDITIVE_MODE, the + * number of excess storage locations is compared to + * contractionFactor. + * + * @return the contraction criterion used to reclaim memory. + * @since 3.1 + */ + public double getContractionCriterion() { + return contractionCriterion; + } + + /** + * Returns the element at the specified index + * + * @param index index to fetch a value from + * @return value stored at the specified index + * @throws ArrayIndexOutOfBoundsException if index is less than + * zero or is greater than getNumElements() - 1. + */ + public synchronized double getElement(int index) { + if (index >= numElements) { + throw new ArrayIndexOutOfBoundsException(index); + } else if (index >= 0) { + return internalArray[startIndex + index]; + } else { + throw new ArrayIndexOutOfBoundsException(index); + } + } + + /** + * Returns a double array containing the elements of this + * ResizableArray. 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 ResizableArray. + * @return the double array. + */ + public synchronized double[] getElements() { + final double[] elementArray = new double[numElements]; + System.arraycopy(internalArray, startIndex, elementArray, 0, numElements); + return elementArray; + } + + /** + * The expansion factor controls the size of a new array when an array + * needs to be expanded. The expansionMode + * determines whether the size of the array is multiplied by the + * expansionFactor (MULTIPLICATIVE_MODE) or if + * the expansion is additive (ADDITIVE_MODE -- expansionFactor + * storage locations added). The default expansionMode is + * MULTIPLICATIVE_MODE and the default expansionFactor + * is 2.0. + * + * @return the expansion factor of this expandable double array + * @deprecated As of 3.1. Return type will be changed to "double" in 4.0. + */ + @Deprecated + public float getExpansionFactor() { + return (float) expansionFactor; + } + + /** + * The expansion mode determines whether the internal storage + * array grows additively or multiplicatively when it is expanded. + * + * @return the expansion mode. + * @deprecated As of 3.1. Return value to be changed to + * {@link ExpansionMode} in 4.0. + */ + @Deprecated + public int getExpansionMode() { + synchronized (this) { + switch (expansionMode) { + case MULTIPLICATIVE: + return MULTIPLICATIVE_MODE; + case ADDITIVE: + return ADDITIVE_MODE; + default: + throw new MathInternalError(); // Should never happen. + } + } + } + + /** + * Notice the package scope on this method. This method is simply here + * for the JUnit test, it allows us check if the expansion is working + * properly after a number of expansions. This is not meant to be a part + * of the public interface of this class. + * + * @return the length of the internal storage array. + * @deprecated As of 3.1. Please use {@link #getCapacity()} instead. + */ + @Deprecated + synchronized int getInternalLength() { + return internalArray.length; + } + + /** + * Gets the currently allocated size of the internal data structure used + * for storing elements. + * This is not to be confused with {@link #getNumElements() the number of + * elements actually stored}. + * + * @return the length of the internal array. + * @since 3.1 + */ + public int getCapacity() { + return internalArray.length; + } + + /** + * Returns the number of elements currently in the array. Please note + * that this is different from the length of the internal storage array. + * + * @return the number of elements. + */ + public synchronized int getNumElements() { + return numElements; + } + + /** + * Returns the internal storage array. Note that this method returns + * a reference to the internal storage array, not a copy, and to correctly + * address elements of the array, the startIndex is + * required (available via the {@link #start} method). This method should + * only be used in cases where copying the internal array is not practical. + * The {@link #getElements} method should be used in all other cases. + * + * + * @return the internal storage array used by this object + * @since 2.0 + * @deprecated As of 3.1. + */ + @Deprecated + public synchronized double[] getInternalValues() { + return internalArray; + } + + /** + * Provides direct access to the internal storage array. + * Please note that this method returns a reference to this object's + * storage array, not a copy. + *
              + * To correctly address elements of the array, the "start index" is + * required (available via the {@link #getStartIndex() getStartIndex} + * method. + *
              + * This method should only be used to avoid copying the internal array. + * The returned value must be used for reading only; other + * uses could lead to this object becoming inconsistent. + *
              + * The {@link #getElements} method has no such limitation since it + * returns a copy of this array's addressable elements. + * + * @return the internal storage array used by this object. + * @since 3.1 + */ + protected double[] getArrayRef() { + return internalArray; + } + + /** + * Returns the "start index" of the internal array. + * This index is the position of the first addressable element in the + * internal storage array. + * The addressable elements in the array are at indices contained in + * the interval [{@link #getStartIndex()}, + * {@link #getStartIndex()} + {@link #getNumElements()} - 1]. + * + * @return the start index. + * @since 3.1 + */ + protected int getStartIndex() { + return startIndex; + } + + /** + * Sets the contraction criteria. + * + * @param contractionCriteria contraction criteria + * @throws MathIllegalArgumentException if the contractionCriteria is less than + * the expansionCriteria. + * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final"). + */ + @Deprecated + public void setContractionCriteria(float contractionCriteria) + throws MathIllegalArgumentException { + checkContractExpand(contractionCriteria, getExpansionFactor()); + synchronized(this) { + this.contractionCriterion = contractionCriteria; + } + } + + /** + * Performs an operation on the addressable elements of the array. + * + * @param f Function to be applied on this array. + * @return the result. + * @since 3.1 + */ + public double compute(MathArrays.Function f) { + final double[] array; + final int start; + final int num; + synchronized(this) { + array = internalArray; + start = startIndex; + num = numElements; + } + return f.evaluate(array, start, num); + } + + /** + * Sets the element at the specified index. If the specified index is greater than + * getNumElements() - 1, the numElements property + * is increased to index +1 and additional storage is allocated + * (if necessary) for the new element and all (uninitialized) elements + * between the new element and the previous end of the array). + * + * @param index index to store a value in + * @param value value to store at the specified index + * @throws ArrayIndexOutOfBoundsException if {@code index < 0}. + */ + public synchronized void setElement(int index, double value) { + if (index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + if (index + 1 > numElements) { + numElements = index + 1; + } + if ((startIndex + index) >= internalArray.length) { + expandTo(startIndex + (index + 1)); + } + internalArray[startIndex + index] = value; + } + + /** + * Sets the expansionFactor. Throws IllegalArgumentException if the + * the following conditions are not met: + *
                + *
              • expansionFactor > 1
              • + *
              • contractionFactor >= expansionFactor
              • + *
              + * @param expansionFactor the new expansion factor value. + * @throws MathIllegalArgumentException if expansionFactor is <= 1 or greater + * than contractionFactor + * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final"). + */ + @Deprecated + public void setExpansionFactor(float expansionFactor) throws MathIllegalArgumentException { + checkContractExpand(getContractionCriterion(), expansionFactor); + // The check above verifies that the expansion factor is > 1.0; + synchronized(this) { + this.expansionFactor = expansionFactor; + } + } + + /** + * Sets the expansionMode. The specified value must be one of + * ADDITIVE_MODE, MULTIPLICATIVE_MODE. + * + * @param expansionMode The expansionMode to set. + * @throws MathIllegalArgumentException if the specified mode value is not valid. + * @deprecated As of 3.1. Please use {@link #setExpansionMode(ExpansionMode)} instead. + */ + @Deprecated + public void setExpansionMode(int expansionMode) + throws MathIllegalArgumentException { + if (expansionMode != MULTIPLICATIVE_MODE && + expansionMode != ADDITIVE_MODE) { + throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_EXPANSION_MODE, expansionMode, + MULTIPLICATIVE_MODE, "MULTIPLICATIVE_MODE", + ADDITIVE_MODE, "ADDITIVE_MODE"); + } + synchronized(this) { + if (expansionMode == MULTIPLICATIVE_MODE) { + setExpansionMode(ExpansionMode.MULTIPLICATIVE); + } else if (expansionMode == ADDITIVE_MODE) { + setExpansionMode(ExpansionMode.ADDITIVE); + } + } + } + + /** + * Sets the {@link ExpansionMode expansion mode}. + * + * @param expansionMode Expansion mode to use for resizing the array. + * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final"). + */ + @Deprecated + public void setExpansionMode(ExpansionMode expansionMode) { + synchronized(this) { + this.expansionMode = expansionMode; + } + } + + /** + * Sets the initial capacity. Should only be invoked by constructors. + * + * @param initialCapacity of the array + * @throws MathIllegalArgumentException if initialCapacity is not + * positive. + * @deprecated As of 3.1, this is a no-op. + */ + @Deprecated + protected void setInitialCapacity(int initialCapacity) + throws MathIllegalArgumentException { + // Body removed in 3.1. + } + + /** + * This function allows you to control the number of elements contained + * in this array, and can be used to "throw out" the last n values in an + * array. This function will also expand the internal array as needed. + * + * @param i a new number of elements + * @throws MathIllegalArgumentException if i is negative. + */ + public synchronized void setNumElements(int i) + throws MathIllegalArgumentException { + // If index is negative thrown an error. + if (i < 0) { + throw new MathIllegalArgumentException( + LocalizedFormats.INDEX_NOT_POSITIVE, + i); + } + + // Test the new num elements, check to see if the array needs to be + // expanded to accommodate this new number of elements. + final int newSize = startIndex + i; + if (newSize > internalArray.length) { + expandTo(newSize); + } + + // Set the new number of elements to new value. + numElements = i; + } + + /** + * Returns true if the internal storage array has too many unused + * storage positions. + * + * @return true if array satisfies the contraction criteria + */ + private synchronized boolean shouldContract() { + if (expansionMode == ExpansionMode.MULTIPLICATIVE) { + return (internalArray.length / ((float) numElements)) > contractionCriterion; + } else { + return (internalArray.length - numElements) > contractionCriterion; + } + } + + /** + * Returns the starting index of the internal array. The starting index is + * the position of the first addressable element in the internal storage + * array. The addressable elements in the array are + * internalArray[startIndex],...,internalArray[startIndex + numElements -1] + * + * + * @return the starting index. + * @deprecated As of 3.1. + */ + @Deprecated + public synchronized int start() { + return startIndex; + } + + /** + *

              Copies source to dest, copying the underlying data, so dest is + * a new, independent copy of source. Does not contract before + * the copy.

              + * + *

              Obtains synchronization locks on both source and dest + * (in that order) before performing the copy.

              + * + *

              Neither source nor dest may be null; otherwise a {@link NullArgumentException} + * is thrown

              + * + * @param source ResizableDoubleArray to copy + * @param dest ResizableArray to replace with a copy of the source array + * @exception NullArgumentException if either source or dest is null + * @since 2.0 + * + */ + public static void copy(ResizableDoubleArray source, + ResizableDoubleArray dest) + throws NullArgumentException { + MathUtils.checkNotNull(source); + MathUtils.checkNotNull(dest); + synchronized(source) { + synchronized(dest) { + dest.contractionCriterion = source.contractionCriterion; + dest.expansionFactor = source.expansionFactor; + dest.expansionMode = source.expansionMode; + dest.internalArray = new double[source.internalArray.length]; + System.arraycopy(source.internalArray, 0, dest.internalArray, + 0, dest.internalArray.length); + dest.numElements = source.numElements; + dest.startIndex = source.startIndex; + } + } + } + + /** + * Returns a copy of the ResizableDoubleArray. Does not contract before + * the copy, so the returned object is an exact copy of this. + * + * @return a new ResizableDoubleArray with the same data and configuration + * properties as this + * @since 2.0 + */ + public synchronized ResizableDoubleArray copy() { + final ResizableDoubleArray result = new ResizableDoubleArray(); + copy(this, result); + return result; + } + + /** + * Returns true iff object is a ResizableDoubleArray with the same properties + * as this and an identical internal storage array. + * + * @param object object to be compared for equality with this + * @return true iff object is a ResizableDoubleArray with the same data and + * properties as this + * @since 2.0 + */ + @Override + public boolean equals(Object object) { + if (object == this ) { + return true; + } + if (object instanceof ResizableDoubleArray == false) { + return false; + } + synchronized(this) { + synchronized(object) { + boolean result = true; + final ResizableDoubleArray other = (ResizableDoubleArray) object; + result = result && (other.contractionCriterion == contractionCriterion); + result = result && (other.expansionFactor == expansionFactor); + result = result && (other.expansionMode == expansionMode); + result = result && (other.numElements == numElements); + result = result && (other.startIndex == startIndex); + if (!result) { + return false; + } else { + return Arrays.equals(internalArray, other.internalArray); + } + } + } + } + + /** + * Returns a hash code consistent with equals. + * + * @return the hash code representing this {@code ResizableDoubleArray}. + * @since 2.0 + */ + @Override + public synchronized int hashCode() { + final int[] hashData = new int[6]; + hashData[0] = Double.valueOf(expansionFactor).hashCode(); + hashData[1] = Double.valueOf(contractionCriterion).hashCode(); + hashData[2] = expansionMode.hashCode(); + hashData[3] = Arrays.hashCode(internalArray); + hashData[4] = numElements; + hashData[5] = startIndex; + return Arrays.hashCode(hashData); + } + +} diff --git a/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/TransformerMap.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/TransformerMap.java new file mode 100644 index 000000000..0af09a33a --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/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 com.fr.third.org.apache.commons.math3.util; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.math3.exception.MathIllegalArgumentException; + +/** + * 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 + * double transform(Object o) method. + */ +public class TransformerMap implements NumberTransformer, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 4605318041528645258L; + + /** + * A default Number Transformer for Numbers and numeric Strings. + */ + private NumberTransformer defaultTransformer = null; + + /** + * The internal Map. + */ + private Map, NumberTransformer> map = null; + + /** + * Build a map containing only the default transformer. + */ + public TransformerMap() { + map = new HashMap, 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> classes() { + return map.keySet(); + } + + /** + * Returns the Set of NumberTransformers used as values + * in the map. + * @return Set of NumberTransformers + */ + public Collection transformers() { + return map.values(); + } + + /** + * Attempts to transform the Object against the map of + * NumberTransformers. Otherwise it returns Double.NaN. + * + * @param o the Object to be transformed. + * @return the double value of the Object. + * @throws MathIllegalArgumentException if the Object can not be + * transformed into a Double. + * @see NumberTransformer#transform(java.lang.Object) + */ + public double transform(Object o) throws MathIllegalArgumentException { + double value = Double.NaN; + + if (o instanceof Number || o instanceof String) { + value = defaultTransformer.transform(o); + } else { + NumberTransformer trans = getTransformer(o.getClass()); + if (trans != null) { + value = trans.transform(o); + } + } + + return value; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof TransformerMap) { + TransformerMap rhs = (TransformerMap) other; + if (! defaultTransformer.equals(rhs.defaultTransformer)) { + return false; + } + if (map.size() != rhs.map.size()) { + return false; + } + for (Map.Entry, 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/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/package-info.java b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/package-info.java new file mode 100644 index 000000000..5c1ea3a78 --- /dev/null +++ b/fine-commons-math3/src/com/fr/third/org/apache/commons/math3/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Convenience routines and common data structures used throughout the commons-math library. + */ +package com.fr.third.org.apache.commons.math3.util;